Configuración de archivos media
Los archivos media son archivos subidos por los usuarios: imágenes de perfil, documentos adjuntos, vídeos… A diferencia de los archivos estáticos (que son parte del código), los archivos media se almacenan fuera del repositorio y se gestionan en tiempo de ejecución.
# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

MEDIA_URL: prefijo de URL para acceder a los archivos subidos.MEDIA_ROOT: ruta absoluta en el sistema de archivos donde se almacenan.
Servir archivos media en desarrollo
Durante el desarrollo, Django puede servir los archivos media añadiendo una URL especial en urls.py del proyecto:
# urls.py del proyecto
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ... tus URLs ...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
En producción, Nginx u otro servidor web se encarga de servir los archivos media directamente, sin pasar por Django.
Modelos con FileField e ImageField
# models.py
from django.db import models
def ruta_avatar(instance, filename):
"""Genera la ruta de almacenamiento dinámica."""
ext = filename.split('.')[-1]
return f'avatares/{instance.username}.{ext}'
class Perfil(models.Model):
usuario = models.OneToOneField('auth.User', on_delete=models.CASCADE)
avatar = models.ImageField(
upload_to='avatares/', # Subdirectorio dentro de MEDIA_ROOT
blank=True,
null=True,
help_text='Imagen de perfil (máx. 2 MB)'
)
curriculum = models.FileField(
upload_to='curriculos/%Y/%m/', # Organiza por año/mes
blank=True,
null=True
)
def __str__(self):
return f'Perfil de {self.usuario.username}'
class Producto(models.Model):
nombre = models.CharField(max_length=100)
imagen_principal = models.ImageField(upload_to='productos/', blank=True)
imagen_miniatura = models.ImageField(upload_to='productos/miniaturas/', blank=True)
def get_imagen_url(self):
"""Devuelve la URL de la imagen o una imagen por defecto."""
if self.imagen_principal:
return self.imagen_principal.url
return '/static/img/producto_default.jpg'
Para usar ImageField, es necesario instalar Pillow:
pip install Pillow
Formularios con subida de archivos
Los formularios que incluyen FileField o ImageField requieren enctype="multipart/form-data" en la etiqueta <form>:
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Guardar</button>
</form>
En la vista, los archivos se reciben en request.FILES:
# views.py
from django.shortcuts import render, redirect
from .forms import PerfilForm
def editar_perfil(request):
perfil = request.user.perfil
if request.method == 'POST':
form = PerfilForm(request.POST, request.FILES, instance=perfil)
if form.is_valid():
form.save()
return redirect('mi-perfil')
else:
form = PerfilForm(instance=perfil)
return render(request, 'usuarios/editar_perfil.html', {'form': form})
Validar archivos en formularios
# forms.py
from django import forms
from django.core.exceptions import ValidationError
from .models import Perfil
class PerfilForm(forms.ModelForm):
class Meta:
model = Perfil
fields = ['avatar', 'curriculum']
def clean_avatar(self):
avatar = self.cleaned_data.get('avatar')
if avatar:
# Validar tamaño máximo (2 MB)
if avatar.size > 2 * 1024 * 1024:
raise ValidationError('La imagen no puede superar los 2 MB.')
# Validar extensión
extension = avatar.name.split('.')[-1].lower()
if extension not in ['jpg', 'jpeg', 'png', 'webp']:
raise ValidationError('Formato no permitido. Usa JPG, PNG o WebP.')
return avatar
Acceder a archivos en plantillas
{% if perfil.avatar %}
<img src="{{ perfil.avatar.url }}" alt="Avatar de {{ perfil.usuario.username }}">
<p>Tamaño: {{ perfil.avatar.size|filesizeformat }}</p>
{% else %}
<img src="{% static 'img/avatar_default.png' %}" alt="Avatar por defecto">
{% endif %}
{% if perfil.curriculum %}
<a href="{{ perfil.curriculum.url }}" download>Descargar CV</a>
{% endif %}
El atributo .url del campo genera la URL completa usando MEDIA_URL + la ruta almacenada en la base de datos.
Eliminar archivos al borrar el modelo
Django no elimina automáticamente los archivos del disco cuando se borra un registro. Para hacerlo, usa señales:
from django.db.models.signals import post_delete
from django.dispatch import receiver
import os
@receiver(post_delete, sender=Perfil)
def eliminar_avatar(sender, instance, **kwargs):
if instance.avatar:
if os.path.isfile(instance.avatar.path):
os.remove(instance.avatar.path)
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Configurar MEDIA_URL y MEDIA_ROOT para gestionar archivos subidos por usuarios. Definir modelos con FileField e ImageField para almacenar rutas de archivos. Configurar las URLs de media en desarrollo para servir los archivos. Procesar formularios con archivos usando request.FILES y enctype multipart. Gestionar el almacenamiento de archivos con parámetros upload_to y custom storage.