skip to content

How does Django turn an uploaded file's original name into its final storage name, from upload_to through get_available_name()?

level: middleimportance: should knowfreq 45%

answer

  1. field first, storage second
  2. strftime string or a callable
  3. sanitised, then checked for collisions
  4. underscore plus seven random characters
  5. pk may still be None

basics

~20 s

Django's FileField applies upload_to (a strftime string or a callable), the storage sanitises the name with get_valid_name(), and save() calls get_available_name(), which appends an underscore and seven random characters if the name is taken or too long.

solid answer

~40 s

`FieldFile.save()` calls `FileField.generate_filename()`. If `upload_to` is a string it is passed through `strftime` and joined to the file name, so `'avatars/%Y/%m/'` gives dated folders; if it is a callable, Django calls `upload_to(instance, filename)` and uses the returned path. The name then goes to the storage's `generate_filename()`, which rejects `..` path traversal and runs `get_valid_name()`, turning spaces into underscores and stripping other unsafe characters. Next `Storage.save()` calls `get_available_name()`: if the name exists, or is longer than the field's `max_length`, it appends `_` and seven random alphanumeric characters, truncating the stem when needed, and raises `SuspiciousFileOperation` if nothing fits. `FileSystemStorage` also retries on a creation race. The final name is what the row stores. With `FileSystemStorage(allow_overwrite=True)`, added in Django 5.1, the existing file is replaced instead.

code

python · 15 lines
python
import uuid
from pathlib import PurePosixPath

from django.conf import settings
from django.db import models


def avatar_path(instance, filename):
    ext = PurePosixPath(filename).suffix.lower()
    return f'avatars/{instance.user_id}/{uuid.uuid4().hex}{ext}'


class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    avatar = models.ImageField(upload_to=avatar_path, blank=True)

go deeper

for a junior

Recall that upload_to sets the subdirectory, that a string may contain strftime codes, and that name clashes get a random suffix rather than overwriting.

for a middle

Walk the chain: generate_filename on the field, get_valid_name and traversal checks in the storage, then get_available_name with its seven-character suffix and max_length truncation.

for a senior

Design upload_to callables that avoid pk None, collisions and guessable names, and choose between renaming and allow_overwrite deliberately.

for a principal

Set a naming convention for user content across services, balancing unguessable keys, cache behaviour and the ability to find and purge a user's files.

## Who does what The name of an uploaded file passes through two layers: - The **field** (`FileField`, `ImageField`) decides the directory, using `upload_to`. - The **storage** (`STORAGES['default']` or the field's own `storage`) decides what is legal and what is free. The chain starts in `FieldFile.save(name, content)`, which the field's `pre_save()` calls when a model with a new, uncommitted file is saved. ## Step by step 1. **`upload_to` is applied** by `FileField.generate_filename(instance, filename)`: - a **string** is formatted with `datetime.now().strftime(...)` and joined to the name: `'avatars/%Y/%m/'` plus `me.png` gives `avatars/2026/09/me.png`; - a **callable** receives `(instance, filename)` and returns the whole relative path, directory and file name. 2. **The name is validated**: absolute paths and `..` components raise `SuspiciousFileOperation`. 3. **The storage cleans it** in `generate_filename()`, calling `get_valid_name()`. The default uses `get_valid_filename()`: leading and trailing spaces removed, inner spaces turned into underscores, anything other than letters, digits, `-`, `_` and `.` dropped. `john's portrait.jpg` becomes `johns_portrait.jpg`. 4. **`Storage.save()` finds a free name** with `get_available_name(name, max_length)`. While the name exists or exceeds the field's `max_length`, it asks `get_alternative_name()` for `stem_XXXXXXX.ext`, seven random letters and digits, trimming the stem if the result is still too long. If the stem is trimmed to nothing it raises `SuspiciousFileOperation` asking for a larger `max_length`. 5. **The backend writes the bytes** in `_save()`. `FileSystemStorage` opens the file with an exclusive-create flag; if another process created the same name in the meantime it goes back to `get_available_name()` and tries again. 6. **The returned name is stored** on the instance and saved in the row. ## Designing upload_to for avatars A callable gives the most control. For user avatars kept in object storage a common shape is: ```python import uuid from pathlib import PurePosixPath def avatar_path(instance, filename): ext = PurePosixPath(filename).suffix.lower() return f'avatars/{instance.user_id}/{uuid.uuid4().hex}{ext}' ``` - A random component makes collisions practically impossible, so `get_available_name()` in practice never has to rename, and URLs cannot be guessed from the user's original file name. - `instance.user_id` is safe to use because the related user already exists. - **`instance.pk` is not**: for a new row with an auto-incremented key, the file is saved in `pre_save()`, before the `INSERT`, so `pk` is still `None` and the path would contain `None`. - The callable must be a module-level function, not a lambda, because migrations serialise a reference to it. ## Overwriting instead of renaming | Behaviour | How | |---|---| | Keep both files, rename the new one | default for every storage | | Replace the existing file in place | `FileSystemStorage(allow_overwrite=True)`, Django 5.1+ | | Custom policy | subclass the storage and override `get_available_name()` or `get_alternative_name()` | Overwriting a predictable path is risky for user content: browsers and caches may keep serving the old image under the same URL, and two users uploading to one path can clobber each other. Unique names plus explicit deletion of the old file are usually safer.

  • Why is instance.pk a bad ingredient in an upload_to callable?
    The file is saved in the field's `pre_save()`, which runs before the row is inserted. For a new object with an auto-incremented primary key, `pk` is still `None`, so the path would contain `None`. Use a value that already exists, such as a foreign key id or a UUID, or generate a random name.
  • What does get_available_name() do when the field's max_length is too small for the generated name?
    It truncates the file stem so that `stem_XXXXXXX.ext` fits within `max_length`. If the stem would have to be removed entirely, it raises `SuspiciousFileOperation` telling you to allow a larger `max_length` on the field.

saying these in an interview costs you the question

  • Saving a file with an existing name silently overwrites the old file by default.
  • upload_to must always be a callable; strings are taken literally.
  • instance.pk is always available inside an upload_to callable.
  • Django keeps the user's original file name unchanged, spaces and quotes included.
  • A lambda works fine as upload_to because migrations ignore it.