Upload de Arquivos

O upload de arquivos no Django é o processo de permitir que os usuários enviem arquivos para o servidor por meio de um formulário HTML. O Django possui ferramentas integradas para lidar com arquivos enviados e armazená-los de maneira eficiente no servidor.

Quando temos uma entrada de arquivo em HTML input type="file" precisamos informar isso ao navegador com a propriedade enctype="multipart/form-data" para que ele envie arquivos junto com outros dados do formulário. O Django nos dá a possibilidade de acessarmos os arquivos enviados (pelo html por exemplo) através da propriedade especial FILES(um dicionário python) da requisição, assim como todo o conteúdo em POST.

O método chunks() faz parte da classe UploadedFile, que o Django utiliza para manipular arquivos enviados por formulários, ou seja, todo formulário é um objeto do tipo UploadedFile. Chunks divide o arquivo em partes menores (chunks) para processamento. Quando o arquivo é grande, processar tudo de uma vez pode causar problemas de memória. Dividir o arquivo em partes menores permite manipulá-lo de maneira mais eficiente e evita que o servidor consuma toda a memória disponível.

| Exemplo Upload de Arquivos

Leve em consideração a seguinte estrutura:

criar_perfil_upload.html
<!DOCTYPE html>

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Criar um Perfil</title>
</head>

<body>
  <form action="/perfis/" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="file" name="Imagem" />
    <button>Upload!</button>
  </form>
</body>

</html>
views.py
from django.shortcuts import render
from django.views import View
from django.http import HttpResponseRedirect

def armazenar_arquivo(file):

    # Abre o arquivo em modo binário
    with open("temp/image.jpeg", 'wb+') as destination:
        # Escreve/Salva o Arquivo em partes pois pode ser pesado
        for chunk in file.chunks():
            destination.write(chunk)

class CriarPerfilView(View):
    def get(self, request):
        return render(request, "perfis/criar_perfil_upload.html")

    def post(self, request):
        # Recupera o arquivo imagem enviado no formulário e chama a função para salvar
        armazenar_arquivo(request.FILES['imagem'])
        return HttpResponseRedirect("/perfis")

A função armazenar_arquivo está no mesmo arquivo (views.py) apenas para facilitar o entendimento. temp/ estará na pasta principal do projeto.

urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.CriarPerfilView.as_view())
]

| Exemplo Upload com Forms

forms.py
from django import forms

class PerfilForm(forms.Form):
    usuario_imagem = forms.FileField()
views_forms.py
from django.shortcuts import render
from django.views import View
from django.http import HttpResponseRedirect
from .forms import PerfilForm

def armazenar_arquivo(file):
    with open("temp/image.jpeg", 'wb+') as destination:
        for chunk in file.chunks():
            destination.write(chunk)

class CriarPerfilView(View):
    def get(self, request):
        form = PerfilForm()
        return render(request, "perfis/criar_perfil_upload.html",{
            "form": form
        })

    def post(self, request):
        formulario_enviado = PerfilForm(request.POST,request.FILES)

        if formulario_enviado.is_valid():
            armazenar_arquivo(request.FILES['usuario_imagem'])
            return HttpResponseRedirect("/perfis")

        return render(request, "perfis/criar_perfil_upload.html",{
            "form": formulario_enviado
        })
criar_perfil_upload.html
<!DOCTYPE html>

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Criar um Perfil</title>
</head>

<body>
  <form action="/perfis" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{form}}
    <button>Upload!</button>
  </form>
</body>

</html>

| Exemplo Upload com Models

FileField cria um campo para armazenar o caminho do arquivo no banco de dados. E quando criado também move os arquivos automaticamente para o local/pasta designada no parametro upload_to (renomeando automáticamente o arquivo se já existir), onde ele busca a pasta na raiz do sistema e não do projeto. Para que não haja problemas, nas configurações da pasta projeto (setting.py) é necessário informar ao django onde devem ficar os arquivos upados na variável MEDIA_ROOT. Internamente, o FileField salva apenas o caminho do arquivo no banco de dados, enquanto o arquivo em si é salvo no local configurado por MEDIA_ROOT.

models.py
from django.db import models

class PerfilUsuario(models.Model):
    # Cria uma pasta imagens_perfil dentro da pasta definida como MEDIA_ROOT (uploads)
    imagem = models.FileField(upload_to="imagens_perfil")
    # Se colocar /%Y/%m/%d/ o django automaticamente interpreta a data e cria uma pasta com a data atual o que facilita a busca e organiza melhor.
    imagem = models.FileField(upload_to="imagens_perfil/%Y/%m/%d/")
