Templates and Presentation Layer

Working with Media Files

Media files are user-uploaded content that changes dynamically based on user interactions. Unlike static files, media files are not part of your application code and require special handling for security, storage, and processing.

Working with Media Files

Media files are user-uploaded content that changes dynamically based on user interactions. Unlike static files, media files are not part of your application code and require special handling for security, storage, and processing.

Media Files Overview

What Are Media Files?

Media files include any content uploaded by users or generated dynamically:

  • User Uploads - Profile photos, document attachments, images
  • Generated Content - Thumbnails, processed images, reports
  • Dynamic Assets - User-specific content, temporary files
  • File Attachments - PDFs, spreadsheets, presentations

Media vs Static Files

Media Files:

  • User-generated or uploaded
  • Vary per user/request
  • Stored outside version control
  • Require security considerations
  • Examples: profile photos, uploads

Static Files:

  • Part of application code
  • Same for all users
  • Versioned with codebase
  • Served efficiently by web server
  • Examples: CSS, JS, app icons

Media Files Configuration

Basic Settings

# settings.py
import os
from pathlib import Path

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

# Media files configuration
MEDIA_URL = '/media/'  # URL prefix for media files
MEDIA_ROOT = BASE_DIR / 'media'  # Directory where uploaded files are stored

# File upload settings
FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880  # 5MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 10485760  # 10MB
FILE_UPLOAD_PERMISSIONS = 0o644
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755

# Allowed file types (security)
ALLOWED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
ALLOWED_DOCUMENT_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt']
MAX_UPLOAD_SIZE = 10 * 1024 * 1024  # 10MB

URL Configuration

# urls.py (main project)
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 media files during development
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Directory Structure

myproject/
├── media/                     # Media root directory
│   ├── uploads/
│   │   ├── profiles/          # User profile images
│   │   │   ├── user_1/
│   │   │   │   ├── avatar.jpg
│   │   │   │   └── cover.jpg
│   │   │   └── user_2/
│   │   │       └── avatar.png
│   │   ├── documents/         # Document uploads
│   │   │   ├── 2024/
│   │   │   │   ├── 01/
│   │   │   │   └── 02/
│   │   │   └── contracts/
│   │   └── blog/              # Blog-related uploads
│   │       ├── featured/
│   │       └── gallery/
│   ├── thumbnails/            # Generated thumbnails
│   │   ├── small/
│   │   ├── medium/
│   │   └── large/
│   └── temp/                  # Temporary files
└── static/                    # Static files (separate)

File Upload Models

Basic File Fields

# models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator
import os

def user_profile_path(instance, filename):
    """Generate upload path for user profile images"""
    return f'uploads/profiles/user_{instance.user.id}/{filename}'

