Back to Blog
Python

Handling Django File Uploads and Static Files

python django file uploads and static files: Learn how to configure Django for user file uploads and static file serving, including MEDIA_ROOT, STATIC_ROOT, form handl...

DjangoFile UploadsStatic FilesMedia FilesWeb Development
Diagram showing the separation between Django static files and user-uploaded media files

python django file uploads and static files requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Django separates static files from user-uploaded media, and understanding that separation is the first step to configuring either correctly. Static files are assets you ship with your project: CSS, JavaScript, images, and fonts that are part of the application itself. Uploaded media are files your users provide through forms: profile pictures, documents, attachments, or any other user-generated content.

The two categories use different settings, different URL routing, and different production serving strategies. Static files are controlled by STATIC_URL, STATIC_ROOT, and STATICFILES_DIRS. Uploaded media are controlled by MEDIA_URL and MEDIA_ROOT. The Django development server serves static files automatically when django.contrib.staticfiles is in INSTALLED_APPS, but it does not serve media files unless you add a URL pattern yourself.

Configuring MEDIA_ROOT and MEDIA_URL for User Uploads

To accept file uploads, you need to tell Django where to store the files and how to reference them in URLs. These are the two settings in settings.py:

MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media"

MEDIA_ROOT is the absolute filesystem path where uploaded files are stored. MEDIA_URL is the URL prefix that maps to that directory when files are served. In production, your web server or storage backend must serve files from this location.

The MEDIA_ROOT path should be outside your static file directories. It is also wise to keep it outside your source code repository, since user uploads are not part of your application code and should be backed up separately.

Building a File Upload Form and View

A file upload starts with an HTML form that uses multipart/form-data encoding. Without that encoding, the browser does not send the file contents, and request.FILES will be empty.

Here is a minimal model with a FileField:

from django.db import models class Document(models.Model): title = models.CharField(max_length=200) file = models.FileField(upload_to="documents/") uploaded_at = models.DateTimeField(auto_now_add=True)

The upload_to argument is a subdirectory inside MEDIA_ROOT. Django appends the original filename to this path, and it handles name collisions by adding a suffix.

A form for this model is straightforward:

from django import forms from .models import Document class DocumentForm(forms.ModelForm): class Meta: model = Document fields = ["title", "file"]

The view must check that the request method is POST and that the form receives both request.POST and request.FILES:

from django.shortcuts import render, redirect from .forms import DocumentForm def upload_document(request): if request.method == "POST": form = DocumentForm(request.POST, request.FILES) if form.is_valid(): form.save() return redirect("document_list") else: form = DocumentForm() return render(request, "upload.html", {"form": form})

Passing request.FILES to the form is required. If you omit it, the form will appear valid but the file will be silently discarded.

Handling Uploaded Files in Django Views

When a form is not model-backed, you can access the uploaded file directly through request.FILES. The key is the name attribute of the file input in your HTML.

def upload_avatar(request): if request.method == "POST": uploaded = request.FILES.get("avatar") if uploaded: # uploaded is an UploadedFile instance handle_uploaded_file(uploaded) return redirect("profile") return render(request, "avatar_form.html")

An UploadedFile object exposes useful attributes and methods:

  • name: the filename as sent by the client
  • size: the file size in bytes
  • content_type: the MIME type reported by the browser
  • chunks(): an iterator over the file content, suitable for streaming

You can write the file to a location of your choice, but using a FileField on a model is usually simpler because Django manages the storage path and filename for you.

Serving Uploaded Files in Development

The Django development server does not serve media files by default. You must add URL patterns that map MEDIA_URL to MEDIA_ROOT. This is done in the project's urls.py:

from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... your URL patterns ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

The static() helper returns a URL pattern that serves files from document_root when DEBUG is True. This is only appropriate for development. In production, serving media through Django's development server is inefficient and insecure.

Configuring Static Files for Your Project

Static files use a separate set of settings. The default project template already includes the necessary configuration, but it is worth understanding what each setting does.

STATIC_URL = "static/" STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "staticfiles"

STATIC_URL is the URL prefix for static assets. STATICFILES_DIRS lists additional directories where Django looks for static files during development. STATIC_ROOT is the directory where collectstatic gathers all static files for production deployment.

During development, runserver serves static files from STATICFILES_DIRS and from each app's static/ directory automatically, as long as django.contrib.staticfiles is in INSTALLED_APPS.

In templates, you reference static files with the {% static %} tag:

{% load static %} <link rel="stylesheet" href="{% static 'css/app.css' %}">

Running collectstatic for Production

When you deploy, your web server needs all static files in one place. The collectstatic management command copies files from STATICFILES_DIRS and each app's static/ directory into STATIC_ROOT.

python manage.py collectstatic

After running this command, configure your web server to serve STATIC_ROOT at STATIC_URL. For example, in Nginx:

location /static/ { alias /path/to/your/project/staticfiles/; }

Media files should be served similarly, but from MEDIA_ROOT at MEDIA_URL. Some teams use a separate storage service such as Amazon S3 or a CDN for media files, which avoids tying uploaded files to the application server's filesystem.

Validating Uploaded Files for Security

File uploads introduce security risks that you must address. The most important is restricting what types of files users can upload and how large those files can be.

Django's FileField and forms.FileField accept a validators argument. A common approach is to check the file extension and MIME type:

from django.core.validators import FileExtensionValidator class Document(models.Model): file = models.FileField( upload_to="documents/", validators=[FileExtensionValidator(allowed_extensions=["pdf", "docx", "txt"])] )

You can also limit file size in the form by checking the size attribute:

class DocumentForm(forms.ModelForm): class Meta: model = Document fields = ["title", "file"] def clean_file(self): file = self.cleaned_data["file"] if file.size > 5 * 1024 * 1024: raise forms.ValidationError("File must be smaller than 5 MB.") return file

Note that the MIME type reported by the browser is not a reliable security boundary. A user can upload an executable with a .txt extension or a .pdf MIME type. Validate the actual content when the file type matters for security.

Choosing a Storage Backend for Media Files

By default, Django stores uploaded files on the local filesystem using django.core.files.storage.FileSystemStorage. For many projects this is sufficient. But when you deploy to multiple servers or need to scale, a shared storage backend such as Amazon S3, Google Cloud Storage, or Azure Blob Storage is often a better fit.

Django's storage abstraction lets you swap the backend without changing your model code:

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

The STORAGES setting was introduced in Django 4.2. In earlier versions, DEFAULT_FILE_STORAGE and STATICFILES_STORAGE were used. When you move media to a remote backend, the MEDIA_ROOT setting becomes less relevant, but MEDIA_URL still determines how file URLs are constructed.

The tradeoff is operational: a remote backend adds network latency and cost, but it decouples uploaded files from the application server's lifecycle. A local filesystem is simpler and faster for small projects, but you must handle backups, disk space, and multi-server consistency yourself.

python django file uploads and static files: Practical Usage | RYUSLOG DEV