Django
file handling
web development
file downloads
Python

Having Django serve downloadable files

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Serving downloadable files in Django is straightforward with FileResponse, but production quality requires more than returning bytes. You need correct headers, authorization checks, and efficient handling for large files. This guide covers direct file responses, generated downloads, and deployment-safe patterns.

Basic Download Endpoint With FileResponse

For files already stored on disk, FileResponse streams data and avoids loading the full file into memory.

python
1from pathlib import Path
2from django.conf import settings
3from django.http import FileResponse, Http404
4
5
6def download_report(request, report_name):
7    file_path = Path(settings.MEDIA_ROOT) / "reports" / report_name
8    if not file_path.exists() or not file_path.is_file():
9        raise Http404("File not found")
10
11    response = FileResponse(file_path.open("rb"), as_attachment=True)
12    response["Content-Type"] = "application/pdf"
13    response["Content-Disposition"] = f'attachment; filename="{file_path.name}"'
14    return response

as_attachment=True instructs browsers to download instead of rendering inline.

Add Authorization Before File Access

Never expose file paths directly without checking ownership or permissions. File download endpoints are common data-leak surfaces.

python
1from pathlib import Path
2from django.conf import settings
3from django.http import FileResponse, Http404, HttpResponseForbidden
4from django.shortcuts import get_object_or_404
5from .models import Invoice
6
7
8def download_invoice(request, invoice_id):
9    invoice = get_object_or_404(Invoice, id=invoice_id)
10    if invoice.user_id != request.user.id:
11        return HttpResponseForbidden("Not allowed")
12
13    file_path = Path(settings.MEDIA_ROOT) / invoice.storage_name
14    if not file_path.exists():
15        raise Http404("Invoice file missing")
16
17    return FileResponse(
18        file_path.open("rb"),
19        as_attachment=True,
20        filename=f"invoice-{invoice.id}.pdf"
21    )

The key rule is validating access using business data, not only guessed filenames.

Serving Generated Files

Sometimes files are generated on demand, such as CSV exports. You can build content in memory for small payloads or stream rows for large datasets.

Simple CSV generation:

python
1import csv
2from io import StringIO
3from django.http import HttpResponse
4
5
6def download_users_csv(request):
7    buffer = StringIO()
8    writer = csv.writer(buffer)
9    writer.writerow(["id", "email", "is_active"])
10
11    for user in request.user.__class__.objects.all().order_by("id")[:1000]:
12        writer.writerow([user.id, user.email, user.is_active])
13
14    response = HttpResponse(buffer.getvalue(), content_type="text/csv")
15    response["Content-Disposition"] = 'attachment; filename="users.csv"'
16    return response

For very large exports, use streaming responses or async job generation to avoid long request time.

Production Offloading Patterns

Django can serve files, but reverse proxies and object storage usually do it more efficiently at scale.

Common production approach:

  • app validates permissions
  • app returns internal redirect header
  • Nginx serves the file directly from protected path

This reduces Python process load while keeping access control in application logic.

If files are in cloud object storage, generate short-lived signed URLs after authorization checks. That pattern improves throughput and reduces application bandwidth costs.

Correct Headers and Browser Behavior

Content-Disposition is essential:

  • attachment triggers download
  • inline lets browser attempt display

Also set a meaningful Content-Type when known. Generic binary fallback can cause poor browser handling for file previews.

For filenames with spaces or non-ASCII characters, use robust header encoding utilities rather than manual concatenation. Browser compatibility can vary, so test on target clients.

Handling Missing Files and Logging

File metadata can become stale if files are moved or deleted outside the app. Handle missing files gracefully and log context:

  • file identifier
  • user identifier
  • endpoint name
  • timestamp

Structured logs make incident response much faster when download failures spike.

Also protect against path traversal by resolving filenames from trusted database fields, not user-controlled path fragments.

Common Pitfalls

  • Returning full file content in memory for large files instead of streaming.
  • Skipping authorization checks because endpoint is behind login only.
  • Building file paths directly from user input without validation.
  • Forgetting Content-Disposition, leading to inconsistent browser behavior.
  • Using Django app workers for high-volume static file serving in production.

Summary

  • Use FileResponse for efficient streaming of stored files.
  • Enforce permission checks before opening file handles.
  • Set download headers explicitly for correct client behavior.
  • Prefer proxy or object-storage offloading for large-scale delivery.
  • Treat download endpoints as security-sensitive and log failures clearly.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.