settings.py
# Define um caminho absoluto para armazenar os arquivos com base na pasta raiz.
MEDIA_ROOT = BASE / "uploads"
views.py
from django.shortcuts import render
from django.views import View
from django.http import HttpResponseRedirect

from .forms import PerfilForm
from .models import PerfilUsuario

class CriarPerfilView(View):
    def get(self, request):
        form = PerfilForm()
        return render(request, "perfis/criar_perfil_upload.html",{
            "form": form
        })

    def post(self, request):
        formulario_enviado = PerfilForm(request.POST,request.FILES)

        if formulario_enviado.is_valid():
            perfil = PerfilUsuario(imagem=request.FILES['usuario_imagem'])
            perfil.save()
            return HttpResponseRedirect("/perfis")

        return render(request, "perfis/criar_perfil_upload.html",{
            "form": formulario_enviado
        })

Um objeto do tipo modelo (PerfilUsuario) está sendo criado com o valor do arquivo enviado pelo formulário (request.FILES['usuario_imagem']) atribuído ao campo imagem desse modelo.

views_com_CreateView.py
from django.views.generic import CreateView
from django.http import HttpResponseRedirect

from .models import PerfilUsuario

class CriarPerfilView(CreateView):
    model = PerfilUsuario
    template_name = "perfis/criar_perfil_upload.html"
    fields = "__all__"
    success_url = "/perfis"

Toda a nossa view, desse exemplo, pode ser substituída por essa CBV CreateView. Sem precisar do forms.py.

No exemplo acima usamos no campo o FileField mas para aceitar apenas imagens use ImageField. E para isso é nessário instalar um pacote extra

Instalar ImageField

bash
$ python -m pip install Pillow

Arquivos Estáticos (Statics Files)

Os arquivos estáticos (statics) no Django são arquivos que não mudam frequentemente e são servidos diretamente para o navegador do cliente. Exemplos típicos incluem: Arquivos CSS, JavaScript, Imagens, Fontes. Geralmente, os arquivos estáticos são armazenados em um diretório chamado static no seu projeto ou dentro de cada aplicativo. Veja mais: Arquivos Estáticos.

 

  • STATIC_URL: URL base para os arquivos estáticos que informa o django onde ele deve SERVIR os arquivos staticos de seu projeto. o Django usa essa URL como base para encontrar o arquivo estatico referenciado no html.
  • STATICFILES_DIRS: Diretórios extras com arquivos estáticos no sistema de arquivos. Durante o desenvolvimento, se você tem arquivos estáticos que não estão dentro de um app (por exemplo, arquivos CSS globais em uma pasta static/ no nível do projeto), você deve informar ao Django onde encontrá-los.
  • STATIC_ROOT: Diretório onde os arquivos estáticos serão coletados para uso em ambiente de produção, Deve ser feita uma coleta de arquivos estáticos, onde, todos os arquivos estáticos são coletados em um único diretório especificado por STATIC_ROOT. O comando collectstatic coleta toda a estrutura de diretórios do static/. preservando a estrutura de meu_app/static/meu_app.

Durante o desenvolvimento, o Django serve arquivos estáticos automaticamente. No entanto, em produção, é mais eficiente que o servidor web cuide disso (como nginx ou apache), distribuindo através do diretório definiddo em STATIC_ROOT.

 

Coleta de Arquivos Estáticos

A coleta de arquivos estáticos é feita através desse comando, no ambiente de produção, que transfere todos os arquivos para o diretório especificado em STATIC_ROOT configurado em settings.py. Ao aplicar esse comando, toda seus arquivos serão copiados para a pasta definida em settings.py, e toda vez que fizer alguma alteração, é necessário rodar o comando novamente, onde ele só copiará/substituirá arquivos que forem alterados.

Mas ATENÇÃO. Se os arquivos estiverem em pastas com a mesma estrutura relativa, mas em apps diferentes, o último arquivo processado pelo Django sobrescreverá o anterior. Para evitar problemas, mantenha os arquivos organizados com nomes únicos ou dentro de subpastas específicas de cada app.

bash
$ python manage.py collectstatic
settings_static.py
# Define o prefixo da URL para acessar os arquivos estáticos no navegador.
STATIC_URL = 'static/'

# instrui o Django a buscar arquivos estáticos também na pasta na raiz do projeto.
STATICFILES_DIRS = [
    BASE_DIR / 'static/'
]

# Quando a aplicação está em produção, define a pasta onde todos os arquivos estáticos estarão
STATIC_ROOT = BASE_DIR / 'staticfiles/'
html_com_estatico.html
<!-- Carrega arquivo estáticos para pagina -->
{% load static %}

