Guía de implementación de carga de archivos en Django | FileField, Validación, Compatibilidad con S3
Django tiene un ecosistema rico para cargas de archivos, incluyendo FileField e ImageField, validación basada en formularios e integración con S3 a través de django-storages. Este artículo explica sistemáticamente la implementación de carga de archivos a nivel de producción, desde definiciones de campos en models.py, validación en forms.py, configuración MEDIA_ROOT/MEDIA_URL en settings.py, hasta guardar en AWS S3 usando django-storages.
Definición de FileField e ImageField
FileField admite todo tipo de archivo, mientras que ImageField es solo para imágenes (requiere Pillow). Ambos almacenan rutas de archivo (cadenas) en la base de datos en lugar de archivos reales, con archivos reales almacenados bajo MEDIA_ROOT.
# 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)
Implementación de validación en forms.py
Usando formularios Django, puedes validar tipos MIME de archivo, tamaños, extensiones y más con el método clean_fieldname(). También se pueden adjuntar validadores personalizados a los campos del modelo.
# 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
Procesamiento de carga en views.py
En Django, los archivos de formulario multiparte se recuperan de request.FILES. La validación se ejecuta simplemente pasando request.FILES a la clase de formulario.
# 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)})
Configuración de MEDIA_ROOT y MEDIA_URL en settings.py
Para servir archivos multimedia en un entorno de desarrollo, configure MEDIA_ROOT (el directorio donde se guardan los archivos) y MEDIA_URL (el prefijo de URL), y agregue la entrega de archivos estáticos a urls.py.
# 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)
Configuración de S3 usando django-storages
Usando django-storages y boto3, puede cambiar el destino de almacenamiento de archivos de medios a S3 agregando solo unas pocas líneas a settings.py.
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
Validador personalizado para límites de tamaño de archivo
Al especificar validators en un campo de modelo, la validación se aplica tanto al formulario como al modelo.
# 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']),
],
)
❓ Preguntas frecuentes
¿Cuál es la diferencia entre FileField e ImageField en Django?
¿Dónde guarda Django los archivos subidos?
¿Cómo subo varios archivos a la vez en Django?
Archivos de prueba para este artículo (gratis)
- → Imagen PNG de Prueba (1MB) — Para pruebas de validación de ImageField y FileField
- → Archivo de prueba PDF (1MB) — Para validar tipos MIME y cargas a S3
- → Archivo de prueba ZIP (1MB) — para pruebas de límites de tamaño de archivo