Gerenciando Upload e Download de Arquivos no Django

O upload de arquivos em aplicações web geralmente segue dois padrões principais: submissão via formulário HTML tradicional e envio assíncrono via AJAX.

Upload via Formulário HTML

O envio de arquivos via formulário HTML requer configurações específicas para que o servidor possa interpretar corretamente os dados binários. A especificação RFC 1867 introduziu essa funcionalidade, definindo que o método HTTP deve ser POST e o atributo enctype do formulário deve ser configurado para multipart/form-data. Além disso, um campo de entrada do tipo file é necessário.

Ao usar enctype="multipart/form-data", a requisição HTTP utiliza um delimitador (boundary) para separar os diferentes campos e o próprio arquivo dentro do corpo da requisição. Este delimitador é uma string aleatória que garante a integridade dos dados, prevenindo conflitos com caracteres que possam existir dentro do conteúdo do arquivo. O servidor utiliza esse delimitador para segmentar e processar os dados corretamente.

No lado do servidor Django, o arquivo enviado pode ser acessado através de request.FILES. Os dados de texto e outros campos do formulário estão disponíveis em request.POST.

Exemplo de Formulário (Frontend)

<form action="" method="post" enctype="multipart/form-data">
   {% csrf_token %}
   <input type="file" name="arquivo_carregado">
   <input type="text" name="nome_usuario">
   <input type="submit" value="Enviar">
</form>

Exemplo de View (Backend - Django)

from django.shortcuts import render, redirect
from django.db import transaction
import xlrd # Supondo que xlrd seja usado para processar arquivos Excel

def importar_dados(request, projeto_pk):
   """ Importa dados de um arquivo Excel para um projeto específico. """
   if request.method == 'POST':
       try:
           with transaction.atomic():
               arquivo_excel = request.FILES.get('arquivo_carregado')
               if not arquivo_excel:
                   return render(request, 'template_importacao.html', {"projeto_pk": projeto_pk, "erro": "Nenhum arquivo foi selecionado."})

               # Lê o conteúdo do arquivo sem salvá-lo em disco temporariamente
               workbook = xlrd.open_workbook(filename=None, file_contents=arquivo_excel.read())
               sheet = workbook.sheet_by_index(0) # Pega a primeira planilha

               # Processa os dados da planilha (exemplo: iterando sobre as linhas)
               for i in range(1, sheet.nrows):
                   dados_linha = sheet.row_values(i)
                   print(f"Processando linha {i}: {dados_linha}")
                   # Aqui você adicionaria a lógica para salvar os dados no banco de dados

           return render(request, 'template_importacao.html', {"projeto_pk": projeto_pk, "mensagem_sucesso": "Upload e processamento bem-sucedidos!"})
       except xlrd.XLRDError:
           return render(request, 'template_importacao.html', {"projeto_pk": projeto_pk, "erro": "Formato de arquivo inválido. Suportamos apenas arquivos Excel (.xls, .xlsx)."})
       except Exception as e:
           print(f"Erro durante o processamento: {e}")
           return render(request, 'template_importacao.html', {"projeto_pk": projeto_pk, "erro": f"Ocorreu um erro inesperado: {e}"})

   return render(request, 'template_importacao.html', {"projeto_pk": projeto_pk, "erro": ""})

Upload via AJAX

O envio de aruqivos via AJAX oferece uma experiência de usuário mais fluida, sem a necessidade de recarregar a página inteira. Para isso, utiliza-se o objeto FormData no JavaScript, que permite construir um conjnuto de pares chave/valor a serem enviados, semelhante a como um formulário HTML faz. É crucial incluir o token CSRF para a segurança da aplicação.

Ao configurar a requisição AJAX, é importante definir processData: false e contentType: false. O primeiro impede que o jQuery serialize os dados automaticamente, e o segundo instrui o navegador a não definir o cabeçalho Content-Type, permitindo que o servidor defina o delimitador correto para multipart/form-data.

Exemplo de Código (Frontend - JavaScript com jQuery)


<html lang="pt-BR">
<head>
   <meta charset="UTF-8">
   <title>Upload AJAX</title>
</head>
<body>
<div>
   {% csrf_token %}
   <input type="file" id="arquivoAjax">
   <button id="btnUploadAjax">Enviar via AJAX</button>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
   $("#btnUploadAjax").click(function () {
       var formData = new FormData();
       var arquivoSelecionado = document.getElementById('arquivoAjax').files[0];
       
       if (!arquivoSelecionado) {
           alert("Por favor, selecione um arquivo para enviar.");
           return;
       }

       formData.append('arquivo_upload', arquivoSelecionado);
       formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
       formData.append('usuario_remetente', 'Alice'); // Exemplo de dado adicional

       $.ajax({
           url: "/api/upload/", // URL da sua view de upload
           type: "POST",
           data: formData,
           processData: false,
           contentType: false,
           success: function (resposta) {
               console.log("Resposta do servidor:", resposta);
               alert("Upload realizado com sucesso!");
           },
           error: function (jqXHR, textStatus, errorThrown) {
               console.error("Erro no upload:", textStatus, errorThrown);
               alert("Falha no upload do arquivo.");
           }
       });
   });
