Upload o carga de archivos en Django (avatar)

En este apartado vamos a aprender a cargar un archivo empleando Django, específicamente el avatar pero, los pasos señalados e implementados aquí los puedes emplear y adaptar para la carga de cualquier tipo de archivo como PDFs.

El proceso de carga de archivos no es un proceso difícil, pero sí consta de varios pasos que debemos de tener en cuenta; veremos paso por paso cada uno de estos pasos para que la implementación sea lo más entendible posible para el lector.

Modelo, formulario y otras configuraciones

Para adaptar el modelo de usuarios para que tenga el campo del avatar, pudiéramos modificar el modelo actual, pero, para evitar modificar un modelo de sistema, lo más recomendado sería crear una relación aparte de tipo uno a uno que contenga los campos adicionales que en este ejemplo, es el del 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')

Los campos de tipo ImageField establecen la ruta en donde se van a guardar las imágenes.

La relación es de tipo uno a uno OneToOneField ya que esta relación es para extender la relación de usuario con nuevos datos que en este ejemplo es solo el usuario.

Al guardar, veremos un error en la consola:

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".

Y es que es necesario instalar una dependencia de Python que es empleada para la manipulación de las imágenes al momento de definir el campo ImageField:

$ pip install Pillow

Creamos y ejecutamos las migraciones:

$ python manage.py makemigrations
$ python manage.py migrate

Creamos el formulario del usuario:

account\forms.py

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('avatar','user')

El manejo del campo de usuario aplicaremos varios campos en los siguientes apartados ya que por defecto es de tipo selección obligatoria comportamiento que no queremos.

Cargar un archivo, implementación mínima

Para evitar un error del formulario en el cual indique el el usuario es requerido, establecemos en el modelo que dicho campo puede ser nulo:

account\models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True)

También podríamos modificar el formulario como mostramos anteriormente; el siguiente punto a tratar, es colocar el campo de tipo oculto:

account\forms.py

class UserProfileForm(forms.ModelForm):
    class Meta:
        ***
        widgets = {'user': forms.HiddenInput() }

Para manejar el formulario, la implementación queda similares a los anteriores casos:

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})

Como consideraciones importantes tenemos:

UserProfileForm(request.POST, request.FILES)

Para establecer los archivos enviados por el usuario, que en este ejemplo es solamente uno, también se conserva la petición con datos planos:

request.POST

Ya que en el formulario anterior pueden haber otros campos de tipo textos o similares.

En cuanto al:

commit=False

Se emplea para solamente generar la instancia del modelo y NO guardar en la base de datos, ya que, antes de guardar a la base de dato se debe de establecer el usuario autenticado:

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 %}

Con el proceso anterior, se pueden cargar archivos en el sistema, veremos que se genera una carpeta con el archivo cargado en:

user\avatar

Que es la ruta definida en el campo de tipo imagen:

models.ImageField(upload_to='user/avatar')

Y el registro correspondiente en la base de datos; el problema que tenemos actualmente es que, al ser una relación de tipo uno a uno, solamente se puede procesar los datos una sola vez cuando no existe el registro del perfil del usuario en la base de datos, pero, esto lo solventamos en el siguiente apartado.

Otra forma de modificar los atributos del formulario, es directamente desde la clase:

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

In english
Andrés Cruz

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

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.