Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when configuring Django to store static and media files on AWS S3 with django-storages. Invoke when working with the STORAGES setting, S3 buckets, presigned URLs, CloudFront, or boto3-backed file storage in settings.py. Configures the Django 4.2+ STORAGES dict, public/private custom backends, presigned GET/POST URLs, IAM policies, and S3 mocking for tests. Trigger terms: django-storages, S3, boto3, S3Boto3Storage, STORAGES, presigned URL, CloudFront, media files, collectstatic, AWS_STORAGE_B
.claude/skills/jeffallan-django-storages-s3/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 84% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 68% | 0% |
Senior Django specialist for production-grade file storage on AWS S3 via django-storages and boto3 — public and private media, static files, presigned URLs, and CloudFront.
STORAGES dict or legacy DEFAULT_FILE_STORAGEFileField/ImageField storage to S3 without code changespip install django-storages[s3] boto3; add "storages" to INSTALLED_APPSSTORAGES dict — Set default (media) and staticfiles backends with separate location prefixesSTORAGES entries when neededcollectstatic, confirm uploads land in S3, and mock S3 in tests with InMemoryStorage or motoLoad detailed guidance based on context:
| Topic | Reference | Load When | |-------|-----------|-----------| | Settings & STORAGES | references/configuration.md | Core settings, 4.2+ vs legacy, CloudFront | | Custom backends | references/custom-backends.md | Public vs. private buckets, per-field storage | | Presigned URLs | references/presigned-urls.md | Download links, direct browser uploads | | Testing & IAM | references/testing-storages.md | Mocking S3, IAM policy, common pitfalls |
The snippet below demonstrates the core MUST DO constraints: env-loaded credentials, STORAGES dict, separate media/static locations, and default_acl=None on the media backend.
python# settings.py import os AWS_STORAGE_BUCKET_NAME = os.environ["AWS_STORAGE_BUCKET_NAME"] AWS_S3_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME", "us-east-1") AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com" # On EC2/ECS/Lambda, omit keys entirely — boto3 uses the attached IAM role. STORAGES = { "default": { # media uploads "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", "OPTIONS": { "bucket_name": AWS_STORAGE_BUCKET_NAME, "location": "media", "default_acl": None, # rely on bucket policy, not per-object ACLs "file_overwrite": False, "querystring_auth": False, # public objects → clean URLs }, }, "staticfiles": { "BACKEND": "storages.backends.s3boto3.S3StaticStorage", "OPTIONS": { "bucket_name": AWS_STORAGE_BUCKET_NAME, "location": "static", }, }, } MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/media/" STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/static/"
python# models.py — uploads go straight to S3 on save() from django.db import models class Document(models.Model): file = models.FileField(upload_to="docs/") # uses STORAGES["default"]
When reviewing a project that already uses S3 (not greenfield), walk this checklist — each item is a constraint below rephrased as "find X, confirm Y":
grep -rn "AWS_SECRET_ACCESS_KEY\|aws_secret" settings/ → confirm values come from os.environ/django-environ or an IAM role, never literals committed to the repo.grep -rn "default_acl\|AWS_DEFAULT_ACL" . → on buckets created after April 2023, every value must be None. Any "public-read"/"private" will raise AccessControlListNotSupported; public access belongs in a bucket policy.STORAGES dict, not DEFAULT_FILE_STORAGE/STATICFILES_STORAGE (removed in Django 5.1, so silently ignored on 5.1/5.2/6.0); confirm the static class is S3StaticStorage, not a fabricated name.default (media) and staticfiles have distinct location prefixes or buckets so collectstatic never collides with uploads.region_name (or the global AWS_S3_REGION_NAME) matches the bucket's real region and that AWS_S3_CUSTOM_DOMAIN includes the region segment for non-us-east-1 buckets.querystring_auth=True and custom_domain=None; confirm presigned .url() results aren't cached past AWS_QUERYSTRING_EXPIRE.file_overwrite=False, confirm replaced files are explicitly deleted (otherwise superseded objects leak).Get/Put/Delete/ListBucket on the bucket ARN, not broader S3 access.default_acl=None so bucket policies (not object ACLs) control accesslocation prefixes or separate bucketsSTORAGES dict on Django 4.2+ (same config through 5.2 LTS and 6.0); DEFAULT_FILE_STORAGE/STATICFILES_STORAGE were removed in 5.1, so reserve them for < 4.2 onlycustom_domain=None on any backend that issues presigned URLsInMemoryStorage or moto) in tests instead of hitting real bucketsAWS_SECRET_ACCESS_KEY in settings.py or commit itquerystring_auth=True with a custom_domain (presigning breaks)Get/Put/Delete/ListBucket on the bucket ARNdjango-storages, S3Boto3Storage, S3StaticStorage, boto3, STORAGES dict, presigned URLs, generate_presigned_post, CloudFront, IAM policy, InMemoryStorage, moto
django-expert — core Django models, DRF, and ORM that produce the files this skill persists to S3fullstack-guardian — secure end-to-end upload flows and access control around stored filesdevops-engineer — provisioning the S3 buckets, IAM roles, and CloudFront distributions this skill targets| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→pass | 26,027 | 29,686 | +14% | 1 | 1 | 0% | 4,076 | 7,499 | +84% | 0 | 0 | — |
case-01 | fail→pass | 16,883 | 20,463 | +21% | 1 | 1 | 0% | 3,011 | 5,385 | +79% | 0 | 0 | — |
case-02 | fail→pass | 12,506 | 6,912 | -45% | 1 | 1 | 0% | 2,311 | 3,169 | +37% | 0 | 0 | — |
case-03 | pass→pass | 11,692 | 10,481 | -10% | 1 | 1 | 0% | 2,325 | 3,895 | +68% | 0 | 0 | — |
case-04 | pass→pass | 14,199 | 11,139 | -22% | 1 | 1 | 0% | 2,670 | 3,908 | +46% | 0 | 0 | — |
case-06 | pass→pass | 13,308 | 9,735 | -27% | 1 | 1 | 0% | 2,061 | 3,811 | +85% | 0 | 0 | — |
case-07 | pass→pass | 14,207 | 13,483 | -5% | 1 | 1 | 0% | 2,609 | 4,529 | +74% | 0 | 0 | — |
case-08 | pass→pass | 10,415 | 8,663 | -17% | 1 | 1 | 0% | 1,946 | 3,455 | +78% | 0 | 0 | — |
case-09 | pass→pass | 12,469 | 12,629 | +1% | 1 | 1 | 0% | 2,242 | 4,115 | +84% | 0 | 0 | — |
case-10 | fail→fail | 11,411 | 8,783 | -23% | 1 | 1 | 0% | 2,247 | 3,295 | +47% | 0 | 0 | — |
case-11 | pass→pass | 12,263 | 11,465 | -7% | 1 | 1 | 0% | 2,282 | 3,596 | +58% | 0 | 0 | — |
case-12 | pass→pass | 9,869 | 8,908 | -10% | 1 | 1 | 0% | 1,737 | 3,571 | +106% | 0 | 0 | — |
case-13 | fail→pass | 17,096 | 20,352 | +19% | 1 | 1 | 0% | 3,147 | 4,949 | +57% | 0 | 0 | — |
case-14 | pass→pass | 18,830 | 18,749 | -0% | 1 | 1 | 0% | 3,373 | 5,447 | +61% | 0 | 0 | — |
case-15 | pass→pass | 12,976 | 15,915 | +23% | 1 | 1 | 0% | 2,186 | 4,569 | +109% | 0 | 0 | — |
case-16 | pass→pass | 11,350 | 9,237 | -19% | 1 | 1 | 0% | 1,994 | 3,618 | +81% | 0 | 0 | — |
case-17 | pass→pass | 18,368 | 14,422 | -21% | 1 | 1 | 0% | 2,939 | 4,090 | +39% | 0 | 0 | — |
case-18 | pass→pass | 11,526 | 7,536 | -35% | 1 | 1 | 0% | 1,816 | 3,152 | +74% | 0 | 0 | — |
case-19 | pass→pass | 14,027 | 13,159 | -6% | 1 | 1 | 0% | 2,594 | 4,263 | +64% | 0 | 0 | — |
case-20 | pass→pass | 9,665 | 9,929 | +3% | 1 | 1 | 0% | 1,840 | 3,773 | +105% | 0 | 0 | — |
case-21 | pass→pass | 12,623 | 13,316 | +5% | 1 | 1 | 0% | 2,251 | 4,204 | +87% | 0 | 0 | — |
case-22 | pass→pass | 8,626 | 10,458 | +21% | 1 | 1 | 0% | 1,547 | 3,793 | +145% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.