Skip to content

Chapter 11: Static and Media Files

11.2 Media File Handling

MEDIA_URL Configuration

Media files are user-uploaded files such as images, documents, videos, etc. Django provides dedicated configuration to handle media files.

Basic configuration:

python
# settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# Media file URL prefix
MEDIA_URL = '/media/'

# Media file storage directory
MEDIA_ROOT = BASE_DIR / "media"

# Ensure development server can serve media files
if DEBUG:
    import mimetypes
    mimetypes.add_type("image/svg+xml", ".svg", True)
    mimetypes.add_type("image/svg+xml", ".svgz", True)

Serve media files in development environment:

python
# urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
]

# Serve media files in development environment
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

File Upload

Basic views and forms for handling file uploads:

python
# models.py
from django.db import models

class Document(models.Model):
    title = models.CharField(max_length=200)
    file = models.FileField(upload_to='documents/%Y/%m/%d/')
    uploaded_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.title

class Image(models.Model):
    title = models.CharField(max_length=200)
    image = models.ImageField(upload_to='images/%Y/%m/%d/')
    uploaded_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.title

# forms.py
from django import forms
from .models import Document, Image

class DocumentForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ['title', 'file']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'file': forms.FileInput(attrs={'class': 'form-control'}),
        }

class ImageForm(forms.ModelForm):
    class Meta:
        model = Image
        fields = ['title', 'image']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'image': forms.FileInput(attrs={'class': 'form-control'}),
        }

# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import DocumentForm, ImageForm

def upload_document(request):
    if request.method == 'POST':
        form = DocumentForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            messages.success(request, 'File uploaded successfully!')
            return redirect('document_list')
    else:
        form = DocumentForm()
    
    return render(request, 'upload_document.html', {'form': form})

def upload_image(request):
    if request.method == 'POST':
        form = ImageForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            messages.success(request, 'Image uploaded successfully!')
            return redirect('image_list')
    else:
        form = ImageForm()
    
    return render(request, 'upload_image.html', {'form': form})

File upload templates:

html
<!-- upload_document.html -->
<div class="container">
    <h2>Upload Document</h2>
    
    {% if messages %}
        {% for message in messages %}
            <div class="alert alert-{{ message.tags }}">{{ message }}</div>
        {% endfor %}
    {% endif %}
    
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        
        <div class="mb-3">
            {{ form.title.label_tag }}
            {{ form.title }}
            {% if form.title.errors %}
                <div class="text-danger">{{ form.title.errors }}</div>
            {% endif %}
        </div>
        
        <div class="mb-3">
            {{ form.file.label_tag }}
            {{ form.file }}
            {% if form.file.errors %}
                <div class="text-danger">{{ form.file.errors }}</div>
            {% endif %}
            <div class="form-text">Supported file types: PDF, DOC, DOCX, TXT</div>
        </div>
        
        <button type="submit" class="btn btn-primary">Upload</button>
        <a href="{% url 'document_list' %}" class="btn btn-secondary">Cancel</a>
    </form>
</div>

<!-- document_list.html -->
<div class="container">
    <h2>Document List</h2>
    
    <div class="row">
        {% for document in documents %}
            <div class="col-md-4 mb-3">
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title">{{ document.title }}</h5>
                        <p class="card-text">
                            Upload time: {{ document.uploaded_at|date:"Y-m-d H:i" }}
                        </p>
                        <a href="{{ document.file.url }}" class="btn btn-primary" target="_blank">
                            Download File
                        </a>
                    </div>
                </div>
            </div>
        {% empty %}
            <p>No documents.</p>
        {% endfor %}
    </div>
</div>

Image Processing

Process image uploads using the Pillow library:

# Install Pillow
# pip install Pillow

# models.py
from django.db import models
from PIL import Image
import os

class Photo(models.Model):
    title = models.CharField(max_length=200)
    image = models.ImageField(upload_to='photos/%Y/%m/%d/')
    thumbnail = models.ImageField(upload_to='photos/thumbnails/%Y/%m/%d/', blank=True)
    uploaded_at = models.DateTimeField(auto_now_add=True)
    
    def save(self, *args, **kwargs):
        # Save original image
        super().save(*args, **kwargs)
        
        # Generate thumbnail
        if self.image and not self.thumbnail:
            self.create_thumbnail()
    
    def create_thumbnail(self):
        # Open original image
        img = Image.open(self.image.path)
        
        # Resize image
        max_size = (300, 300)
        img.thumbnail(max_size, Image.Resampling.LANCZOS)
        
        # Save thumbnail
        thumb_name, thumb_extension = os.path.splitext(self.image.name)
        thumb_extension = thumb_extension.lower()
        
        thumb_path = thumb_name + '_thumb' + thumb_extension
        
        if thumb_extension in ['.jpg', '.jpeg']:
            img.save(self.thumbnail.path, 'JPEG', quality=85)
        elif thumb_extension == '.png':
            img.save(self.thumbnail.path, 'PNG', quality=85)
        else:
            img.save(self.thumbnail.path, 'JPEG', quality=85)
    
    def __str__(self):
        return self.title

