Skip to content

第11章:静态文件和媒体文件

11.2 媒体文件处理

MEDIA_URL配置

媒体文件是用户上传的文件,如图片、文档、视频等。Django提供了专门的配置来处理媒体文件。

基本配置:

python
# settings.py
import os
from pathlib import Path

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

# 媒体文件URL前缀
MEDIA_URL = '/media/'

# 媒体文件存储目录
MEDIA_ROOT = BASE_DIR / "media"

# 确保开发服务器能够提供媒体文件
if DEBUG:
    import mimetypes
    mimetypes.add_type("image/svg+xml", ".svg", True)
    mimetypes.add_type("image/svg+xml", ".svgz", True)

在开发环境中提供媒体文件:

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')),
]

# 在开发环境中提供媒体文件
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

文件上传

处理文件上传的基本视图和表单:

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, '文件上传成功!')
            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, '图片上传成功!')
            return redirect('image_list')
    else:
        form = ImageForm()
    
    return render(request, 'upload_image.html', {'form': form})

文件上传模板:

html
<!-- upload_document.html -->
<div class="container">
    <h2>上传文档</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">支持的文件类型:PDF, DOC, DOCX, TXT</div>
        </div>
        
        <button type="submit" class="btn btn-primary">上传</button>
        <a href="{% url 'document_list' %}" class="btn btn-secondary">取消</a>
    </form>
</div>

<!-- document_list.html -->
<div class="container">
    <h2>文档列表</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">
                            上传时间:{{ document.uploaded_at|date:"Y-m-d H:i" }}
                        </p>
                        <a href="{{ document.file.url }}" class="btn btn-primary" target="_blank">
                            下载文件
                        </a>
                    </div>
                </div>
            </div>
        {% empty %}
            <p>暂无文档。</p>
        {% endfor %}
    </div>
</div>

图片处理

使用Pillow库处理图片上传:

# 安装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):
        # 保存原始图片
        super().save(*args, **kwargs)
        
        # 生成缩略图
        if self.image and not self.thumbnail:
            self.create_thumbnail()
    
    def create_thumbnail(self):
        # 打开原始图片
        img = Image.open(self.image.path)
        
        # 调整图片大小
        max_size = (300, 300)
        img.thumbnail(max_size, Image.Resampling.LANCZOS)
        
        # 保存缩略图
        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

# 自定义图片验证
from django.core.exceptions import ValidationError

def validate_image_size(value):
    """验证图片文件大小"""
    filesize = value.size
    
    if filesize > 5 * 1024 * 1024:  # 5MB
        raise ValidationError("图片大小不能超过5MB")
    
    return value

def validate_image_extension(value):
    """验证图片文件扩展名"""
    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'不支持的文件类型。支持的类型:{", ".join(valid_extensions)}')

# 在模型中使用验证器
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]
    )

在模板中显示图片:

<!-- photo_list.html -->
<div class="container">
    <h2>图片库</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">
                            上传时间:{{ photo.uploaded_at|date:"Y-m-d H:i" }}
                        </p>
                        <a href="{{ photo.image.url }}" class="btn btn-primary" target="_blank">
                            查看原图
                        </a>
                    </div>
                </div>
            </div>
        {% empty %}
            <p>暂无图片。</p>
        {% endfor %}
    </div>
</div>

文件存储后端

Django支持多种文件存储后端,包括本地存储和云存储:

# settings.py
# 默认本地文件存储
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'

# 使用云存储(以AWS S3为例)
# 安装django-storages和boto3
# pip install django-storages boto3

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

# AWS S3配置
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'

# 静态文件和媒体文件存储
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

# 或者分别配置静态文件和媒体文件
# STATICFILES_STORAGE = 'myapp.storage.StaticStorage'
# DEFAULT_FILE_STORAGE = 'myapp.storage.MediaStorage'

# 自定义存储后端
# 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

# 阿里云OSS存储配置
# 安装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'

文件访问权限控制:

# 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)
    
    # 检查权限
    if document.owner != request.user and not request.user.is_superuser:
        raise PermissionDenied("您没有权限下载此文件")
    
    # 提供文件下载
    response = HttpResponse(document.file, content_type='application/octet-stream')
    response['Content-Disposition'] = f'attachment; filename="{document.file.name}"'
    
    return response

通过这些配置和实现,你可以有效地处理Django项目中的媒体文件,包括文件上传、图片处理和存储后端配置。

小结

Django媒体文件处理提供了完整的解决方案:

  1. ✅ MEDIA_URL和MEDIA_ROOT配置管理媒体文件访问
  2. ✅ 文件上传表单和视图处理用户上传
  3. ✅ 图片处理功能支持缩略图生成和验证
  4. ✅ 支持多种存储后端,包括云存储服务
  5. ✅ 文件访问权限控制确保安全性

掌握媒体文件处理技巧能够构建功能丰富的Web应用。

下一篇

我们将学习Django中间件和信号系统。

12.1 中间件系统 →

目录

返回课程目录

Released under the Apache 2.0 License.