Upload files in Django (avatar)
In this section we are going to learn how to upload a file using Django, specifically the avatar, but the steps indicated and implemented here can be used and adapted to upload 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 take into account; we will see each of these steps step by step so that the implementation is as understandable as possible for the reader.
Modelo, formulario y otras configuraciones
To adapt the user model to have the avatar field, we could modify the current model, but, to avoid modifying a system model, the most recommended would be to create a separate one-to-one relationship that contains the additional fields, in this example, is the avatar:
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')
Fields of type ImageField establish the path where the images will be saved.
The relationship is one to one type OneToOneField since this relationship is to extend the user relationship with new data which in this example is just the user.
When saving, we will see an error in the console:
ERRORS:
account.UserProfile.avatar: (fields.E210) Cannot use ImageField because Pillow is not installed.
HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow".
And it is necessary to install a Python dependency that is used to manipulate the images when defining the ImageField field:
$ pip install Pillow
We create and execute the migrations:
$ python manage.py makemigrations
$ python manage.py migrate
We create the user form:
account\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('avatar','user')
The management of the user field we will apply several fields in the following sections since by default it is a mandatory selection type behavior that we do not want.
Upload file, minimal implementation
To avoid an error in the form in which the user is required, we establish in the model that said field can 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 above; the next point to deal with is to place the hidden :
account\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
***
widgets = {'user': forms.HiddenInput() }
To manage the form, the implementation is similar to the 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})
As important considerations we have:
UserProfileForm(request.POST, request.FILES)
To establish the files sent by the user, which in this example is only one, the request is also kept with flat data:
request.POST
Since in the previous form there may be other text-type fields or similar.
With this:
commit=False
It is used to only generate the model instance and NOT save it to the database, since, before saving to the database, the authenticated user must be established:
userprofile = form.save(commit=False)
userprofile.user = request.user
userprofile.save()
Definimos el template con el formulario y las clases de Bootstrap y mostrar los errores:
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 %}
We define the template with the form and the Bootstrap classes and show the errors:
user\avatar
Que es la ruta definida en el campo de tipo imagen:
models.ImageField(upload_to='user/avatar')
And the corresponding record in the database; the problem we currently have is that, since it is a one-to-one relationship, the data can only be processed once when there is no user profile record in the database, but we solve this in the following section.
Another way to modify the attributes of the form 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"
***
- Andrés Cruz
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter