Django Static File Management
This chapter details Django's static file management system, including static file configuration, media file handling, file uploads, CDN integration, and more, helping you effectively manage various static resources in your web application.
Static Files Overview
What are Static Files
Static files are files that are not dynamically generated, including:
- CSS Stylesheets - Page styling
- JavaScript Files - Client-side scripts
- Image Files - Icons, background images, etc.
- Font Files - Web fonts
- Other Resources - PDFs, audio, video, etc.
Static Files vs Media Files
python
# Static Files
- Created by developers
- Managed by version control
- Collected to unified directory during deployment
- Examples: CSS, JS, icons
# Media Files
- Uploaded by users
- Dynamically generated content
- Stored in media directory
- Examples: User avatars, article imagesStatic File Configuration
Basic Configuration
python
# settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Static file URL prefix
STATIC_URL = '/static/'
# Development environment static file directories
STATICFILES_DIRS = [
BASE_DIR / 'static', # Project-level static files
BASE_DIR / 'assets', # Additional static file directories
]
# Production environment static file collection directory
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Media file configuration
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Static file finders
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]Directory Structure
myproject/
├── static/ # Project-level static files
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ ├── style.css
│ │ └── admin-custom.css
│ ├── js/
│ │ ├── jquery.min.js
│ │ ├── bootstrap.min.js
│ │ └── main.js
│ ├── images/
│ │ ├── logo.png
│ │ ├── favicon.ico
│ │ └── backgrounds/
│ └── fonts/
│ ├── roboto.woff2
│ └── icons.ttf
├── media/ # Media file directory
│ ├── uploads/
│ │ ├── avatars/
│ │ ├── articles/
│ │ └── documents/
│ └── cache/
├── blog/
│ └── static/
│ └── blog/ # Application-level static files
│ ├── css/
│ │ └── blog.css
│ ├── js/
│ │ └── blog.js
│ └── images/
│ └── blog-icon.png
└── staticfiles/ # Collected static files (production environment)URL Configuration
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('blog.urls')),
]
# Serve static and media files in development environment
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)Using Static Files in Templates
Basic Usage
html
<!-- Load static file tag -->
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<!-- CSS files -->
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
<!-- Favicon -->
<link rel="icon" href="{% static 'images/favicon.ico' %}">
<!-- Fonts -->
<link rel="preload" href="{% static 'fonts/roboto.woff2' %}" as="font" type="font/woff2" crossorigin>
</head>
<body>
<!-- Images -->
<img src="{% static 'images/logo.png' %}" alt="Website Logo" class="logo">
<!-- Background images -->
<div class="hero" style="background-image: url('{% static 'images/backgrounds/hero.jpg' %}');">
<h1>Welcome to My Website</h1>
</div>
<!-- JavaScript files -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'blog/js/blog.js' %}"></script>
</body>
</html>Dynamic Static File Paths
html
<!-- Using variables to construct paths -->
{% load static %}
{% with 'css/'|add:theme|add:'.css' as theme_css %}
<link rel="stylesheet" href="{% static theme_css %}">
{% endwith %}
<!-- Loop loading multiple files -->
{% for css_file in css_files %}
<link rel="stylesheet" href="{% static css_file %}">
{% endfor %}
<!-- Conditional loading -->
{% if user.is_authenticated %}
<script src="{% static 'js/user-dashboard.js' %}"></script>
{% endif %}
{% if debug %}
<script src="{% static 'js/debug.js' %}"></script>
{% endif %}Static File Version Control
python
# settings.py
# Enable static file version control
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# Or use cached version
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'html
<!-- Automatically add version number -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<!-- Output: /static/css/style.a1b2c3d4.css -->Media File Handling
File Upload Models
python
# models.py
from django.db import models
from django.contrib.auth.models import User
import os
def user_avatar_path(instance, filename):
"""User avatar upload path"""
ext = filename.split('.')[-1]
filename = f"{instance.user.id}_avatar.{ext}"
return os.path.join('avatars', filename)
def article_image_path(instance, filename):
"""Article image upload path"""
ext = filename.split('.')[-1]
filename = f"{instance.id}_{filename}"
return os.path.join('articles', str(instance.created_at.year), filename)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(
upload_to=user_avatar_path,
default='avatars/default.png',
help_text='User avatar'
)
bio = models.TextField(max_length=500, blank=True)
def __str__(self):
return f"{self.user.username}'s profile"
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
featured_image = models.ImageField(
upload_to=article_image_path,
blank=True,
null=True,
help_text='Article featured image'
)
attachments = models.FileField(
upload_to='articles/attachments/',
blank=True,
null=True,
help_text='Article attachments'
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
def get_image_url(self):
"""Get image URL"""
if self.featured_image:
return self.featured_image.url
return '/static/images/default-article.png'
class Document(models.Model):
title = models.CharField(max_length=200)
file = models.FileField(upload_to='documents/%Y/%m/')
uploaded_at = models.DateTimeField(auto_now_add=True)
file_size = models.PositiveIntegerField(blank=True, null=True)
def save(self, *args, **kwargs):
if self.file:
self.file_size = self.file.size
super().save(*args, **kwargs)
def get_file_size_display(self):
"""Format file size"""
if self.file_size:
if self.file_size < 1024:
return f"{self.file_size} B"
elif self.file_size < 1024 * 1024:
return f"{self.file_size / 1024:.1f} KB"
else:
return f"{self.file_size / (1024 * 1024):.1f} MB"
return "Unknown size"File Upload Forms
python
# forms.py
from django import forms
from .models import UserProfile, Article, Document
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['avatar', 'bio']
widgets = {
'avatar': forms.FileInput(attrs={
'class': 'form-control',
'accept': 'image/*'
}),
'bio': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4
})
}
def clean_avatar(self):
avatar = self.cleaned_data.get('avatar')
if avatar:
# Check file size (2MB limit)
if avatar.size > 2 * 1024 * 1024:
raise forms.ValidationError('Avatar file cannot exceed 2MB')
# Check file type
if not avatar.content_type.startswith('image/'):
raise forms.ValidationError('Please upload an image file')
return avatar
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'featured_image', 'attachments']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
'featured_image': forms.FileInput(attrs={
'class': 'form-control',
'accept': 'image/*'
}),
'attachments': forms.FileInput(attrs={'class': 'form-control'})
}
class DocumentUploadForm(forms.ModelForm):
class Meta:
model = Document
fields = ['title', 'file']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'file': forms.FileInput(attrs={'class': 'form-control'})
}
def clean_file(self):
file = self.cleaned_data.get('file')
if file:
# File size limit (10MB)
if file.size > 10 * 1024 * 1024:
raise forms.ValidationError('File cannot exceed 10MB')
# Allowed file types
allowed_types = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain'
]
if file.content_type not in allowed_types:
raise forms.ValidationError('Unsupported file type')
return fileFile Upload Views
python
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse, HttpResponse, Http404
from django.core.files.storage import default_storage
from django.conf import settings
import os
import mimetypes
@login_required
def upload_avatar(request):
"""Upload user avatar"""
profile, created = UserProfile.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
# Delete old avatar
if profile.avatar and profile.avatar.name != 'avatars/default.png':
if default_storage.exists(profile.avatar.name):
default_storage.delete(profile.avatar.name)
form.save()
messages.success(request, 'Avatar uploaded successfully!')
return redirect('profile')
else:
form = UserProfileForm(instance=profile)
return render(request, 'accounts/upload_avatar.html', {'form': form})
@login_required
def create_article(request):
"""Create article"""
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
messages.success(request, 'Article created successfully!')
return redirect('blog:article_detail', id=article.id)
else:
form = ArticleForm()
return render(request, 'blog/create_article.html', {'form': form})
def ajax_upload_image(request):
"""AJAX image upload"""
if request.method == 'POST' and request.FILES.get('image'):
image = request.FILES['image']
# Validate file
if not image.content_type.startswith('image/'):
return JsonResponse({'error': 'Please upload an image file'}, status=400)
if image.size > 5 * 1024 * 1024: # 5MB limit
return JsonResponse({'error': 'Image cannot exceed 5MB'}, status=400)
# Save file
filename = default_storage.save(f'uploads/images/{image.name}', image)
file_url = default_storage.url(filename)
return JsonResponse({
'success': True,
'url': file_url,
'filename': filename
})
return JsonResponse({'error': 'Invalid request'}, status=400)
def download_file(request, file_id):
"""File download"""
document = get_object_or_404(Document, id=file_id)
if not document.file:
raise Http404("File does not exist")
# Check if file exists
if not default_storage.exists(document.file.name):
raise Http404("File does not exist")
# Get file content
file_content = default_storage.open(document.file.name).read()
# Determine MIME type
content_type, _ = mimetypes.guess_type(document.file.name)
if not content_type:
content_type = 'application/octet-stream'
# Create response
response = HttpResponse(file_content, content_type=content_type)
response['Content-Disposition'] = f'attachment; filename="{document.title}"'
response['Content-Length'] = len(file_content)
return responseDisplaying Media Files in Templates
html
<!-- Display user avatar -->
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" alt="User avatar" class="avatar">
{% else %}
<img src="{% static 'images/default-avatar.png' %}" alt="Default avatar" class="avatar">
{% endif %}
<!-- Display article images -->
{% if article.featured_image %}
<img src="{{ article.featured_image.url }}" alt="{{ article.title }}" class="img-fluid">
{% endif %}
<!-- File upload form -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.avatar.id_for_label }}" class="form-label">Avatar</label>
{{ form.avatar }}
{% if form.avatar.errors %}
<div class="text-danger">{{ form.avatar.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.bio.id_for_label }}" class="form-label">Bio</label>
{{ form.bio }}
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<!-- Image preview -->
<script>
document.getElementById('id_avatar').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('avatar-preview').src = e.target.result;
};
reader.readAsDataURL(file);
}
});
</script>Advanced Static File Handling
Custom Storage Backend
python
# storage.py
from django.core.files.storage import FileSystemStorage
from django.conf import settings
import os
class CustomFileStorage(FileSystemStorage):
"""Custom file storage"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.base_url = settings.MEDIA_URL
def get_available_name(self, name, max_length=None):
"""Generate available filename"""
# Add timestamp if file already exists
if self.exists(name):
import time
name, ext = os.path.splitext(name)
name = f"{name}_{int(time.time())}{ext}"
return super().get_available_name(name, max_length)
def save(self, name, content, max_length=None):
"""Additional processing when saving files"""
# Can add file processing logic here
# Example: image compression, virus scanning, etc.
return super().save(name, content, max_length)
class SecureFileStorage(FileSystemStorage):
"""Secure file storage"""
def url(self, name):
"""Provide file access through view"""
return f"/secure-media/{name}"
def get_accessed_time(self, name):
"""Get file access time"""
return super().get_accessed_time(name)Image Processing
python
# utils.py
from PIL import Image
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
import io
def resize_image(image_file, max_width=800, max_height=600, quality=85):
"""Resize image"""
# Open image
image = Image.open(image_file)
# Convert to RGB (if RGBA)
if image.mode in ('RGBA', 'LA', 'P'):
image = image.convert('RGB')
# Calculate new dimensions
width, height = image.size
if width > max_width or height > max_height:
# Maintain aspect ratio
ratio = min(max_width / width, max_height / height)
new_width = int(width * ratio)
new_height = int(height * ratio)
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Save to memory
output = io.BytesIO()
image.save(output, format='JPEG', quality=quality, optimize=True)
output.seek(0)
return ContentFile(output.read())
def create_thumbnail(image_file, size=(150, 150)):
"""Create thumbnail"""
image = Image.open(image_file)
# Convert to RGB
if image.mode in ('RGBA', 'LA', 'P'):
image = image.convert('RGB')
# Create thumbnail
image.thumbnail(size, Image.Resampling.LANCZOS)
# Save to memory
output = io.BytesIO()
image.save(output, format='JPEG', quality=90)
output.seek(0)
return ContentFile(output.read())
# Using in models
class Article(models.Model):
title = models.CharField(max_length=200)
featured_image = models.ImageField(upload_to='articles/')
thumbnail = models.ImageField(upload_to='articles/thumbnails/', blank=True)
def save(self, *args, **kwargs):
# Process images
if self.featured_image:
# Resize original image
resized_image = resize_image(self.featured_image)
self.featured_image.save(
self.featured_image.name,
resized_image,
save=False
)
# Create thumbnail
thumbnail = create_thumbnail(self.featured_image)
thumbnail_name = f"thumb_{self.featured_image.name}"
self.thumbnail.save(thumbnail_name, thumbnail, save=False)
super().save(*args, **kwargs)CDN Integration
python
# settings.py
# Using AWS S3 as static file storage
if not DEBUG:
# AWS S3 configuration
AWS_ACCESS_KEY_ID = 'your-access-key'
AWS_SECRET_ACCESS_KEY = 'your-secret-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 file storage
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/static/'
# Media file storage
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'
# Using CloudFlare CDN
STATIC_URL = 'https://cdn.yourdomain.com/static/'
MEDIA_URL = 'https://cdn.yourdomain.com/media/'Static File Compression
python
# settings.py
# Install: pip install django-compressor
INSTALLED_APPS = [
# ...
'compressor',
]
# Compressor configuration
COMPRESS_ENABLED = not DEBUG
COMPRESS_CSS_FILTERS = [
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.rCSSMinFilter',
]
COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.JSMinFilter',
]
# Compressed file storage
COMPRESS_STORAGE = 'compressor.storage.CompressorFileStorage'
COMPRESS_URL = STATIC_URL
COMPRESS_ROOT = STATIC_ROOT
# Offline compression
COMPRESS_OFFLINE = Truehtml
<!-- Using compression in templates -->
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
{% endcompress %}
{% compress js %}
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/bootstrap.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
{% endcompress %}Performance Optimization
Static File Caching
python
# settings.py
# Static file cache headers
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# Nginx configuration example
"""
location /static/ {
alias /path/to/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
gzip_static on;
}
location /media/ {
alias /path/to/media/;
expires 30d;
add_header Cache-Control "public";
}
"""Image Optimization
python
# utils.py
from PIL import Image, ImageOptim
import os
def optimize_image(image_path, quality=85):
"""Optimize image"""
with Image.open(image_path) as img:
# Convert to RGB
if img.mode in ('RGBA', 'LA', 'P'):
img = img.convert('RGB')
# Optimize and save
img.save(image_path, 'JPEG', quality=quality, optimize=True)
# Further optimize using external tools
if os.system(f'jpegoptim --max={quality} {image_path}') == 0:
print(f"Image optimized: {image_path}")
def generate_webp(image_path):
"""Generate WebP format image"""
webp_path = os.path.splitext(image_path)[0] + '.webp'
with Image.open(image_path) as img:
img.save(webp_path, 'WebP', quality=85, optimize=True)
return webp_pathResponsive Images
html
<!-- Responsive images -->
<picture>
<source media="(min-width: 800px)" srcset="{{ article.featured_image.url }}">
<source media="(min-width: 400px)" srcset="{{ article.thumbnail.url }}">
<img src="{{ article.thumbnail.url }}" alt="{{ article.title }}" class="img-fluid">
</picture>
<!-- Using srcset -->
<img src="{{ article.thumbnail.url }}"
srcset="{{ article.thumbnail.url }} 300w, {{ article.featured_image.url }} 800w"
sizes="(max-width: 600px) 300px, 800px"
alt="{{ article.title }}"
class="img-fluid">Security Considerations
File Upload Security
python
# settings.py
# File upload limits
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # 2.5MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 # 2.5MB
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
# Allowed file types
ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
ALLOWED_DOCUMENT_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain'
]
# utils.py
import magic
from django.core.exceptions import ValidationError
def validate_file_type(file):
"""Validate file type"""
# Use python-magic to check real file type
file_type = magic.from_buffer(file.read(1024), mime=True)
file.seek(0) # Reset file pointer
allowed_types = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf', 'text/plain'
]
if file_type not in allowed_types:
raise ValidationError(f'Disallowed file type: {file_type}')
def scan_file_for_malware(file_path):
"""Scan file for malware (example)"""
# Can integrate ClamAV or other antivirus software here
# Return True if file is safe
return TrueSecure File Access
python
# views.py
from django.http import HttpResponse, Http404
from django.contrib.auth.decorators import login_required
from django.core.files.storage import default_storage
import os
@login_required
def secure_file_view(request, file_path):
"""Secure file access"""
# Check user permissions
if not request.user.has_perm('app.view_file'):
raise Http404("No access permission")
# Prevent path traversal attacks
file_path = os.path.normpath(file_path)
if '..' in file_path or file_path.startswith('/'):
raise Http404("Invalid file path")
# Check if file exists
full_path = os.path.join('secure', file_path)
if not default_storage.exists(full_path):
raise Http404("File does not exist")
# Return file content
try:
with default_storage.open(full_path, 'rb') as f:
response = HttpResponse(f.read())
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = f'attachment; filename="{os.path.basename(file_path)}"'
return response
except Exception:
raise Http404("File read failed")Chapter Summary
This chapter detailed Django's static file management system:
Key Points:
- Static File Configuration: STATIC_URL, STATICFILES_DIRS, STATIC_ROOT, etc.
- Media File Handling: Storage and management of user-uploaded files
- Usage in Templates: {% static %} tag and media file URLs
- File Uploads: Form handling, validation, and security considerations
- Performance Optimization: Compression, caching, CDN integration
Important Concepts:
- Static Files vs Media Files: Developer files vs user-uploaded files
- File Storage Backends: Local storage, cloud storage, etc.
- File Security: Type validation, permission control, virus scanning
- Performance Optimization: Compression, caching, responsive images
Best Practices:
- Reasonably organize static file directory structure
- Use version control to manage static file changes
- Implement strict file upload validation
- Configure appropriate caching strategies
- Consider using CDN to improve access speed
- Optimize image size and format
In the next chapter, we will learn about Django's admin interface system to understand how to quickly build powerful administration interfaces.