Upload files in Django (avatar)
Content Index
- ⚙️ Introduction: Uploading files in Django isn't complicated (if you know this beforehand)
- Initial Setup: Prepare Your Django Environment
- Typical Error: Pillow is not installed
- Model Creation: Where to Save the Uploaded File
- Uploading a file, minimum implementation
- View and logic to process the upload
- HTML Template to Display the Form
- Verify that the upload works
- Common errors when uploading files in Django
- Tips and Best Practices
- ❓ Frequently Asked Questions (FAQs)
- Conclusion
Uploading files in Django might seem complicated the first time, but it's really not... if you know what steps to follow and what errors to avoid.
We're going to learn how to upload a file using Django, specifically the avatar, but the steps outlined and implemented here can be used and adapted for uploading any type of file, such as PDFs.
The file upload process is not a difficult process, but it does consist of several steps that we must keep in mind; we will cover each of these steps step-by-step to make the implementation as understandable as possible for the reader.
⚙️ Introduction: Uploading files in Django isn't complicated (if you know this beforehand)
Before starting, it's worth clarifying something: Django is already prepared to handle files; you just need to configure it properly.
This tutorial will serve you for both uploading a profile image and for uploading PDF documents or other formats.
When I first tried this, I ran into silly errors—like the classic "Pillow is not installed"—but I'll show you how to solve them quickly.
Initial Setup: Prepare Your Django Environment
Install dependencies and define paths
First, install Pillow, the library Django uses to handle images:
$ pip install PillowIn your settings.py file, define the paths where files will be saved:
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'Create the /media folder in the project's root directory and ensure you have write permissions.
I forgot to create it the first time, and Django didn't automatically generate the directory.
Typical Error: Pillow is not installed
If you see this error:
Cannot use ImageField because Pillow is not installed.simply install the dependency and run again:
$ python manage.py makemigrations $ python manage.py migrateModel Creation: Where to Save the Uploaded File
To adapt the user model so that it includes the avatar field, we could modify the current model, but to avoid modifying a system model, the most recommended approach would be to create a separate one-to-one relationship containing the additional fields, which in this example is the avatar.
For this example, we'll use a model that extends the user through a OneToOneField relationship.
This way we avoid modifying the system's base model, which I always recommend:
account\models.py
# Create your models here.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='user/avatar')✨ I chose to create a separate model to modify the Django User entity; it's cleaner and safer in the long run.
We create and run the migrations:
$ python manage.py makemigrations
$ python manage.py migrateNow we create a Django ModelForm that handles both regular data (request.POST) and files (request.FILES):
account\forms.py
from django import forms
from .models import UserProfile
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('avatar', 'user')
widgets = {'user': forms.HiddenInput()}We will apply several configurations for the user field in the following sections, as by default it is a mandatory selection type, which is not the behavior we want.
Uploading a file, minimum implementation
To avoid a form error indicating that the user is required, we set the model to allow that field to be null:
account\models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True)We could also modify the form as shown previously; the next point to address is setting the field type to hidden:
account\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
***
widgets = {'user': forms.HiddenInput() }View and logic to process the upload
To handle the form, the implementation is similar to previous cases:
account\views.py
@login_required
def profile(request):
form = UserProfileForm()
if request.method == "POST":
form = UserProfileForm(request.POST, request.FILES)
if form.is_valid():
userprofile = form.save(commit=False)
userprofile.user = request.user
userprofile.save()
return render(request, 'profile.html', {'form':form})Important considerations include:
UserProfileForm(request.POST, request.FILES)To set the files sent by the user, which in this example is only one, the request with flat data is also retained:
request.POSTSince the form above may have other text-type or similar fields.
As for:
commit=FalseIt is used only to generate the model instance and NOT save to the database, because, before saving to the database, the authenticated user must be set:
userprofile = form.save(commit=False) userprofile.user = request.user userprofile.save()We define the template with the form, Bootstrap classes, and to show errors:
HTML Template to Display the Form
account\templates\profile.html
{% extends "base_user_account.html" %}
{% load widget_tweaks %}
{% block content %}
{% if form.errors %}
<div class="alert alert-warning">
{{ form.errors }}
</div>
{% endif %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mt-3">
{{ form.avatar|add_label_class:"form-label" }}
{{ form.avatar|add_class:"form-control" }}
</div>
{{ form.user }}
<button type="submit" class="mt-3 btn btn-primary">Send</button>
</form>
{% endblock %}Remember the enctype="multipart/form-data" attribute; without it, files are not sent correctly.
With the previous process, files can be uploaded to the system; we will see that a folder is generated with the uploaded file in:
user\avatar
Which is the path defined in the image type field:
models.ImageField(upload_to='user/avatar')And the corresponding record in the database; the problem we currently have is that, being a one-to-one relationship, data can only be processed once when the user profile record does not exist in the database, but we solve this in the next section.
Another way to modify the form attributes is directly from the class:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('avatar','user')
def __init__(self,*args,**kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['user'].widget = forms.HiddenInput()
self.fields['user'].required = False
self.fields['avatar'].widget.attrs['class'] = "custom-file-input"
self.fields['avatar'].widget.attrs['id'] = "customFile"
***Verify that the upload works
After submitting the form:
- The file is saved inside /media/user/avatar/.
- A UserProfile record is created in the database.
- You can check it from the Django admin or by exploring the media folder.
I opened the media/user/avatar folder directly and confirmed that the image had been copied correctly.
Common errors when uploading files in Django
Error Cause Solution
request.FILES empty Missing enctype="multipart/form-data" Add to the form
“MEDIA_ROOT not defined” Missing configuration Define in settings.py
“Pillow is not installed” Missing library pip install Pillow
File not visible Incorrectly defined path Verify MEDIA_URL and MEDIA_ROOT
Tips and Best Practices
Keep paths clean (upload_to='user/avatar' is clear and organized).
Control the maximum file size.
Use custom validations in the form.
If uploading images, optimize them with Pillow before saving.
Protect media paths in production environments (Nginx/Apache).
❓ Frequently Asked Questions (FAQs)
Where are uploaded files saved in Django?
In the folder defined by MEDIA_ROOT within the project.
How to allow multiple files?
Use a FileField with multiple=True and process request.FILES.getlist().
What happens if the user already has a previous file?
You can overwrite it or delete the old one before saving the new one.
Conclusion
Uploading files in Django is much easier when you understand the logic behind request.FILES, the form, and the storage path.
In my experience, the most common errors come from small omissions (like forgetting the enctype or not installing Pillow).
By following this guide, you will have a functional and professional implementation ready for production.
The next step is to use the detail view.
I agree to receive announcements of interest about this Blog.
We will see how to upload files, specifically the avatar using Django 5 and Bootstrap 5 components.