Users upload scanned invoices of up to 20 MB to a Django view; which DATA_UPLOAD_* and FILE_UPLOAD_* settings apply, and how do you actually cap the file size?
answer
- no setting caps one file
- DATA_UPLOAD_MAX_MEMORY_SIZE excludes files
- field and file counts
- SuspiciousOperation becomes 400
- cap before, during or after parsing
basics
~20 sNo Django setting caps a single file's size: DATA_UPLOAD_MAX_MEMORY_SIZE excludes file parts, and FILE_UPLOAD_MAX_MEMORY_SIZE only picks memory or disk. Cap it at the web server, with a custom upload handler, or by validating size after receipt.
solid answer
~40 sFour settings touch the request. `FILE_UPLOAD_MAX_MEMORY_SIZE` (2.5 MB) only decides that a 20 MB scan goes to a temp file. `DATA_UPLOAD_MAX_MEMORY_SIZE` (2.5 MB) limits **non-file** data, so it does not stop large files, though reading `request.body` on a multipart request counts the whole body and raises `RequestDataTooBig`. `DATA_UPLOAD_MAX_NUMBER_FIELDS` (1000) and `DATA_UPLOAD_MAX_NUMBER_FILES` (100) cap counts and raise `TooManyFieldsSent` or `TooManyFilesSent`. All three exceptions are `SuspiciousOperation` subclasses and become 400 responses. To cap file size you layer controls: a request-body limit in the web server in front of Django, which rejects early; a custom upload handler that raises `StopUpload` once a file passes 20 MB; and a check of `uploaded.size` in the form or view, which is the easiest but only runs after the bytes are on disk.
code
python · 20 lines# settings.py
FILE_UPLOAD_MAX_MEMORY_SIZE = 2_621_440 # default: memory/disk threshold only
DATA_UPLOAD_MAX_MEMORY_SIZE = 2_621_440 # default: non-file data only
DATA_UPLOAD_MAX_NUMBER_FILES = 5 # one invoice form, a few scans at most
FILE_UPLOAD_TEMP_DIR = "/srv/upload-tmp" # monitored volume
# forms.py
from django import forms
MAX_INVOICE_BYTES = 20 * 1024 * 1024
class InvoiceUploadForm(forms.Form):
scan = forms.FileField()
def clean_scan(self):
scan = self.cleaned_data["scan"]
if scan.size > MAX_INVOICE_BYTES:
raise forms.ValidationError("Invoices must be 20 MB or smaller.")
return scango deeper
Remember that Django has no per-file size setting, so a size limit must be added by you.
Explain what each DATA_UPLOAD_* and FILE_UPLOAD_* setting limits, which exceptions they raise, and why file parts escape DATA_UPLOAD_MAX_MEMORY_SIZE.
Layer an early body limit, an optional upload handler and form validation, and avoid request.body in upload views.
Set an organisation-wide upload policy covering limits at each layer, temp storage capacity and whether uploads bypass Django entirely.
## The settings that apply | Setting | Default | What it limits | When exceeded | |---|---|---|---| | `FILE_UPLOAD_MAX_MEMORY_SIZE` | `2621440` (2.5 MB) | nothing; memory versus temp-file threshold | file goes to disk | | `DATA_UPLOAD_MAX_MEMORY_SIZE` | `2621440` (2.5 MB) | request data **excluding file uploads** | `RequestDataTooBig` | | `DATA_UPLOAD_MAX_NUMBER_FIELDS` | `1000` | number of GET/POST parameters | `TooManyFieldsSent` | | `DATA_UPLOAD_MAX_NUMBER_FILES` | `100` | number of files in one multipart request | `TooManyFilesSent` | | `FILE_UPLOAD_TEMP_DIR` | `None` (system temp dir) | where spooled files are written | n/a | The three exceptions subclass `SuspiciousOperation`, so Django's request handler turns them into **400 Bad Request** responses (with a technical page when `DEBUG` is on). Setting any of the limits to `None` disables it; leaving them on is part of Django's denial-of-service protection. ## Why none of them caps a 20 MB file The multipart parser counts bytes against `DATA_UPLOAD_MAX_MEMORY_SIZE` only for ordinary form fields; file parts are handed to upload handlers and are not counted. A single 2 GB upload therefore passes every default setting and is written to `FILE_UPLOAD_TEMP_DIR`. For an invoice inbox, that means disk usage, not Django, is the first thing that breaks under abuse. One trap runs the other way: `request.body` applies `DATA_UPLOAD_MAX_MEMORY_SIZE` to the **whole** body, using `Content-Length` (or the actual size of a buffered body). Code that reads `request.body` in an upload view, for logging or signature checks, raises `RequestDataTooBig` for any invoice over 2.5 MB. ## Three places to cap the size 1. **Before Django: the web server or proxy.** A request-body size limit (a little above 20 MB to allow for the multipart overhead) rejects oversized requests before a worker reads them. It is the cheapest control, but it applies to the whole request, and its configuration lives outside Django. 2. **During parsing: a custom upload handler.** A `FileUploadHandler` subclass placed first in the handler list counts bytes in `receive_data_chunk()` and raises `StopUpload(connection_reset=True)` once a file exceeds 20 MB. Parsing stops, the file being received is closed and discarded, so it never reaches `request.FILES`, and the connection is reset rather than reading the rest of the body. The view still runs, so it must notice the missing file. 3. **After parsing: validation.** Checking `uploaded.size > 20 * 1024 * 1024` in the view, or in a form's `clean_<field>()`, gives the nicest error message, but the bytes have already been received and written to a temp file. A robust invoice upload uses the outer limit for protection and the validation for a friendly message; the handler is worth it when you want Django itself to stop early. ## Related knobs worth setting deliberately - Lower `DATA_UPLOAD_MAX_NUMBER_FILES` if the form accepts one invoice at a time; 100 files of 20 MB is 2 GB per request. - Point `FILE_UPLOAD_TEMP_DIR` at a volume sized for peak concurrent uploads. - Do not trust `uploaded.content_type`; check the file's actual bytes. ## Testing the limits The limits can be exercised in ordinary tests: - `@override_settings(DATA_UPLOAD_MAX_NUMBER_FILES=5)` plus a `client.post()` with six `SimpleUploadedFile` objects under one name should return 400; - a `SimpleUploadedFile` just over 20 MB should produce the form's size error; - a view test that reads `request.body` in an upload view fails as soon as the body passes 2.5 MB, which catches the logging trap early. ## Checklist - Web-server body limit in place and tested. - Size validated with a user-facing message. - File-count limit matches the form. - No `request.body` access in upload views. - Temp directory monitored.
- An upload view logs request.body for auditing and every invoice over 2.5 MB now fails with a 400. Why?Accessing `request.body` applies `DATA_UPLOAD_MAX_MEMORY_SIZE` to the whole body, files included, and raises `RequestDataTooBig`, a `SuspiciousOperation` that Django turns into a 400. The multipart parser exempts file parts, but `request.body` does not. Log metadata from `request.FILES` instead.
- What is the difference between StopUpload() and StopUpload(connection_reset=True) in a custom upload handler?Both stop parsing and close the file currently being received, so it never reaches `request.FILES`. With the default `connection_reset=False`, Django still reads and discards the rest of the request body so the client gets a normal response. With `connection_reset=True`, it stops reading immediately, which saves bandwidth and disk but makes the browser show a connection-reset error.
- Why is checking uploaded.size in a form not enough protection on its own?The check runs after parsing, when the whole file has already been received and written to a temporary file. An attacker can still send huge bodies that consume bandwidth, worker time and disk. It is the right place for a friendly message, but an earlier limit, in the web server or an upload handler, is what protects the service.
saying these in an interview costs you the question
- DATA_UPLOAD_MAX_MEMORY_SIZE rejects file uploads larger than 2.5 MB
- FILE_UPLOAD_MAX_MEMORY_SIZE caps the size of an uploaded file
- RequestDataTooBig produces a 500 Internal Server Error
- Validating uploaded.size in a form stops the bytes from being received
- Django has a FILE_UPLOAD_MAX_SIZE setting for per-file limits
- StopUpload makes Django return a 413 response automatically