# Custom image validation
from django.core.exceptions import ValidationError

def validate_image_size(value):
    """Validate image file size"""
    filesize = value.size
    
    if filesize > 5 * 1024 * 1024:  # 5MB
        raise ValidationError("Image size cannot exceed 5MB")
    
    return value

def validate_image_extension(value):
    """Validate image file extension"""
    valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
    ext = os.path.splitext(value.name)[1].lower()
    
    if ext not in valid_extensions:
        raise ValidationError(f'Unsupported file type. Supported types: {", ".join(valid_extensions)}')

# Use validators in model
class Photo(models.Model):
    title = models.CharField(max_length=200)
    image = models.ImageField(
        upload_to='photos/%Y/%m/%d/',
        validators=[validate_image_size, validate_image_extension]
    )

Display images in templates:

<!-- photo_list.html -->
<div class="container">
    <h2>Photo Gallery</h2>
    
    <div class="row">
        {% for photo in photos %}
            <div class="col-md-4 mb-4">
                <div class="card">
                    {% if photo.thumbnail %}
                        <img src="{{ photo.thumbnail.url }}" class="card-img-top" alt="{{ photo.title }}">
                    {% else %}
                        <img src="{{ photo.image.url }}" class="card-img-top" alt="{{ photo.title }}">
                    {% endif %}
                    <div class="card-body">
                        <h5 class="card-title">{{ photo.title }}</h5>
                        <p class="card-text">
                            Upload time: {{ photo.uploaded_at|date:"Y-m-d H:i" }}
                        </p>
                        <a href="{{ photo.image.url }}" class="btn btn-primary" target="_blank">
                            View Original
                        </a>
                    </div>
                </div>
            </div>
        {% empty %}
            <p>No photos.</p>
        {% endfor %}
    </div>
</div>

File Storage Backends

Django supports multiple file storage backends, including local storage and cloud storage:

# settings.py
# Default local file storage
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'

# Using cloud storage (AWS S3 example)
# Install django-storages and boto3
# pip install django-storages boto3

# settings.py
INSTALLED_APPS = [
    # ...
    'storages',
]

# AWS S3 configuration
AWS_ACCESS_KEY_ID = 'your-access-key-id'
AWS_SECRET_ACCESS_KEY = 'your-secret-access-key'
AWS_STORAGE_BUCKET_NAME = 'your-bucket-name'
AWS_S3_REGION_NAME = 'us-east-1'
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'

# Static and media file storage
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

# Or configure static and media files separately
# STATICFILES_STORAGE = 'myapp.storage.StaticStorage'
# DEFAULT_FILE_STORAGE = 'myapp.storage.MediaStorage'

# Custom storage backend
# storage.py
from storages.backends.s3boto3 import S3Boto3Storage

class StaticStorage(S3Boto3Storage):
    location = 'static'
    default_acl = 'public-read'

class MediaStorage(S3Boto3Storage):
    location = 'media'
    default_acl = 'public-read'
    file_overwrite = False

# Alibaba Cloud OSS storage configuration
# Install django-oss-storage
# pip install django-oss-storage

# settings.py
DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage'
STATICFILES_STORAGE = 'django_oss_storage.backends.OssStaticStorage'

OSS_ACCESS_KEY_ID = 'your-access-key-id'
OSS_ACCESS_KEY_SECRET = 'your-access-key-secret'
OSS_BUCKET_NAME = 'your-bucket-name'
OSS_ENDPOINT = 'http://oss-cn-hangzhou.aliyuncs.com'

File access permission control:

# models.py
from django.db import models
from django.contrib.auth.models import User

class PrivateDocument(models.Model):
    title = models.CharField(max_length=200)
    file = models.FileField(upload_to='private/%Y/%m/%d/')
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.title

# views.py
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, Http404
from django.core.exceptions import PermissionDenied
from .models import PrivateDocument

@login_required
def download_private_document(request, document_id):
    document = get_object_or_404(PrivateDocument, id=document_id)
    
    # Check permissions
    if document.owner != request.user and not request.user.is_superuser:
        raise PermissionDenied("You don't have permission to download this file")
    
    # Provide file download
    response = HttpResponse(document.file, content_type='application/octet-stream')
    response['Content-Disposition'] = f'attachment; filename="{document.file.name}"'
    
    return response

With these configurations and implementations, you can effectively handle media files in Django projects, including file uploads, image processing, and storage backend configuration.

Summary

Django media file handling provides a complete solution:

  1. ✅ MEDIA_URL and MEDIA_ROOT configuration manages media file access
  2. ✅ File upload forms and views handle user uploads
  3. ✅ Image processing features support thumbnail generation and validation
  4. ✅ Support for multiple storage backends, including cloud storage services
  5. ✅ File access permission control ensures security

Mastering media file handling skills can build feature-rich web applications.

Next Article

We will learn about Django middleware and signal systems.

12.1 Middleware System →

Directory

Return to Course Directory

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).