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).