Belajar Django - Static Files, Media & Storage
Episode 17 of 27

Belajar Django - Static Files, Media & Storage

Mengelola aset aplikasi: struktur static files, pengumpulan dengan collectstatic dan penyajian dengan WhiteNoise, upload media pengguna dengan FileField, serta memindahkan storage ke S3-compatible untuk production.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi blog devblog sudah punya halaman, API, auth, dan background task. Tapi belum ada satu aspek yang tidak bisa dihindari: aset — CSS, JavaScript, gambar, dan file upload pengguna. Di episode ini kita mengatur Static Files, Media, dan Storage: bagaimana Django mengumpulkan dan menyajikannya di development, lalu memindahkannya ke object storage untuk produksi.

Mengapa topik ini penting? Karena cara aset disajikan menentukan kecepatan loading halaman — dan file yang di-upload user menentukan ketersediaan data. Kesalahan konfigurasi storage berarti gambar rusak, CSS tidak ke-load, atau — lebih buruk — file hilang saat server diganti.

Static vs Media

Django membedakan dua jenis aset:

Static filesMedia files
ContohCSS, JS, gambar templateAvatar user, gambar post
SumberDikontrol developer (repository)Di-upload pengguna
PenyimpananSTATIC_ROOT setelah collectstaticMEDIA_ROOT
BackupIkut codebasePerlu backup terpisah

Di settings:

Pythonsettings.py - static & media
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]
 
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

STATICFILES_DIRS adalah folder static project (di luar app), sementara tiap app punya folder blog/static/blog/. STATIC_ROOT adalah folder output saat collectstatic — ini yang disajikan web server di produksi.

Untuk development, serve media lewat urls:

Pythondevblog/urls.py - serve media (dev only)
from django.conf import settings
from django.conf.urls.static import static
 
urlpatterns = [
    ...
]
 
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Warning

Blok static(settings.MEDIA_URL, ...) di atas hanya boleh aktif saat DEBUG = True. Di produksi, Nginx/CDN (episode 23) yang menyajikan media — Django sendiri tidak dirancang untuk menyajikan file besar secara efisien. Lupa mematikan ini di produksi = bypass yang menghantam performa.

collectstatic dan WhiteNoise

Di produksi, semua static harus dikumpulkan ke satu folder dan disajikan tanpa meminta web server eksternal mengelola (atau bersama-sama dengan itu). collectstatic menyalin semua static dari semua app + STATICFILES_DIRS ke STATIC_ROOT:

Kumpulkan static files
python manage.py collectstatic --noinput

Untuk menyajikan static langsung dari aplikasi Django (tanpa Nginx khusus static), pakai WhiteNoise:

Install WhiteNoise
pip install whitenoise
Pythonsettings.py - WhiteNoise
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    ...
]
 
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

WhiteNoiseMiddleware harus berada tepat setelah SecurityMiddleware. CompressedManifestStaticFilesStorage menggabungkan ManifestStaticFilesStorage (nama file di-hash untuk cache busting) dengan kompresi gzip/brotli otomatis. Hasilnya: static cepat dengan cache yang benar — tanpa server static terpisah.

Media Upload dengan FileField

Tambahkan upload gambar ke model:

Pythonblog/models.py - FileField
class Post(models.Model):
    ...
    cover_image = models.ImageField(upload_to="post_covers/", blank=True, null=True)
Install Pillow untuk ImageField
pip install Pillow

ImageField butuh Pillow untuk validasi gambar. File di-upload ke MEDIA_ROOT/post_covers/ dengan nama acak (Django menambahkan suffix untuk mencegah tabrakan nama). Di template:

Pythontemplates - tampilkan media
{% if post.cover_image %}
    <img src="{{ post.cover_image.url }}" alt="{{ post.title }}">
{% endif %}

{{ post.cover_image.url }} otomatis menghasilkan URL di MEDIA_URL.

Tip

Jangan pernah menggunakan file yang di-upload user langsung tanpa validasi ukuran dan jenis. Batasi MaxLengthValidator pada file, periksa tipe konten, dan pertimbangkan resize gambar dengan Pillow saat proses (task Celery episode 13 cocok untuk ini). File anonim yang di-upload adalah salah satu vektor penyebaran malware.

Storage ke S3-Compatible

Di produksi dengan banyak instance (episode 24), media tidak boleh disimpan di disk lokal setiap server — server mana yang menyimpan avatar user itu? Solusinya object storage: S3 AWS atau yang kompatibel seperti Cloudflare R2, MinIO, dan Backblaze B2.

Install django-storages
pip install django-storages boto3
Pythonsettings.py - S3 storage
import os
 
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}
 
AWS_ACCESS_KEY_ID = env.AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY = env.AWS_SECRET_ACCESS_KEY
AWS_STORAGE_BUCKET_NAME = env.AWS_STORAGE_BUCKET_NAME
AWS_S3_REGION_NAME = env.AWS_S3_REGION_NAME
AWS_S3_ENDPOINT_URL = env.AWS_S3_ENDPOINT_URL  # untuk R2/MinIO
AWS_DEFAULT_ACL = "private"

Kredensial semua lewat env dari episode 16. AWS_S3_ENDPOINT_URL membuat konfigurasi yang sama bekerja untuk AWS S3 maupun layanan S3-compatible (R2, MinIO) — cukup ganti env. AWS_DEFAULT_ACL = "private" penting: file upload bersifat privat dan hanya disajikan saat ada authorisasi (untuk akses publik, buat presigned URL).

PythonPresigned URL untuk file privat
from django.core.files.storage import default_storage
 
def get_media_url(file_field):
    return default_storage.url(file_field.name, expire=3600)

default_storage kini mengarah ke S3 — semua kode lama (model.file.url) tetap bekerja tanpa perubahan. Inilah keunggulan abstraksi storage Django.

Strategi CDN dan Cache Header

Untuk static dan media yang sering diakses, pasang CDN di depan (Cloudflare, CloudFront). Dua pengaturan yang saling mendukung:

Pythonsettings.py - cache headers
from django.views.decorators.cache import cache_control
 
@cache_control(max_age=60 * 60 * 24 * 30, immutable=True)
def serve_cached_asset(request, path):
    ...

ManifestStaticFilesStorage sudah memberi nama file ber-hash — jadi header immutable=True aman dipakai (file baru punya nama baru). Media user sebaiknya private + presigned URL agar tidak sembarang orang mengunduh.

Penutup

Inti yang harus dibawa pulang:

  • Static dikontrol developer, media di-upload user — dua dunia penyimpanan yang berbeda.
  • collectstatic mengumpulkan ke STATIC_ROOT; WhiteNoise menyajikan cepat tanpa server terpisah.
  • ImageField butuh Pillow; validasi ukuran & jenis file upload.
  • Object storage (S3/R2/MinIO) wajib untuk multi-instance; django-storages membuatnya transparan.
  • Kredensial storage via env (episode 16); file privat + presigned URL.
  • ManifestStaticFilesStorage memberi hash filename → cache immutable aman.

Di episode 18 selanjutnya kita masuk fase keamanan: Security Best Practices (OWASP) — XSS, CSRF, SQL injection, clickjacking, security headers, manage.py check --deploy, dan hardening produksi dengan SECURE_* settings. Sampai jumpa di episode 18!

Belajar Django - Static Files, Media & Storage | Belajar Django