Issues while trying to upload a new profile picture through a form

I’m trying to allow user to delete and to upload a new profile picture through a form that I sent in a template. The deletion part works unlike the upload one.
So here we have:

Models.py:

from django.contrib.auth.models import AbstractUser
from django.db import models
from datetime import datetime
import time
import uuid
from PIL import Image
from django.core.files.base import ContentFile
import io


def resize_image(image, size=(100, 100)):
    img = Image.open(image)
    img = img.convert('RGB')
    img = img.resize(size, Image.LANCZOS)

    thumb_io = io.BytesIO()
    img.save(thumb_io, format='JPEG')
    thumbnail = ContentFile(thumb_io.getvalue(), name=image.name)

    return thumbnail


def profile_picture_thumbnail(instance, filename):
    now = datetime.now()
    unique_id = uuid.uuid4().hex[:8]
    timestamp = now.strftime("%Y%m%d%H%M%S")
    new_filename = f"user_{unique_id}_{timestamp}.jpg"
    return f'users/{instance.pk}/profile/{time.strftime("%Y/%m/%d")}/{new_filename}'


class User(AbstractUser):
    PUBLISHER = 'PUBLISHER'
    MODERATOR = 'MODERATOR'
    VIEWER = 'VIEWER'
    ELDER = 'ELDER'

    ROLE_CHOICES = (
        (PUBLISHER, 'Publicateur'),
        (MODERATOR, 'Modérateur'),
        (VIEWER, 'Utilisateur normal'),
        (ELDER, 'Ancien')

    )
    profile_picture = models.ImageField(upload_to=profile_picture_thumbnail,
                                        verbose_name='Photo de profil', default='default/default_profile.png')
    role = models.CharField(max_length=30, choices=ROLE_CHOICES, verbose_name='Rôle', default='VIEWER')

    def save(self, *args, **kwargs):
        if self.profile_picture:
            self.profile_picture = resize_image(self.profile_picture)
        super(User, self).save(*args, **kwargs)

views.py

from django.conf import settings
from django.shortcuts import render, redirect, HttpResponse
from django.views.generic import View
from authentication.forms import LoginForm, SignUpForm, DeleteProfilePictureForm, UploadProfilePictureForm
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.decorators import login_required
import os

@login_required
def account_center(request):
    user = request.user
    default_profile_picture_path = os.path.join(settings.MEDIA_ROOT, 'default/default_profile.png')

    if request.method == 'POST':
        if 'pp_deletion_submit' in request.POST:
            if user.profile_picture:
                user.profile_picture.delete(save=False)

            if os.path.exists(default_profile_picture_path):
                with open(default_profile_picture_path, 'rb') as f:
                    user.profile_picture.save('default_profile.png', f, save=True)
                user.save()
                return redirect('account-center')
            else:
                return HttpResponse("Image par défaut non trouvée", status=404)

        elif 'pp_upload_submit' in request.POST:
            upload_profile_picture_form = UploadProfilePictureForm(request.POST, request.FILES, instance=user)
            if upload_profile_picture_form.is_valid():
                upload_profile_picture_form.save()
                return redirect('account-center')
        else:
            upload_profile_picture_form = UploadProfilePictureForm(instance=user)

    else:
        upload_profile_picture_form = UploadProfilePictureForm(instance=user)

    context = {
        'form': upload_profile_picture_form,
        'user': user,
    }
    return render(request, 'authentication/user_account.html', context)

forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model


User = get_user_model()

class UploadProfilePictureForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['profile_picture']

    profile_picture = forms.ImageField(
        label="Envoyer une photo de profil",
        required=True,
        error_messages={
            'invalid': "Uniquement des images",
            'required': "Ce champ est requis"
        },
        widget=forms.ClearableFileInput(attrs={'accept': 'image/*'})
    )

    def clean_profile_picture(self):
        picture = self.cleaned_data.get('profile_picture')
        if picture.size > 5 * 1024 * 1024:  # Validate the file size here
            raise forms.ValidationError("Le fichier est trop large ( > 5MB ).")
        return picture

    def save(self, commit=True):
        instance = super(UploadProfilePictureForm, self).save(commit=False)
        if self.cleaned_data.get('profile_picture'):
            instance.profile_picture = self.cleaned_data['profile_picture']
        if commit:
            instance.save()
        return instance


class DeleteProfilePictureForm(forms.Form):
    confirm_deletion = forms.BooleanField(
        label="Etes-vous sûr de vouloir supprimer votre photo de profil?",
        required=True
    )

template.html

{% extends 'authentication/base.html' %}
{% load static %}

<div id="dropdown_container">
                            <ul>
                                <button type="button" id="dropdown" onclick="displayDropdownList()">Editer</button>
                                    <ul id="dropdown-list" style="display:none;">
                                        <li>
                                            <form method="post">
                                                {% csrf_token %}
                                                {{form.as_p}}
                                                <button type="submit" name="pp_upload_submit">Enregistrer</button>
                                            </form>
                                        </li>
                                        <li>
                                            <form method="post">
                                                {% csrf_token %}
                                                <button type="submit" name="pp_deletion_submit">Supprimer la photo de profil</button>
                                            </form>
                                        </li>
                                    </ul>

                            </ul>
                        </div>

Please can someone expain me what’s wrong with my code ?

Backend should be able to understand the language of Frontend, I corrected and gave the codes below. You can understand how it should be by examining it.

models.py:

I added a check to prevent the default profile image from resizing. This will prevent it from resizing continuously.

