本文へ移動
cccskills
無料GitHub で公開

django-storages

Use when configuring django-storages for cloud file storage - STORAGES setting, Amazon S3, Google Cloud Storage and Azure backends, signed URLs, CDN and caching, multipart uploads, or storage testing

インストール方法を見る

含まれるファイル(4)

  • SKILL.md14.9 KB
  • references/cloud-features.md3.0 KB
  • references/models-code-urls.md3.3 KB
  • references/providers.md4.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Django Storages

Django package for cloud storage backends. Provides a unified API for storing files across different cloud providers.

Quick Start

pip install django-storages[boto3]  # AWS S3 example
# settings.py
import os

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "my-app-media",
            "region_name": "us-east-1",
        },
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
    },
}

# Credentials from environment (never in settings)
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]

Critical Pitfall: Filename Encoding

The single most common failure with user-uploaded files is non-ASCII characters, spaces, and special characters in filenames.

What Breaks

  • Unicode filenames (报告.pdf, vacation été.jpg) may not survive URL encoding
  • Spaces become %20 or + inconsistently across backends
  • Duplicate names overwrite existing files silently
  • Some backends reject certain characters entirely

Solution: Sanitize in upload_to

import os
import unicodedata
import uuid
from django.utils.text import slugify

def sanitize_upload_path(instance, filename):
    """
    Sanitize filename and guarantee uniqueness.
    Never trust client-supplied filenames.
    """
    # Extract extension
    ext = os.path.splitext(filename)[1]
    
    # Normalize unicode (NFKD decomposes, then slugify strips diacritics)
    sanitized = unicodedata.normalize("NFKD", filename)
    sanitized = slugify(sanitized)
    
    # Fallback if slugify produces empty string
    if not sanitized:
        sanitized = "file"
    
    # Append random suffix to guarantee uniqueness
    unique_id = uuid.uuid4().hex[:8]
    new_filename = f"{sanitized}_{unique_id}{ext}"
    
    return f"uploads/{new_filename}"

class Document(models.Model):
    title = models.CharField(max_length=255)
    file = models.FileField(upload_to=sanitize_upload_path)

Why Not Just Use slugify?

  • Two users upload report.pdf → second overwrites first
  • Filename contains only special characters → slugify produces empty string
  • You need to reference the original name for download → store it separately
class Document(models.Model):
    title = models.CharField(max_length=255)
    original_name = models.CharField(max_length=255)  # For download
    file = models.FileField(upload_to=sanitize_upload_path)

Configuration (Django 6.0+)

STORAGES Setting

The STORAGES dict replaces DEFAULT_FILE_STORAGE and STATICFILES_STORAGE (removed in Django 6.0).

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "my-app-media",
            "region_name": "us-east-1",
        },
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
    },
}

Why Backend Selection Goes in Settings, Not Code

Problem: Hardcoding the backend in code forces environment-specific branching.

# ❌ DON'T: Environment branching in code
if settings.DEBUG:
    from django.core.files.storage import FileSystemStorage
    storage = FileSystemStorage()
else:
    from storages.backends.s3 import S3Storage
    storage = S3Storage()

Solution: Swap backends via settings per environment.

# settings/dev.py
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
}

# settings/prod.py
STORAGES = {
    "default": {"BACKEND": "storages.backends.s3.S3Storage"},
}

# Code stays the same:
from django.core.files.storage import default_storage
default_storage.save("file.txt", content)

Credentials from Environment Variables

Problem: A leaked settings file = leaked cloud credentials.

# ❌ DON'T: Hardcoded credentials
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "access_key": "AKIAIOSFODNN7EXAMPLE",  # Leaked = compromised
            "secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        },
    },
}

Solution: Environment variables or IAM roles.

# ✅ DO: Environment variables
import os
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "access_key": os.environ["AWS_ACCESS_KEY_ID"],
            "secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
        },
    },
}

# ✅ BETTER: IAM role (EC2/ECS) - no credentials needed
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "my-app-media",
            # No access_key/secret_key → uses IAM role
        },
    },
}

ACL and Authorization Decisions

default_acl: When to Make Files Public

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "default_acl": "public-read",  # or None for private
        },
    },
}

Set public-read only for genuinely public assets:

  • Static website assets (images, CSS, JS)
  • Publicly accessible user avatars
  • Marketing materials