def document_upload_path(instance, filename):
    """Generate upload path for documents with date organization"""
    return f'uploads/documents/{instance.created_at.year}/{instance.created_at.month:02d}/{filename}'

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    
    # Image field with custom upload path
    avatar = models.ImageField(
        upload_to=user_profile_path,
        blank=True,
        null=True,
        help_text="Profile picture (max 5MB)"
    )
    
    # File field with validation
    resume = models.FileField(
        upload_to='uploads/resumes/',
        blank=True,
        null=True,
        validators=[FileExtensionValidator(allowed_extensions=['pdf', 'doc', 'docx'])],
        help_text="Resume in PDF or Word format"
    )
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class Document(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    
    # File field with custom path and validation
    file = models.FileField(
        upload_to=document_upload_path,
        validators=[
            FileExtensionValidator(
                allowed_extensions=['pdf', 'doc', 'docx', 'txt', 'jpg', 'png']
            )
        ]
    )
    
    # Additional metadata
    file_size = models.PositiveIntegerField(blank=True, null=True)
    content_type = models.CharField(max_length=100, blank=True)
    
    uploaded_by = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def save(self, *args, **kwargs):
        if self.file:
            self.file_size = self.file.size
            self.content_type = self.file.file.content_type
        super().save(*args, **kwargs)
    
    def get_file_extension(self):
        return os.path.splitext(self.file.name)[1].lower()
    
    def is_image(self):
        return self.get_file_extension() in ['.jpg', '.jpeg', '.png', '.gif', '.webp']
    
    def __str__(self):
        return self.title

Advanced File Handling

# models.py (continued)
from PIL import Image
from django.core.files.base import ContentFile
import io

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    
    # Featured image with automatic thumbnail generation
    featured_image = models.ImageField(
        upload_to='uploads/blog/featured/',
        blank=True,
        null=True
    )
    
    # Thumbnail (auto-generated)
    thumbnail = models.ImageField(
        upload_to='uploads/blog/thumbnails/',
        blank=True,
        null=True,
        editable=False
    )
    
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        
        # Generate thumbnail if featured image exists
        if self.featured_image and not self.thumbnail:
            self.create_thumbnail()
    
    def create_thumbnail(self, size=(300, 200)):
        """Create thumbnail from featured image"""
        if not self.featured_image:
            return
        
        # Open the image
        image = Image.open(self.featured_image.path)
        
        # Convert to RGB if necessary
        if image.mode != 'RGB':
            image = image.convert('RGB')
        
        # Create thumbnail
        image.thumbnail(size, Image.Resampling.LANCZOS)
        
        # Save thumbnail
        thumb_io = io.BytesIO()
        image.save(thumb_io, format='JPEG', quality=85)
        thumb_io.seek(0)
        
        # Generate filename
        thumb_name = f"thumb_{os.path.basename(self.featured_image.name)}"
        thumb_name = os.path.splitext(thumb_name)[0] + '.jpg'
        
        # Save to model
        self.thumbnail.save(
            thumb_name,
            ContentFile(thumb_io.read()),
            save=False
        )
        
        # Save the model (without triggering save again)
        super().save(update_fields=['thumbnail'])

class Gallery(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

class GalleryImage(models.Model):
    gallery = models.ForeignKey(Gallery, on_delete=models.CASCADE, related_name='images')
    title = models.CharField(max_length=200, blank=True)
    
    # Original image
    image = models.ImageField(upload_to='uploads/gallery/original/')
    
    # Multiple sizes (auto-generated)
    image_small = models.ImageField(upload_to='uploads/gallery/small/', blank=True, editable=False)
    image_medium = models.ImageField(upload_to='uploads/gallery/medium/', blank=True, editable=False)
    image_large = models.ImageField(upload_to='uploads/gallery/large/', blank=True, editable=False)
    
    alt_text = models.CharField(max_length=200, blank=True)
    order = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['order', 'created_at']
    
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        
        if self.image:
            self.create_image_sizes()
    
    def create_image_sizes(self):
        """Create multiple image sizes"""
        sizes = {
            'small': (150, 150),
            'medium': (400, 300),
            'large': (800, 600)
        }
        
        for size_name, dimensions in sizes.items():
            field_name = f'image_{size_name}'
            field = getattr(self, field_name)
            
            if not field:
                resized_image = self.resize_image(dimensions)
                if resized_image:
                    filename = f"{size_name}_{os.path.basename(self.image.name)}"
                    field.save(filename, resized_image, save=False)
        
        # Save all size fields
        self.save(update_fields=['image_small', 'image_medium', 'image_large'])
    
    def resize_image(self, size):
        """Resize image to specified dimensions"""
        try:
            image = Image.open(self.image.path)
            
            if image.mode != 'RGB':
                image = image.convert('RGB')
            
            # Resize maintaining aspect ratio
            image.thumbnail(size, Image.Resampling.LANCZOS)
            
            # Save to BytesIO
            output = io.BytesIO()
            image.save(output, format='JPEG', quality=85)
            output.seek(0)
            
            return ContentFile(output.read())
        except Exception as e:
            print(f"Error resizing image: {e}")
            return None

File Upload Forms

Basic Upload Forms

# forms.py
from django import forms
from django.core.exceptions import ValidationError
from .models import UserProfile, Document, BlogPost
import os

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ['avatar', 'resume']
        widgets = {
            'avatar': forms.FileInput(attrs={
                'class': 'form-control',
                'accept': 'image/*'
            }),
            'resume': forms.FileInput(attrs={
                'class': 'form-control',
                'accept': '.pdf,.doc,.docx'
            })
        }
    
    def clean_avatar(self):
        avatar = self.cleaned_data.get('avatar')
        
        if avatar:
            # Check file size (5MB limit)
            if avatar.size > 5 * 1024 * 1024:
                raise ValidationError("Image file too large (max 5MB)")
            
            # Check file extension
            ext = os.path.splitext(avatar.name)[1].lower()
            if ext not in ['.jpg', '.jpeg', '.png', '.gif']:
                raise ValidationError("Invalid image format. Use JPG, PNG, or GIF.")
            
            # Check image dimensions
            try:
                from PIL import Image
                image = Image.open(avatar)
                width, height = image.size
                
                if width > 2000 or height > 2000:
                    raise ValidationError("Image dimensions too large (max 2000x2000)")
                
                if width < 100 or height < 100:
                    raise ValidationError("Image dimensions too small (min 100x100)")
                    
            except Exception:
                raise ValidationError("Invalid image file")
        
        return avatar
    
    def clean_resume(self):
        resume = self.cleaned_data.get('resume')
        
        if resume:
            # Check file size (10MB limit)
            if resume.size > 10 * 1024 * 1024:
                raise ValidationError("File too large (max 10MB)")
            
            # Check file extension
            ext = os.path.splitext(resume.name)[1].lower()
            if ext not in ['.pdf', '.doc', '.docx']:
                raise ValidationError("Invalid file format. Use PDF or Word documents.")
        
        return resume

class DocumentUploadForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ['title', 'description', 'file']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
            'file': forms.FileInput(attrs={'class': 'form-control'})
        }
    
    def clean_file(self):
        file = self.cleaned_data.get('file')
        
        if file:
            # File size validation
            max_size = 20 * 1024 * 1024  # 20MB
            if file.size > max_size:
                raise ValidationError(f"File too large (max {max_size // (1024*1024)}MB)")
            
            # File type validation
            allowed_types = [
                'application/pdf',
                'application/msword',
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                'text/plain',
                'image/jpeg',
                'image/png'
            ]
            
            if file.content_type not in allowed_types:
                raise ValidationError("File type not allowed")
        
        return file

class MultipleFileUploadForm(forms.Form):
    files = forms.FileField(
        widget=forms.ClearableFileInput(attrs={
            'multiple': True,
            'class': 'form-control'
        }),
        help_text="Select multiple files to upload"
    )
    
    def clean_files(self):
        files = self.files.getlist('files')
        
        if not files:
            raise ValidationError("No files selected")
        
        # Validate each file
        for file in files:
            if file.size > 5 * 1024 * 1024:  # 5MB per file
                raise ValidationError(f"File {file.name} is too large (max 5MB)")
        
        return files

AJAX File Upload

# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required
import json

@login_required
@require_http_methods(["POST"])
def ajax_file_upload(request):
    """Handle AJAX file uploads"""
    try:
        uploaded_file = request.FILES.get('file')
        
        if not uploaded_file:
            return JsonResponse({'error': 'No file provided'}, status=400)
        
        # Validate file
        if uploaded_file.size > 10 * 1024 * 1024:  # 10MB
            return JsonResponse({'error': 'File too large'}, status=400)
        
        # Create document
        document = Document.objects.create(
            title=uploaded_file.name,
            file=uploaded_file,
            uploaded_by=request.user
        )
        
        return JsonResponse({
            'success': True,
            'file_id': document.id,
            'file_name': document.file.name,
            'file_url': document.file.url,
            'file_size': document.file_size
        })
        
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

@login_required
def upload_progress(request):
    """Track upload progress (requires additional setup)"""
    # This requires django-uploadify or similar package
    # for real upload progress tracking
    pass

Displaying Media Files

Template Usage

<!-- templates/profile.html -->
{% load static %}

<div class="user-profile">
    <div class="profile-header">
        {% if user.userprofile.avatar %}
            <img src="{{ user.userprofile.avatar.url }}" 
                 alt="{{ user.username }}'s avatar" 
                 class="avatar-large">
        {% else %}
            <img src="{% static 'images/default-avatar.png' %}" 
                 alt="Default avatar" 
                 class="avatar-large">
        {% endif %}
        
        <h2>{{ user.get_full_name|default:user.username }}</h2>
    </div>
    
    {% if user.userprofile.resume %}
        <div class="resume-section">
            <h3>Resume</h3>
            <a href="{{ user.userprofile.resume.url }}" 
               class="btn btn-primary" 
               download>
                <i class="fas fa-download"></i> Download Resume
            </a>
        </div>
    {% endif %}
</div>

<!-- Document list -->
<div class="documents">
    <h3>Documents</h3>
    {% for document in documents %}
        <div class="document-item">
            <div class="document-info">
                <h4>{{ document.title }}</h4>
                <p>{{ document.description }}</p>
                <small>
                    Uploaded by {{ document.uploaded_by.username }} 
                    on {{ document.created_at|date:"M d, Y" }}
                    ({{ document.file_size|filesizeformat }})
                </small>
            </div>
            
            <div class="document-actions">
                {% if document.is_image %}
                    <a href="{{ document.file.url }}" 
                       class="btn btn-sm btn-outline-primary" 
                       target="_blank">
                        <i class="fas fa-eye"></i> View
                    </a>
                {% endif %}
                
                <a href="{{ document.file.url }}" 
                   class="btn btn-sm btn-primary" 
                   download>
                    <i class="fas fa-download"></i> Download
                </a>
            </div>
        </div>
    {% empty %}
        <p>No documents uploaded yet.</p>
    {% endfor %}
</div>

Responsive Images

<!-- templates/blog/post_detail.html -->
{% load static %}

<article class="blog-post">
    <header class="post-header">
        <h1>{{ post.title }}</h1>
        
        {% if post.featured_image %}
            <div class="featured-image">
                <picture>
                    <!-- Mobile -->
                    <source media="(max-width: 480px)" 
                            srcset="{{ post.thumbnail.url }}">
                    
                    <!-- Tablet -->
                    <source media="(max-width: 768px)" 
                            srcset="{{ post.featured_image.url }}">
                    
                    <!-- Desktop -->
                    <img src="{{ post.featured_image.url }}" 
                         alt="{{ post.title }}" 
                         class="img-fluid">
                </picture>
            </div>
        {% endif %}
    </header>
    
    <div class="post-content">
        {{ post.content|linebreaks }}
    </div>
</article>

<!-- Gallery display -->
<div class="gallery">
    {% for image in gallery.images.all %}
        <div class="gallery-item">
            <a href="{{ image.image.url }}" 
               data-lightbox="gallery" 
               data-title="{{ image.title }}">
                <img src="{{ image.image_medium.url|default:image.image.url }}" 
                     alt="{{ image.alt_text|default:image.title }}" 
                     loading="lazy">
            </a>
        </div>
    {% endfor %}
</div>

File Type Icons

<!-- templates/includes/file_icon.html -->
{% load static %}

{% if document.is_image %}
    <i class="fas fa-image text-success"></i>
{% else %}
    {% with ext=document.get_file_extension %}
        {% if ext == '.pdf' %}
            <i class="fas fa-file-pdf text-danger"></i>
        {% elif ext == '.doc' or ext == '.docx' %}
            <i class="fas fa-file-word text-primary"></i>
        {% elif ext == '.xls' or ext == '.xlsx' %}
            <i class="fas fa-file-excel text-success"></i>
        {% elif ext == '.txt' %}
            <i class="fas fa-file-alt text-secondary"></i>
        {% else %}
            <i class="fas fa-file text-muted"></i>
        {% endif %}
    {% endwith %}
{% endif %}

File Security

Access Control

# views.py
from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
import os
import mimetypes

@login_required
def secure_file_download(request, file_id):
    """Secure file download with access control"""
    document = get_object_or_404(Document, id=file_id)
    
    # Check permissions
    if not can_access_file(request.user, document):
        raise Http404("File not found")
    
    # Get file path
    file_path = document.file.path
    
    if not os.path.exists(file_path):
        raise Http404("File not found")
    
    # Determine content type
    content_type, _ = mimetypes.guess_type(file_path)
    if not content_type:
        content_type = 'application/octet-stream'
    
    # Create response
    with open(file_path, 'rb') as f:
        response = HttpResponse(f.read(), content_type=content_type)
        response['Content-Disposition'] = f'attachment; filename="{document.title}"'
        response['Content-Length'] = os.path.getsize(file_path)
        
    return response

def can_access_file(user, document):
    """Check if user can access the file"""
    # Owner can always access
    if document.uploaded_by == user:
        return True
    
    # Admin can access all files
    if user.is_staff:
        return True
    
    # Add custom logic here
    # e.g., shared files, team access, etc.
    
    return False

# URL pattern for secure downloads
# urls.py
urlpatterns = [
    path('files/<int:file_id>/download/', secure_file_download, name='secure_download'),
]

File Validation

# validators.py
from django.core.exceptions import ValidationError
from django.core.files.images import get_image_dimensions
import magic
import os

def validate_file_extension(value):
    """Validate file extension"""
    allowed_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png', '.gif']
    ext = os.path.splitext(value.name)[1].lower()
    
    if ext not in allowed_extensions:
        raise ValidationError(f'File extension {ext} not allowed')

def validate_file_size(value):
    """Validate file size"""
    max_size = 10 * 1024 * 1024  # 10MB
    
    if value.size > max_size:
        raise ValidationError(f'File size {value.size} exceeds maximum {max_size}')

def validate_image_dimensions(value):
    """Validate image dimensions"""
    try:
        width, height = get_image_dimensions(value)
        
        if width and height:
            if width > 4000 or height > 4000:
                raise ValidationError('Image dimensions too large (max 4000x4000)')
            
            if width < 50 or height < 50:
                raise ValidationError('Image dimensions too small (min 50x50)')
                
    except Exception:
        raise ValidationError('Invalid image file')

def validate_file_content(value):
    """Validate file content matches extension"""
    try:
        # Read file content to determine actual type
        file_content = value.read()
        value.seek(0)  # Reset file pointer
        
        # Use python-magic to detect file type
        file_type = magic.from_buffer(file_content, mime=True)
        
        # Map extensions to MIME types
        allowed_types = {
            '.pdf': 'application/pdf',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.png': 'image/png',
            '.gif': 'image/gif',
        }
        
        ext = os.path.splitext(value.name)[1].lower()
        expected_type = allowed_types.get(ext)
        
        if expected_type and file_type != expected_type:
            raise ValidationError(f'File content does not match extension {ext}')
            
    except Exception as e:
        raise ValidationError(f'File validation error: {str(e)}')

# Usage in models
class SecureDocument(models.Model):
    file = models.FileField(
        upload_to='secure_uploads/',
        validators=[
            validate_file_extension,
            validate_file_size,
            validate_file_content
        ]
    )

Image Processing

Thumbnail Generation

# utils.py
from PIL import Image, ImageOps
from django.core.files.base import ContentFile
import io
import os

def create_thumbnail(image_field, size=(300, 300), quality=85):
    """Create thumbnail from image field"""
    if not image_field:
        return None
    
    try:
        # Open image
        image = Image.open(image_field.path)
        
        # Convert to RGB if necessary
        if image.mode in ('RGBA', 'LA', 'P'):
            image = image.convert('RGB')
        
        # Create thumbnail maintaining aspect ratio
        image = ImageOps.fit(image, size, Image.Resampling.LANCZOS)
        
        # Save to BytesIO
        thumb_io = io.BytesIO()
        image.save(thumb_io, format='JPEG', quality=quality, optimize=True)
        thumb_io.seek(0)
        
        # Generate filename
        name, ext = os.path.splitext(os.path.basename(image_field.name))
        thumb_name = f"{name}_thumb.jpg"
        
        return ContentFile(thumb_io.read(), name=thumb_name)
        
    except Exception as e:
        print(f"Error creating thumbnail: {e}")
        return None

def resize_image(image_field, width=None, height=None, quality=85):
    """Resize image to specific dimensions"""
    if not image_field:
        return None
    
    try:
        image = Image.open(image_field.path)
        
        if image.mode in ('RGBA', 'LA', 'P'):
            image = image.convert('RGB')
        
        # Calculate new size
        original_width, original_height = image.size
        
        if width and height:
            new_size = (width, height)
        elif width:
            ratio = width / original_width
            new_size = (width, int(original_height * ratio))
        elif height:
            ratio = height / original_height
            new_size = (int(original_width * ratio), height)
        else:
            return None
        
        # Resize image
        image = image.resize(new_size, Image.Resampling.LANCZOS)
        
        # Save to BytesIO
        output = io.BytesIO()
        image.save(output, format='JPEG', quality=quality, optimize=True)
        output.seek(0)
        
        # Generate filename
        name, ext = os.path.splitext(os.path.basename(image_field.name))
        resized_name = f"{name}_{new_size[0]}x{new_size[1]}.jpg"
        
        return ContentFile(output.read(), name=resized_name)
        
    except Exception as e:
        print(f"Error resizing image: {e}")
        return None

def optimize_image(image_field, quality=85, max_width=1920, max_height=1080):
    """Optimize image for web"""
    if not image_field:
        return None
    
    try:
        image = Image.open(image_field.path)
        
        # Convert to RGB
        if image.mode in ('RGBA', 'LA', 'P'):
            image = image.convert('RGB')
        
        # Resize if too large
        width, height = image.size
        if width > max_width or height > max_height:
            image.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
        
        # Optimize and save
        output = io.BytesIO()
        image.save(output, format='JPEG', quality=quality, optimize=True)
        output.seek(0)
        
        return ContentFile(output.read(), name=image_field.name)
        
    except Exception as e:
        print(f"Error optimizing image: {e}")
        return None

Production Considerations

Cloud Storage Integration

# settings/production.py
from storages.backends.s3boto3 import S3Boto3Storage

# AWS S3 Configuration
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1')
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'

# Media files storage
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'

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

class PrivateMediaStorage(S3Boto3Storage):
    location = 'private'
    default_acl = 'private'
    file_overwrite = False
    custom_domain = False

# Usage in models
class Document(models.Model):
    public_file = models.FileField(storage=MediaStorage())
    private_file = models.FileField(storage=PrivateMediaStorage())

CDN Integration

# settings/production.py

# CloudFront CDN
AWS_S3_CUSTOM_DOMAIN = 'cdn.example.com'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'

# Cache control headers
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',  # 1 day
}

# Different cache times for different file types
class OptimizedMediaStorage(S3Boto3Storage):
    def get_object_parameters(self, name):
        params = super().get_object_parameters(name)
        
        # Longer cache for images
        if name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
            params['CacheControl'] = 'max-age=2592000'  # 30 days
        
        # Shorter cache for documents
        elif name.lower().endswith(('.pdf', '.doc', '.docx')):
            params['CacheControl'] = 'max-age=86400'  # 1 day
        
        return params

Media files require careful handling for security, performance, and user experience. Proper validation, processing, and storage strategies ensure your Django application can handle user uploads safely and efficiently at scale.