第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媒体文件处理提供了完整的解决方案:
- ✅ MEDIA_URL和MEDIA_ROOT配置管理媒体文件访问
- ✅ 文件上传表单和视图处理用户上传
- ✅ 图片处理功能支持缩略图生成和验证
- ✅ 支持多种存储后端,包括云存储服务
- ✅ 文件访问权限控制确保安全性
掌握媒体文件处理技巧能够构建功能丰富的Web应用。
下一篇
我们将学习Django中间件和信号系统。