Keep None (private) for:

  • User documents (PDFs, spreadsheets)
  • Financial records
  • Any data requiring authorization

Why it matters: Public buckets are a common security incident vector. Obscurity is not security.

querystring_auth: Signed URLs

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "querystring_auth": True,   # Require signatures (private)
            "querystring_expire": 3600, # 1 hour expiry
        },
    },
}

When querystring_auth = False:

  • Bucket has a public ACL policy
  • CDN frontends the bucket (CloudFront with OAI)
  • Static assets that never expire

Critical: If querystring_auth = False, signed URLs must carry their own expiry via url() parameters:

# Private bucket with querystring_auth=False (e.g., CDN-protected)
# You MUST pass expiry explicitly:
url = storage.url("file.pdf", expire=3600)  # Expires in 1 hour

When you need signed URLs:

# Download as attachment (forces download with original filename)
url = storage.url(
    "documents/report.pdf",
    parameters={"ResponseContentDisposition": "attachment; filename=report.pdf"}
)

# Temporary access (e.g., password-protected page)
url = storage.url("private/secret.pdf", expire=1800)  # 30 minutes

CDN Integration Pattern

Private Bucket + CDN Origin

Architecture:

  1. S3 bucket is private (querystring_auth = True)
  2. CloudFront (or other CDN) fronts the bucket
  3. Public users access via CDN URL
  4. Signed URLs bypass CDN for direct S3 access (or use CloudFront signed URLs)
# settings.py
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "my-app-private",
            "region_name": "us-east-1",
            "querystring_auth": True,  # Private bucket
            "custom_domain": "d1234abcdef.cloudfront.net",  # CDN domain
        },
    },
}

Cache-Control Headers

Differentiate cacheable public assets from private content:

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "object_parameters": {
                # Public static assets: cache aggressively
                "CacheControl": "public, max-age=31536000, immutable",
            },
        },
    },
}

# Per-file cache control (overrides default)
from django.core.files.storage import default_storage

# Public image - cache for a year
default_storage.save("images/logo.png", content)
# S3 object params can be set via custom storage subclass or boto3 directly

# Temporary report - don't cache
default_storage.save("reports/daily.pdf", content)

CDN Invalidation

CloudFront:

import boto3

def invalidate_cloudfront(paths):
    """
    Invalidate CloudFront cache for given paths.
    Cost: $0.005 per path, 1000 paths/month free.
    """
    cloudfront = boto3.client("cloudfront")
    distribution_id = "E1234ABCDEF"
    
    cloudfront.create_invalidation(
        DistributionId=distribution_id,
        InvalidationBatch={
            "Id": f"invalidation-{uuid.uuid4().hex[:8]}",
            "Paths": {
                "Quantity": len(paths),
                "Items": paths,
            },
        },
    )

# Invalidate specific files
invalidate_cloudfront(["/images/logo.png", "/reports/daily.pdf"])

# Invalidate by prefix (expensive - use sparingly)
invalidate_cloudfront(["/reports/*"])  # Wildcards supported in CloudFront

Azure CDN:

# Azure CDN Classic invalidation
from azure.cdn.management import CdnManagementClient

client = CdnManagementClient(credential, subscription_id)
client.profiles.begin_create_or_update(...)

# Invalidate by path prefix
client.endpoints.invalidate(
    resource_group_name="my-rg",
    profile_name="my-profile",
    endpoint_name="my-endpoint",
    content_paths=["/images/*", "/css/*"],
)

Source: AWS CloudFront Invalidations | Azure CDN Invalidation


When Not to Use django-storages

Local Development

Use Django's FileSystemStorage instead:

# settings/dev.py
STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
        "OPTIONS": {
            "location": "/tmp/media",  # or MEDIA_ROOT
        },
    },
}

Why: No network calls, no credentials, instant file access for debugging.

Static Assets Pipeline

Use a build pipeline instead of storage backend for assets that ship with code:

  • Webpack/Vite/Rollup output
  • Compiled CSS/JS
  • Optimized images from source control

Why: These are versioned at deploy time, not user-uploaded. Use collectstatic with ManifestStaticFilesStorage.

STORAGES = {
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
    },
}

Single Bucket, No Django Integration Needed

Use the SDK directly:

import boto3

