python
Django
Install
django>=4.2
django-storages[s3]>=1.14Configure
Configure the endpoint, region, credential and bucket in this tool’s vocabulary. Path-style addressing is recommended for dotted bucket names and clients without nested wildcard TLS, but virtual-hosted addressing is also supported.
| In Django | Set it to |
|---|---|
| OPTIONS.endpoint_url | https://s3.canada.popcloud.ca |
| OPTIONS.region_name | canada |
| OPTIONS.access_key | PCAK00EXAMPLEKEYID00 |
| OPTIONS.secret_key | <your secret access key> |
| OPTIONS.addressing_style | path |
export AWS_ACCESS_KEY_ID=PCAK00EXAMPLEKEYID00
export AWS_SECRET_ACCESS_KEY=<your secret access key>
export AWS_ENDPOINT_URL=https://s3.canada.popcloud.ca
export AWS_REGION=canada
export POPCLOUD_BUCKET=pc-your-org-mediaThese are example values. Sign in and every snippet on this site fills in with your own endpoint, key and bucket.
# settings.py — Django 4.2 and later. There is nothing PopCloud-specific in the
# backend: it is the same S3 backend, pointed at a different endpoint.
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"bucket_name": os.environ["POPCLOUD_BUCKET"],
"endpoint_url": os.environ["AWS_ENDPOINT_URL"], # https://s3.<region>.popcloud.ca
"region_name": os.environ["AWS_REGION"], # canada
"access_key": os.environ["AWS_ACCESS_KEY_ID"],
"secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
# Recommended for predictable custom-endpoint behavior. PopCloud
# also supports virtual-hosted requests for single-label buckets.
"addressing_style": "path",
# Keep signed media URLs on modern AWS Signature Version 4 too.
"signature_version": "s3v4",
# PopCloud has no bucket ACLs — access is a property of the
# credential and of the bucket, not of each object.
"default_acl": None,
# Keep uploads from silently overwriting each other; Django appends
# a suffix instead.
"file_overwrite": False,
# Media URLs come back signed and expiring. Set querystring_auth to
# False only for a bucket you have made public.
"querystring_auth": True,
"querystring_expire": 3600,
},
},
"staticfiles": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"bucket_name": os.environ["POPCLOUD_BUCKET"],
"endpoint_url": os.environ["AWS_ENDPOINT_URL"],
"region_name": os.environ["AWS_REGION"],
"access_key": os.environ["AWS_ACCESS_KEY_ID"],
"secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
"addressing_style": "path",
"default_acl": None,
"location": "static",
# Static files are public and immutable — serve them unsigned from a
# public bucket, with a long cache header.
"querystring_auth": False,
"object_parameters": {"CacheControl": "max-age=31536000, immutable"},
},
},
}Use it
Save a file
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
# In a model this is just a FileField or ImageField — the storage backend is
# what changed, not your code.
stored_name = default_storage.save(name, ContentFile(content))Read a file
from django.core.files.storage import default_storage
with default_storage.open(name) as handle:
data = handle.read()Build a URL
from django.core.files.storage import default_storage
# With querystring_auth on, this is a presigned URL that expires — safe to
# hand to a browser for a private object.
url = default_storage.url(name)Delete an object
from django.core.files.storage import default_storage
default_storage.delete(name)What to watch for
These apply to Django specifically. The full list covers the platform.
- You’ll see
- 405 MethodNotAllowed from CreateBucket or DeleteBucket — `aws s3 mb`, `mc mb`, or a tool provisioning its own bucket on first run.
- Why
- Bucket lifecycle belongs to the control plane, which also allocates the storage account, the data-plane key and the CORS rules that come with it. Letting the edge create buckets would create half of one.
- Instead
- Create buckets in the dashboard, or with `POST /v1/buckets` on the control-plane API. Everything object-level then works normally against that bucket.
- You’ll see
- 405 MethodNotAllowed on PutBucketPolicy, PutBucketLifecycleConfiguration, PutBucketWebsite, PutBucketReplication, PutBucketNotificationConfiguration, PutBucketTagging, PutPublicAccessBlock and their delete counterparts; the matching Get calls return 501 or are refused upstream.
- Why
- These configure infrastructure behaviour that PopCloud manages itself. Access control is expressed through credential scopes and the bucket's public flag rather than through bucket policies.
- Instead
- Use credential scopes for access control (permissions plus a bucket allowlist, optionally with an expiry), the bucket's public flag for anonymous reads, and the control plane for CORS. Tools that reconcile bucket configuration — Terraform's `aws_s3_bucket` in particular — must be limited to object-level resources.
How this page is kept true
Every snippet above was extracted from examples/python/django-storages, a program that uploads, downloads, compares bytes, lists, presigns and cleans up after itself. It runs in CI against a live sandbox organisation via make docs-verify-python. If it stops passing, this page is wrong and we treat that as a bug in the product.
Not yet run against the live sandbox — the runner is wired, the first verified run stamps this line.