</script>
</body>
</html>

Exemplo de View (Backend - Django)

from django.shortcuts import render
from django.http import JsonResponse
import xlrd # Exemplo com xlrd

def handle_ajax_upload(request):
   if request.is_ajax() and request.method == 'POST':
       # Acessa o arquivo enviado
       arquivo = request.FILES.get('arquivo_upload')
       # Dados de texto adicionais
       usuario = request.POST.get('usuario_remetente')
       
       if not arquivo:
           return JsonResponse({"status": "erro", "mensagem": "Nenhum arquivo foi recebido."}, status=400)

       print(f"Arquivo recebido: {arquivo.name} ({arquivo.content_type})")
       print(f"Usuário remetente: {usuario}")

       try:
           # Exemplo: processar arquivo Excel
           workbook = xlrd.open_workbook(filename=None, file_contents=arquivo.read())
           sheet = workbook.sheet_by_index(0)
           print(f"Primeira linha da planilha: {sheet.row_values(0)}")
           # Lógica de processamento...
           
           return JsonResponse({"status": "sucesso", "mensagem": "Arquivo processado com êxito.", "dados_recebidos": {"usuario": usuario}})
       except xlrd.XLRDError:
           return JsonResponse({"status": "erro", "mensagem": "Formato de arquivo não suportado."}, status=400)
       except Exception as e:
           print(f"Erro no processamento do arquivo: {e}")
           return JsonResponse({"status": "erro", "mensagem": "Erro interno no servidor."}, status=500)
           
   return render(request, 'template_upload_ajax.html')

Download de Arquivos

O Django oferece classes eficientes para servir arquivos estáticos, como StreamingHttpResponse e FileResponse. Ambas são adequadas para download, com FileResponse sendo geralmente preferível para arquivos locais, pois pode otimizar o envio.

Ao configurar a resposta de download, é importante definir os cabeçalhos Content-Type e Content-Disposition. O Content-Type especifica o tipo MIME do arquivo, e Content-Disposition: attachment;filename="nome_arquivo.ext" instrui o navegador a baixar o arquivo com o nome especificado.

Download com StreamingHttpResponse

from django.http import StreamingHttpResponse
import os

def download_stream(request):
   caminho_arquivo = 'caminho/para/seu/arquivo.txt' # Substitua pelo caminho real
   if os.path.exists(caminho_arquivo):
       arquivo_aberto = open(caminho_arquivo, 'rb')
       resposta = StreamingHttpResponse(arquivo_aberto, content_type='application/octet-stream')
       resposta['Content-Disposition'] = f'attachment; filename="{os.path.basename(caminho_arquivo)}"'
       return resposta
   else:
       return JsonResponse({"erro": "Arquivo não encontrado"}, status=404)

Download com FileResponse

from django.http import FileResponse
import os

def download_file(request):
   caminho_arquivo = 'caminho/para/seu/arquivo.txt' # Substitua pelo caminho real
   if os.path.exists(caminho_arquivo):
       arquivo_aberto = open(caminho_arquivo, 'rb')
       resposta = FileResponse(arquivo_aberto, content_type='application/octet-stream')
       resposta['Content-Disposition'] = f'attachment; filename="{os.path.basename(caminho_arquivo)}"'
       return resposta
   else:
       return JsonResponse({"erro": "Arquivo não encontrado"}, status=404)

Tratamento de Nomes de Arquivo com Caracteres Especiais (Ex: Chineses)

Nomes de arquivo contendo caracteres não-ASCII podem causar problemas em alguns navegadores. O Django oferece a função escape_uri_path para codificar corretamente esses nomes, garantindo compatibilidade.

from django.http import FileResponse
from django.utils.encoding import escape_uri_path
import os

def download_arquivo_nome_especial(request):
   nome_arquivo_original = "Arquivo com Caracteres Especiais.txt" # Exemplo
   caminho_arquivo = 'caminho/para/seu/arquivo.txt' # Caminho real do arquivo

   if os.path.exists(caminho_arquivo):
       arquivo_aberto = open(caminho_arquivo, 'rb')
       resposta = FileResponse(arquivo_aberto, content_type='application/octet-stream')
       
       # Codifica o nome do arquivo para garantir compatibilidade
       nome_arquivo_codificado = escape_uri_path(nome_arquivo_original)
       resposta['Content-Disposition'] = f'attachment; filename="{nome_arquivo_codificado}"'
       
       return resposta
   else:
       return JsonResponse({"erro": "Arquivo não encontrado"}, status=404)

Tags: Django Python upload download ajax

Publicado em 7-8 17:58