s3 = boto3.client("s3")
s3.upload_file("local.txt", "my-bucket", "remote.txt")

Why: If you only use one bucket and never swap backends, django-storages adds indirection without benefit.

Presigned URL Uploads from Browser

Bypass Django entirely:

# View that returns upload credentials
def get_upload_url(request):
    s3 = boto3.client("s3")
    url = s3.generate_presigned_url(
        "put_object",
        Params={
            "Bucket": "my-bucket",
            "Key": f"uploads/{uuid.uuid4()}.pdf",
            "ContentType": "application/pdf",
        },
        ExpiresIn=3600,
    )
    return JsonResponse({"url": url, "key": key})

# Browser uploads directly to S3
# POST { url, key } → client PUTs file to S3

Why: Large file uploads shouldn't proxy through Django. Use presigned URLs for direct browser-to-storage uploads.


Configuration Settings (When You Need Them)

S3-Specific Options

Most settings have sensible defaults. Use these only when you need specific behavior:

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            # Only set if you need non-default storage class
            "object_parameters": {
                "StorageClass": "STANDARD_IA",  # Infrequent access (cheaper)
            },
            
            # Only set if you want to prevent overwrites
            "file_overwrite": False,  # Default: True (overwrites same name)
            
            # Only set if using KMS encryption
            "object_parameters": {
                "SSEKMSKeyId": "arn:aws:kms:...",
            },
            
            # Only set if bucket is in different region
            "region_name": "eu-west-1",  # Default: us-east-1
            
            # Only set if using custom domain/CDN
            "custom_domain": "cdn.example.com",
            
            # Only set if you need non-default ACL
            "default_acl": "public-read",  # Default: None (private)
            
            # Only set if you need URL prefix in bucket
            "location": "media/",  # All files stored under media/
        },
    },
}

Source: django-storages S3 Backend Docs


Development and Testing

Environment Switching

# settings.py
from pathlib import Path

env = os.environ.get("DJANGO_ENV", "development")

if env == "development":
    STORAGES = {
        "default": {
            "BACKEND": "django.core.files.storage.FileSystemStorage",
        },
    }
else:
    STORAGES = {
        "default": {
            "BACKEND": "storages.backends.s3.S3Storage",
            "OPTIONS": {
                "bucket_name": os.environ["AWS_STORAGE_BUCKET_NAME"],
            },
        },
    }

Testing with Mock

from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase

class DocumentUploadTest(TestCase):
    @patch("django.core.files.storage.default_storage")
    def test_upload(self, mock_storage):
        mock_storage.save.return_value = "uploads/file.txt"
        
        file = SimpleUploadedFile("file.txt", b"content")
        path = default_storage.save("file.txt", file)
        
        self.assertEqual(path, "uploads/file.txt")
        mock_storage.save.assert_called_once()

Deep Dives

Load these reference files for detailed backend-specific guidance:

  • Provider Configuration — references/providers.md — Amazon S3, Google Cloud Storage, Azure Blob Storage detailed setup
  • Models and Code — references/models-code-urls.md — FileField usage, direct backend access, uploads, signed URLs
  • Advanced Cloud Features — references/cloud-features.md — Encryption, CDN, caching, multipart uploads (load when implementing provider-specific features)

References

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

aiohttp

無料

Use when building Python async HTTP services or clients with aiohttp - web server routing, middleware, WebSocket, SSE, streaming, client sessions, pytest-aiohttp testing, or troubleshooting SSL and timeout issues

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

ast-grep

無料

Use when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

Use when building GBA games with the BPCore Lua engine - entity, sprite and tilemap functions, SRAM save and load, link cable multiplayer protocol, camera and scrolling, or optimization patterns

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

celery

無料

Use when running background tasks with Celery - worker and broker configuration (Redis, RabbitMQ), task routing by name vs queue, chains/groups/chords, retry patterns (autoretry_for, retry_backoff), acks_late semantics, failure detection, and monitoring with Flower

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

django

無料

Use when building Django applications - security hardening, authentication and permissions, ORM optimization, PostgreSQL features, Django 6.0, migrations, testing, and ecosystem libraries

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

Use when customizing Django Admin - save_formset, get_search_results, formsets, queryset optimization, db_index, custom URLs

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

CodeAtCode のスキルをすべて見る

このスキルの問題を報告する