<!-- Mostra aonde carrega-los -->
<link rel="stylesheet" href="{% static 'css/header.css' %}">

Carregue o arquivo estático para página! 


Servindo Arquivos

URL VS PATH

A propriedade url difere de path, onde a url representa a URL pública do arquivo, ou seja, o caminho completo acessível ao navegador ou cliente HTTP e deve ser usada para exibir o arquivo em um template ou fornecer um link público. Já path representa o caminho absoluto no sistema de arquivos do servidor onde o arquivo foi salvo, e deve ser usado em situações onde você precisa manipular diretamente o arquivo no servidor, como abrir, editar, deletar e executar operações específicas (ex.: redimensionar imagens, processar dados, etc.) no arquivo.

O Django automaticamente bloqueia o acesso a todas as pastas do projeto (menos a estática que ele configura uma url pública automáticamente, ou seja, ele serve css, imagens estáticas da pasta static apenas no ambiente de desenvolvimento), sendo assim, os arquivos de imagens ficam bloqueados para serem exibidos. Por isso a propriedade MEDIA_URL o arquivo settings.py deverá ser configurada.

MEDIA_URL é uma configuração que funciona como uma representação pública (URL) do caminho físico definido em MEDIA_ROOT. Ela não tem existência física no sistema de arquivos, mas serve para mapear as solicitações HTTP para os arquivos de mídia armazenados no diretório especificado por MEDIA_ROOT. Sendo assim MEDIA_URL como funciona como uma "ponte" que liga o mundo web ao sistema de arquivos físico.

Configuração Descrição Exemplo
MEDIA_ROOT Define onde os arquivos de mídia estão armazenados fisicamente no servidor. /home/user/meuprojeto/media/
MEDIA_URL Define o caminho público para acessar esses arquivos pela web. http://localhost:8000/media/ ou /media/

Além disso, no aquivo urls.py  (do projeto principal) deve-se usar a função static(), para informar ao Django a url que deve ser usada para mostrar os arquivos publicamente ( MEDIA_URL) o caminho físico/path onde eles se encontram (MEDIA_ROOT). O operador + no contexto do Python soma listas, ou seja, ele concatena duas ou mais listas em uma só. No caso do Django, urlpatterns é uma lista de rotas, e static() retorna outra lista que contém as configurações para servir arquivos de mídia. O + junta essas duas listas.

Por padrão, segurança e desempenho a seguinte estrutura deve ser levada em consideração, definindo MEDIA_ROOT = BASE_DIR/"media/" e MEDIA_URL = "/media/" .Evite usar a pasta static/ para uploads de usuários já que é destinado a arquivos estáticos que não mudam (CSS, JS, imagens fixas). Já os uploads de usuários podem ser alterados frequentemente e exigem um caminho dinâmico, tornando o uso de media/ mais adequado.

| Exemplo Expor Arquivos

usuario_perfil.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Perfil do Usuário</title>
</head>
<body>
    <h1>Perfis dos Usuários</h1>
    <ul>
        {% for perfil in perfis %}
            <li><img src="{{ perfil.imagem.url }}" alt="Imagem do perfil"></li>
        {% endfor %}
    </ul>
</body>
</html>
views.py
from django.views.generic import ListView
from .models import PerfilUsuario

class PerfilView(ListView):
    model = PerfilUsuario
    template_name = "perfis/usuario_perfil.html"
    context_object_name = "perfis"
urls_principal.py
from django.contrib import admin
from django.urls import path, include

# Importa o método static que é usado para informar a Django os arquivos que serão expostos
from django.conf.urls.static import static

# Importa MEDIA_URL (url pública) e MEDIA_ROOT (path físico)
from django.conf import settings 

urlpatterns = [
    path('admin/', admin.site.urls),
    path("perfis/", include("perfis.urls")),
]  + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

A função static cria uma rota de URL baseada em MEDIA_URL e faz o Django servir arquivos do diretório especificado em MEDIA_ROOT.

urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("lista", views.PerfilView.as_view()),
]
settings.py
MEDIA_ROOT = BASE_DIR / 'uploads'

# Configura acesso externo
MEDIA_URL = '/midias-usuarios/'

| Servir Arquivos Estáticos Pelo Django

Essa opção server para servidores pequenos sem muitas requisições.

urls_principal.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings 
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path("perfis/", include("perfis.urls")),
]  + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \

# Essa linha configura o Django para servir os Arquivos estáticos
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

# Também pode ser feito como abaixo
urlpatteners += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)