Upload o carga de archivos en Django
Índice de contenido
- ⚙️ Introducción: subir archivos en Django no es complicado (si sabes esto antes)
- Configuración inicial: prepara tu entorno Django
- Error típico: Pillow is not installed
- Creación del modelo: dónde guardar el archivo del upload
- Cargar un archivo, implementación mínima
- Vista y lógica para procesar la carga
- Template HTML para mostrar el formulario
- Verificar que la carga funciona
- Errores comunes al subir archivos en Django
- Consejos y buenas prácticas
- ❓ Preguntas frecuentes (FAQs)
- Conclusión
Subir archivos en Django puede parecer complicado la primera vez, pero en realidad no lo es… si sabes qué pasos seguir y qué errores evitar.
Vamos a aprender a cargar un archivo empleando Django, específicamente el avatar pero, los pasos señalados e implementados aquí los puedes emplear y adaptar para la carga de cualquier tipo de archivo como PDFs.
El proceso de carga de archivos no es un proceso difícil, pero sí consta de varios pasos que debemos de tener en cuenta; veremos paso por paso cada uno de estos pasos para que la implementación sea lo más entendible posible para el lector.
⚙️ Introducción: subir archivos en Django no es complicado (si sabes esto antes)
Antes de comenzar, vale aclarar algo: Django ya está preparado para manejar archivos, solo hay que configurarlo bien.
Este tutorial te servirá tanto para subir una imagen de perfil como para cargar documentos en PDF u otros formatos.
Cuando probé esto por primera vez, me topé con errores tontos —como el clásico “Pillow is not installed”— pero te mostraré cómo resolverlos rápido.
Configuración inicial: prepara tu entorno Django
Instalar dependencias y definir rutas
Primero, instala Pillow, la librería que Django usa para manejar imágenes:
$ pip install PillowEn tu archivo settings.py, define las rutas donde se guardarán los archivos:
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'Crea la carpeta /media en el directorio raíz del proyecto y asegúrate de tener permisos de escritura.
Ovidé crearla la primera vez y Django no generó el directorio automáticamente.
Error típico: Pillow is not installed
Si ves este error:
Cannot use ImageField because Pillow is not installed.simplemente instala la dependencia y vuelve a ejecutar:
$ python manage.py makemigrations
$ python manage.py migrateCreación del modelo: dónde guardar el archivo del upload
Para adaptar el modelo de usuarios para que tenga el campo del avatar, pudiéramos modificar el modelo actual, pero, para evitar modificar un modelo de sistema, lo más recomendado sería crear una relación aparte de tipo uno a uno que contenga los campos adicionales que en este ejemplo, es el del avatar.
Para este ejemplo, usaremos un modelo que extienda al usuario mediante una relación OneToOneField.
Así evitamos modificar el modelo base del sistema, algo que siempre recomiendo:
account\models.py
# Create your models here.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='user/avatar')✨ Opté por crear un modelo separado para modificar la entidad de User de Django; es más limpio y seguro a largo plazo.
Creamos y ejecutamos las migraciones:
$ python manage.py makemigrations
$ python manage.py migrateAhora creamos un ModelForm de Django que se encargue de manejar tanto los datos normales (request.POST) como los archivos (request.FILES):
account\forms.py
from django import forms
from .models import UserProfile
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('avatar', 'user')
widgets = {'user': forms.HiddenInput()}El manejo del campo de usuario aplicaremos varios campos en los siguientes apartados ya que por defecto es de tipo selección obligatoria comportamiento que no queremos.
Cargar un archivo, implementación mínima
Para evitar un error del formulario en el cual indique el el usuario es requerido, establecemos en el modelo que dicho campo puede ser nulo:
account\models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True)También podríamos modificar el formulario como mostramos anteriormente; el siguiente punto a tratar, es colocar el campo de tipo oculto:
account\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
***
widgets = {'user': forms.HiddenInput() }Vista y lógica para procesar la carga
Para manejar el formulario, la implementación queda similares a los anteriores casos:
account\views.py
@login_required
def profile(request):
form = UserProfileForm()
if request.method == "POST":
form = UserProfileForm(request.POST, request.FILES)
if form.is_valid():
userprofile = form.save(commit=False)
userprofile.user = request.user
userprofile.save()
return render(request, 'profile.html', {'form':form})Como consideraciones importantes tenemos:
UserProfileForm(request.POST, request.FILES)Para establecer los archivos enviados por el usuario, que en este ejemplo es solamente uno, también se conserva la petición con datos planos:
request.POSTYa que en el formulario anterior pueden haber otros campos de tipo textos o similares.
En cuanto al:
commit=FalseSe emplea para solamente generar la instancia del modelo y NO guardar en la base de datos, ya que, antes de guardar a la base de dato se debe de establecer el usuario autenticado:
userprofile = form.save(commit=False)
userprofile.user = request.user
userprofile.save()Definimos el template con el formulario y las clases de Bootstrap y mostrar los errores:
Template HTML para mostrar el formulario
account\templates\profile.html
{% extends "base_user_account.html" %}
{% load widget_tweaks %}
{% block content %}
{% if form.errors %}
<div class="alert alert-warning">
{{ form.errors }}
</div>
{% endif %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mt-3">
{{ form.avatar|add_label_class:"form-label" }}
{{ form.avatar|add_class:"form-control" }}
</div>
{{ form.user }}
<button type="submit" class="mt-3 btn btn-primary">Send</button>
</form>
{% endblock %}Recuerda el atributo enctype="multipart/form-data"; sin él, los archivos no se envían correctamente.
Con el proceso anterior, se pueden cargar archivos en el sistema, veremos que se genera una carpeta con el archivo cargado en:
user\avatar
Que es la ruta definida en el campo de tipo imagen:
models.ImageField(upload_to='user/avatar')Y el registro correspondiente en la base de datos; el problema que tenemos actualmente es que, al ser una relación de tipo uno a uno, solamente se puede procesar los datos una sola vez cuando no existe el registro del perfil del usuario en la base de datos, pero, esto lo solventamos en el siguiente apartado.
Otra forma de modificar los atributos del formulario, es directamente desde la clase:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('avatar','user')
def __init__(self,*args,**kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['user'].widget = forms.HiddenInput()
self.fields['user'].required = False
self.fields['avatar'].widget.attrs['class'] = "custom-file-input"
self.fields['avatar'].widget.attrs['id'] = "customFile"
***Verificar que la carga funciona
Tras enviar el formulario:
- El archivo se guarda dentro de /media/user/avatar/.
- En la base de datos se crea un registro UserProfile.
- Puedes comprobarlo desde el Django admin o explorando la carpeta media.
Abre directamente la carpeta media/user/avatar y confirmé que la imagen se había copiado correctamente.
Errores comunes al subir archivos en Django
Error Causa Solución
request.FILES vacío Falta enctype="multipart/form-data" Añadir al formulario
“MEDIA_ROOT not defined” Configuración faltante Definir en settings.py
“Pillow is not installed” Falta librería pip install Pillow
Archivo no visible Ruta mal definida Verificar MEDIA_URL y MEDIA_ROOT
Consejos y buenas prácticas
Mantén rutas limpias (upload_to='user/avatar' es claro y ordenado).
Controla el tamaño máximo de los archivos.
Usa validaciones personalizadas en el formulario.
Si subes imágenes, optimízalas con Pillow antes de guardarlas.
Protege las rutas de media en entornos productivos (Nginx/Apache).
❓ Preguntas frecuentes (FAQs)
¿Dónde se guardan los archivos subidos en Django?
En la carpeta definida por MEDIA_ROOT dentro del proyecto.
¿Cómo permitir múltiples archivos?
Usa un FileField con multiple=True y procesa request.FILES.getlist().
¿Qué pasa si el usuario ya tiene un archivo anterior?
Puedes sobreescribirlo o eliminar el viejo antes de guardar el nuevo.
Conclusión
Subir archivos en Django es mucho más fácil cuando entiendes la lógica detrás de request.FILES, el formulario y la ruta de almacenamiento.
En mi experiencia, los errores más comunes vienen de pequeñas omisiones (como olvidar el enctype o no instalar Pillow).
Siguiendo esta guía, tendrás una implementación funcional y profesional lista para producción.
El siguiente paso es emplear la vista de detalle.
Acepto recibir anuncios de interes sobre este Blog.
Veremos como realizar la carga de archivos, especificamente del avatar empleando Django 5 y componentes de Bootstrap 5.