Upload files in Django (avatar)

- Andrés Cruz

En español

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

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy