Django 文件上传实现指南 | FileField · 验证 · S3 支持
Django 拥有丰富的文件上传生态系统,包括 <code>FileField</code> 和 <code>ImageField</code>、基于表单的验证以及通过 <code>django-storages</code> 的 S3 集成。本文系统地解释了生产级文件上传实现,从 <code>models.py</code> 中的字段定义、<code>forms.py</code> 中的验证、<code>settings.py</code> 中的 <code>MEDIA_ROOT</code>/<code>MEDIA_URL</code> 配置,到使用 <code>django-storages</code> 保存到 AWS S3。
FileField 和 ImageField 的定义
<code>FileField</code> 支持所有文件类型,而 <code>ImageField</code> 仅用于图像(需要 Pillow)。两者都在数据库中存储文件路径(字符串)而不是实际文件,实际文件存储在 <code>MEDIA_ROOT</code> 目录下。
# models.py
from django.db import models
def upload_to_documents(instance, filename):
"""ユーザーIDごとにサブディレクトリを作成するアップロードパス関数"""
return f'documents/{instance.user.id}/{filename}'
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
# 画像フィールド(Pillowが必要: pip install Pillow)
avatar = models.ImageField(
upload_to='avatars/', # MEDIA_ROOT/avatars/ 以下に保存
blank=True,
null=True,
verbose_name='プロフィール画像',
)
def __str__(self):
return str(self.user)
class Document(models.Model):
title = models.CharField(max_length=255)
file = models.FileField(
upload_to=upload_to_documents, # 関数でパスを動的に決定
verbose_name='添付ファイル',
)
uploaded_at = models.DateTimeField(auto_now_add=True)
file_size = models.PositiveIntegerField(default=0)
mime_type = models.CharField(max_length=100, blank=True)
class Meta:
ordering = ['-uploaded_at']
def save(self, *args, **kwargs):
# 保存時にファイルサイズを記録
if self.file:
self.file_size = self.file.size
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
# レコード削除時に実ファイルも削除
storage = self.file.storage
path = self.file.name
super().delete(*args, **kwargs)
storage.delete(path)
在 forms.py 中实现验证
使用 Django 表单,你可以用 <code>clean_字段名()</code> 方法验证文件 MIME 类型、大小、扩展名等。也可以将自定义验证器附加到模型字段。
# forms.py
from django import forms
from django.core.exceptions import ValidationError
from .models import Document
# 許可するMIMEタイプの定義
ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
ALLOWED_DOC_TYPES = ['application/pdf', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
MAX_UPLOAD_SIZE = 10 * 1024 * 1024 # 10MB
class AvatarUploadForm(forms.Form):
avatar = forms.ImageField(
label='プロフィール画像',
help_text='JPEG・PNG・GIF・WebP、最大5MB',
)
def clean_avatar(self):
image = self.cleaned_data.get('avatar')
if not image:
return image
# ファイルサイズの検証
max_size = 5 * 1024 * 1024 # 5MB
if image.size > max_size:
raise ValidationError(
f'ファイルサイズは5MB以下にしてください(現在: {image.size // 1024 // 1024}MB)'
)
# MIMEタイプの検証(content_type はクライアント申告値なので補助的に使用)
if image.content_type not in ALLOWED_IMAGE_TYPES:
raise ValidationError(
f'許可されていないファイル形式です({image.content_type})。'
'JPEG・PNG・GIF・WebP のみ使用できます。'
)
return image
class DocumentUploadForm(forms.ModelForm):
class Meta:
model = Document
fields = ['title', 'file']
def clean_file(self):
file = self.cleaned_data.get('file')
if not file:
return file
# ファイルサイズ検証
if file.size > MAX_UPLOAD_SIZE:
raise ValidationError(
f'ファイルサイズは{MAX_UPLOAD_SIZE // 1024 // 1024}MB以下にしてください。'
)
# MIMEタイプ検証
if file.content_type not in ALLOWED_DOC_TYPES:
raise ValidationError('PDF または Word ファイルのみアップロードできます。')
# 拡張子の検証
ext = file.name.rsplit('.', 1)[-1].lower()
if ext not in ['pdf', 'doc', 'docx']:
raise ValidationError('ファイルの拡張子が不正です。')
return file
views.py 中的上传处理
在 Django 中,多部分表单文件从 <code>request.FILES</code> 检索。只需将 <code>request.FILES</code> 传递给表单类即可执行验证。
# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from django.views import View
from .forms import DocumentUploadForm
class DocumentUploadView(View):
template_name = 'documents/upload.html'
def get(self, request):
form = DocumentUploadForm()
return render(request, self.template_name, {'form': form})
def post(self, request):
# request.POST と request.FILES の両方を渡す
form = DocumentUploadForm(request.POST, request.FILES)
if form.is_valid():
document = form.save(commit=False)
document.user = request.user # ログインユーザーを設定
document.save()
messages.success(request, 'ファイルをアップロードしました。')
return redirect('documents:list')
return render(request, self.template_name, {'form': form})
# REST API(Django REST Framework)の場合
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import status
class DocumentUploadAPIView(APIView):
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
file = request.FILES.get('file')
if not file:
return Response({'error': 'ファイルが見つかりません。'}, status=status.HTTP_400_BAD_REQUEST)
# バリデーション
if file.size > 10 * 1024 * 1024:
return Response({'error': 'ファイルが大きすぎます(上限10MB)。'}, status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
# 保存処理(forms.py のロジックを再利用することを推奨)
from django.core.files.storage import default_storage
path = default_storage.save(f'uploads/{file.name}', file)
return Response({'path': path, 'url': default_storage.url(path)})
settings.py 中的 MEDIA_ROOT 和 MEDIA_URL 设置
要在开发环境中提供媒体文件,请配置 <code>MEDIA_ROOT</code>(文件保存目录)和 <code>MEDIA_URL</code>(URL前缀),并在 <code>urls.py</code> 中添加静态文件服务。
# settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# メディアファイルの保存先(開発環境)
MEDIA_ROOT = BASE_DIR / 'media'
# メディアファイルの URL プレフィックス
MEDIA_URL = '/media/'
# ファイルアップロードのデフォルト設定
# 2.5MB 以下はメモリ上で処理、それ以上は一時ファイルに書き込む
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # 2.5MB
# Django が受け付けるリクエストボディの上限
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
# アップロードハンドラーのカスタマイズ(デフォルト)
FILE_UPLOAD_HANDLERS = [
'django.core.files.uploadhandler.MemoryFileUploadHandler',
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]
# urls.py(開発環境でのメディアファイル配信)
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
urlpatterns = [
# ... アプリの URL
]
# 開発環境のみメディアファイルを Django で配信
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
使用django-storages配置S3
使用 <code>django-storages</code> 和 <code>boto3</code>,只需在 <code>settings.py</code> 中添加几行代码就可以将媒体文件存储位置切换到 S3。
pip install django-storages[s3] boto3
# settings.py(S3 設定)
import os
INSTALLED_APPS = [
# ...
'storages',
]
# S3 バックエンドを使用
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# 静的ファイルも S3 に配置する場合
# STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
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', 'ap-northeast-1')
# S3 の URL 設定
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'
# CloudFront を使う場合
# AWS_S3_CUSTOM_DOMAIN = 'xxxxxxxx.cloudfront.net'
# ファイルの公開設定(デフォルトは private)
AWS_DEFAULT_ACL = 'private'
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400', # 1日キャッシュ
}
# アップロードファイルのプレフィックス
AWS_LOCATION = 'media'
# 署名付きURL の有効期間(秒)
AWS_QUERYSTRING_EXPIRE = 3600 # 1時間
# HTTPS を強制
AWS_S3_USE_SSL = True
文件大小限制的自定义验证器
通过在模型字段上指定 <code>validators</code>,验证将应用于表单和模型两者。
# validators.py
from django.core.exceptions import ValidationError
def validate_file_size(value, max_mb=10):
"""ファイルサイズを検証するバリデーター"""
max_bytes = max_mb * 1024 * 1024
if value.size > max_bytes:
raise ValidationError(
f'ファイルサイズは {max_mb}MB 以下にしてください。'
f'(現在: {value.size / 1024 / 1024:.1f}MB)'
)
def validate_file_extension(value, allowed_extensions=None):
"""ファイル拡張子を検証するバリデーター"""
if allowed_extensions is None:
allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf']
ext = value.name.rsplit('.', 1)[-1].lower()
if ext not in allowed_extensions:
raise ValidationError(
f'許可されていない拡張子です(.{ext})。'
f'使用可能: {", ".join(allowed_extensions)}'
)
# models.py での使用例
from django.db import models
from .validators import validate_file_size, validate_file_extension
class Attachment(models.Model):
file = models.FileField(
upload_to='attachments/',
validators=[
validate_file_size,
lambda v: validate_file_extension(v, ['pdf', 'docx', 'xlsx']),
],
)
本文中可用的测试文件(免费)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 测试图像(1MB)</a> — 用于 ImageField·FileField 验证测试
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF 测试文件(1MB)</a> — 用于验证 MIME 类型和 S3 上传
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP 测试文件(1MB)</a> — 用于文件大小限制的边界值测试
相关文章
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">AWS S3・CloudFront 文件上传限制总结</a>
- → <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Web 表单文件验证实现检查清单</a>
- → <a href="/ja/blog/laravel-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Laravel 文件上传实现指南|验证、Storage 和 S3 支持</a>