Skip to content

Django File Upload Implementation Guide | FileField, Validation, S3 Support

Category: Django / Python

Django has a rich ecosystem for file uploads, including FileField and ImageField, form-based validation, and S3 integration via django-storages. This article systematically explains production-level file upload implementation, from field definitions in models.py, validation in forms.py, MEDIA_ROOT/MEDIA_URL configuration in settings.py, to saving to AWS S3 using django-storages.

Django file upload flow Client multipart/form-data urls.py route forms.py validate views.py save() MEDIA_ROOT / S3 storage request.FILES is_valid() model.save() FileSystemStorage / S3Boto3Storage Request to storage processing flow
Fig 1: Django file upload processing flow

Definition of FileField and ImageField

FileField supports all file types, while ImageField is for images only (requires Pillow). Both store file paths (strings) in the database rather than the actual files, with real files stored under 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)

Implementing Validation in forms.py

Using Django forms, you can validate file MIME types, sizes, extensions, and more with the clean_fieldname() method. Custom validators can also be attached to model fields.

# 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

Upload processing in views.py

In Django, multipart form files are retrieved from request.FILES. Validation is executed simply by passing request.FILES to the form class.

# 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)})

MEDIA_ROOT and MEDIA_URL settings in settings.py

To serve media files in a development environment, configure MEDIA_ROOT (the directory where files are saved) and MEDIA_URL (the URL prefix), and add static file serving to 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)

S3 Configuration Using django-storages

Using django-storages and boto3, you can switch the media file storage destination to S3 by adding just a few lines to 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

Custom Validator for File Size Limits

By specifying validators on a model field, validation is applied to both the form and model.

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

❓ Frequently Asked Questions

What is the difference between Django's FileField and ImageField?
FileField accepts any file; ImageField accepts only images (JPEG, PNG, GIF, WebP, BMP) and uses Pillow to validate them. ImageField extends FileField and adds width_field and height_field, which write the image dimensions into model fields automatically.
Where does Django store uploaded files?
Under the directory named by MEDIA_ROOT in settings.py. FileField's upload_to parameter takes a relative path or a callable that builds one. In production, rather than using MEDIA_ROOT directly, the usual advice is to store files in object storage such as S3 via django-storages.
How do I upload multiple files at once in Django?
Add the multiple attribute to the form input (input type='file' name='files' multiple) and read them in views.py with request.FILES.getlist('files'), which returns a list. Loop over it and save each file. The usual model design is one record per file.

Test files for this article (free)