views.py:

You need to handle request.FILES correctly for file uploads.

template.html:

I added enctype=“multipart/form-data” to the form to handle file uploads.

models.py

from django.contrib.auth.models import AbstractUser
from django.db import models
from datetime import datetime
import time
import uuid
from PIL import Image
from django.core.files.base import ContentFile
import io

def resize_image(image, size=(100, 100)):
    img = Image.open(image)
    img = img.convert('RGB')
    img = img.resize(size, Image.LANCZOS)

    thumb_io = io.BytesIO()
    img.save(thumb_io, format='JPEG')
    thumbnail = ContentFile(thumb_io.getvalue(), name=image.name)

    return thumbnail

def profile_picture_thumbnail(instance, filename):
    now = datetime.now()
    unique_id = uuid.uuid4().hex[:8]
    timestamp = now.strftime("%Y%m%d%H%M%S")
    new_filename = f"user_{unique_id}_{timestamp}.jpg"
    return f'users/{instance.pk}/profile/{time.strftime("%Y/%m/%d")}/{new_filename}'

class User(AbstractUser):
    PUBLISHER = 'PUBLISHER'
    MODERATOR = 'MODERATOR'
    VIEWER = 'VIEWER'
    ELDER = 'ELDER'

    ROLE_CHOICES = (
        (PUBLISHER, 'Publicateur'),
        (MODERATOR, 'Modérateur'),
        (VIEWER, 'Utilisateur normal'),
        (ELDER, 'Ancien')
    )
    profile_picture = models.ImageField(upload_to=profile_picture_thumbnail,
                                        verbose_name='Photo de profil', default='default/default_profile.png')
    role = models.CharField(max_length=30, choices=ROLE_CHOICES, verbose_name='Rôle', default='VIEWER')

    def save(self, *args, **kwargs):
        if self.profile_picture and 'default_profile.png' not in self.profile_picture.name:
            self.profile_picture = resize_image(self.profile_picture)
        super(User, self).save(*args, **kwargs)

views.py

from django.conf import settings
from django.shortcuts import render, redirect, HttpResponse
from django.contrib.auth.decorators import login_required
import os
from .forms import UploadProfilePictureForm, DeleteProfilePictureForm

@login_required
def account_center(request):
    user = request.user
    default_profile_picture_path = os.path.join(settings.MEDIA_ROOT, 'default/default_profile.png')

    if request.method == 'POST':
        if 'pp_deletion_submit' in request.POST:
            if user.profile_picture and user.profile_picture.name != 'default/default_profile.png':
                user.profile_picture.delete(save=False)

            if os.path.exists(default_profile_picture_path):
                with open(default_profile_picture_path, 'rb') as f:
                    user.profile_picture.save('default_profile.png', f, save=True)
                user.save()
                return redirect('account-center')
            else:
                return HttpResponse("Image par défaut non trouvée", status=404)

        elif 'pp_upload_submit' in request.POST:
            upload_profile_picture_form = UploadProfilePictureForm(request.POST, request.FILES, instance=user)
            if upload_profile_picture_form.is_valid():
                upload_profile_picture_form.save()
                return redirect('account-center')

    else:
        upload_profile_picture_form = UploadProfilePictureForm(instance=user)

    context = {
        'form': upload_profile_picture_form,
        'user': user,
    }
    return render(request, 'authentication/user_account.html', context)

forms.py

from django import forms
from django.contrib.auth import get_user_model

User = get_user_model()

class UploadProfilePictureForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['profile_picture']

    profile_picture = forms.ImageField(
        label="Envoyer une photo de profil",
        required=True,
        error_messages={
            'invalid': "Uniquement des images",
            'required': "Ce champ est requis"
        },
        widget=forms.ClearableFileInput(attrs={'accept': 'image/*'})
    )

    def clean_profile_picture(self):
        picture = self.cleaned_data.get('profile_picture')
        if picture.size > 5 * 1024 * 1024:  
            raise forms.ValidationError("Le fichier est trop large ( > 5MB ).")
        return picture

    def save(self, commit=True):
        instance = super(UploadProfilePictureForm, self).save(commit=False)
        if self.cleaned_data.get('profile_picture'):
            instance.profile_picture = self.cleaned_data['profile_picture']
        if commit:
            instance.save()
        return instance

class DeleteProfilePictureForm(forms.Form):
    confirm_deletion = forms.BooleanField(
        label="Etes-vous sûr de vouloir supprimer votre photo de profil?",
        required=True
    )

template.html

{% extends 'authentication/base.html' %}
{% load static %}

<div id="dropdown_container">
    <ul>
        <button type="button" id="dropdown" onclick="displayDropdownList()">Editer</button>
        <ul id="dropdown-list" style="display:none;">
            <li>
                <form method="post" enctype="multipart/form-data">
                    {% csrf_token %}
                    {{ form.as_p }}
                    <button type="submit" name="pp_upload_submit">Enregistrer</button>
                </form>
            </li>
            <li>
                <form method="post">
                    {% csrf_token %}
                    <button type="submit" name="pp_deletion_submit">Supprimer la photo de profil</button>
                </form>
            </li>
        </ul>
    </ul>
</div>

1 Like

Ohhh thanks to you I’ve realised my mistake now. it works, and I know why it was not the case before.
Thank you :love_you_gesture:

You’re welcome, man. If you have any trouble, I’d be happy to help.

Alright man, thanks again

You’re welcome, man :grinning: