How to display, in a ModelForm, the initial data in base


(Aurélia Gourbère) #1

Hello everyone and best wishes for this new year.
I am a student in development web application Django and Python.
I started to code of my final project which must close my student course.
I read diligently your tutorials, Vitor. I am largely inspired by your methods and I want to thank you again for that.
Here’s my question: I struggle to find a simple answer on the Web. I would like to display a form (based on a ModelForm) to update my user’s profile. I would like this form to display in initial data the values already present in the database. As in the administration of Django.

Here is my code for the moment , I have no prepopulated field…
models.py

class UserProfile(models.Model):
   user = models.OneToOneField(User, on_delete=models.CASCADE)
   username = models.CharField(max_length=60, blank=True)
   bio = models.TextField(max_length=500, blank=True)
   dept = models.CharField(max_length=5, blank=True)
   town = models.CharField(max_length=60, blank=True)
   birth_year = YearField(null=True, blank=True)
   avatar = models.ImageField(null=True, blank=True, upload_to='user_avatar/')
   created_at = models.DateTimeField(auto_now_add=True)
   updated_at = models.DateTimeField(auto_now=True)

   @receiver(post_save, sender=User)
   def create_user_profile(sender, instance, created, **kwargs):
       if created:
           UserProfile.objects.create(user=instance)

   @receiver(post_save, sender=User)
   def save_user_profile(sender, instance, **kwargs):
       instance.userprofile.save()

forms.py

class ProfileForm(forms.ModelForm):

    class Meta:
        model = UserProfile
        fields = ['username', 'bio', 'dept', 'town', 'birth_year', 'avatar']

views.py

@login_required
@transaction.atomic
def update_profile(request):

    if request.method == 'POST':

        # manage file storage for the avatar picture


        # user_form = SignupForm(request.POST, instance=request.user)

        profile_form = ProfileForm(request.POST, instance=request.user.userprofile)
        if profile_form.is_valid():
            # user_form.save()
            profile_form.save()
            messages.success(request , _('Your profile was successfully updated!'))
            return redirect('musicians:profile')
        else:
            messages.error(request , _('Please correct the error below.'))

    else:
        # user_form = SignupForm(request.POST , instance=request.user)
        profile_form = ProfileForm(request.POST , instance=request.user.userprofile)

    return render(request , 'musicians/update_profile.html', {
        # 'user_form': user_form,
        'profile_form': profile_form
    })

Thanks a lot in advance for your attention.
Best regards.
Aurélia


(Vitor Freitas) #2

Hi @Aurelia_Gourbere!

Welcome to the forum :slight_smile:

Your code looks fine, except on the views.py you should not include the request.POST statement in the else block:

views.py

@login_required
@transaction.atomic
def update_profile(request):
    if request.method == 'POST':
        profile_form = ProfileForm(request.POST, request.FILES, instance=request.user.userprofile)
        if profile_form.is_valid():
            profile_form.save()
            messages.success(request , _('Your profile was successfully updated!'))
            return redirect('musicians:profile')
        else:
            messages.error(request , _('Please correct the error below.'))
    else:
        profile_form = ProfileForm(instance=request.user.userprofile)  # <- here

    return render(request , 'musicians/update_profile.html', {
        'profile_form': profile_form
    })

Then your update_profile.html should be just:

musicians/update_profile.html

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  {{ profile_form.as_p }}
  <input type="submit" value="Save">
</form>

PS: I included the request.FILES and enctype="multipart/form-data" because your model have a FileField.


(Aurélia Gourbère) #3

Oh Thanks a lot Vitor !!! I turned around this problem a lot of hours!!! It 's work fine for the moment !
In the meantime, I’ve been looking for a view-based solution based on the UpdateView class … is not it a better way to upload a user profile?


(Vitor Freitas) #4

Yes, you can use an UpdateView to do the same thing.

The equivalent implementation using class-based views would be something like that:

from django.urls import reverse_lazy
from django.views.generic import UpdateView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import UserProfile
from .forms import ProfileForm

class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = UserProfile
    form_class = ProfileForm
    success_url = reverse_lazy('musicians:profile')
    template_name = 'musicians/update_profile.html'
    success_message = 'Your profile was successfully updated!'

    def get_object(self, queryset=None):
        return self.request.user.userprofile

    def form_invalid(self, form):
        messages.error(self.request , 'Please correct the error below.')
        return super().form_invalid(form)
  • UpdateView: Base implementation of the form processing
  • LoginRequiredMixin: Protect the view against non authorized users (equivalent to the decorator @login_required)
  • SuccessMessageMixin: Utility to include a success message using the django.contrib.messages framework

Both implementations are fine. But I’m a big fan of plain function based views :slight_smile: I find them easier to read and to know what’s going on


(Aurélia Gourbère) #5

I find this exchange interesting because many examples are given with views based on classes, which made me think that it was the best way. I will follow your advice because, indeed, it is more readable and the tests will be easier to write later. Thank you very much.