django class-based view - UpdateView - How to access the request user while processing a form?

10,921

Solution 1

Hacking around like passing a hidden field doesn't make sense as this truly has nothing to do with the client - this classic "associate with logged in user" problem should definitely be handled on the server side.

I'd put this behavior in the form_valid method.

class MyUpdateView(UpdateView):
    def form_valid(self, form):
        instance = form.save(commit=False)
        instance.user = self.request.user
        super(MyUpdateView, self).save(form)

   # the default implementation of form_valid is...
   # def form_valid(self, form):
   #     self.object = form.save()
   #     return HttpResponseRedirect(self.get_success_url())

Solution 2

Must return an HttpResponse object. The code below works:

class MyUpdateView(UpdateView):
    def form_valid(self, form):
        instance = form.save(commit=False)
        instance.user = self.request.user
        return super(MyUpdateView, self).form_valid(form)

Solution 3

We can also do like

class MyUpdateView(UpdateView):
    form_class = SomeModelForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super(MyUpdateView, self).form_valid(form)
Share:
10,921
un33k
Author by

un33k

Val Neekman is a Solutions Architect with a passion for Typescript-Angular, Python-Django, UX and Performant Software. He is the Principal Consultant at Neekware Inc.

Updated on July 19, 2022

Comments

  • un33k
    un33k almost 2 years

    In a class-base UpdateView in Django, I exclude the user field as it is internal to the system and I won't ask for it. Now what is the proper Django way of passing the user into the form. (How I do it now, is I pass the user into the init of the form and then override the form's save() method. But I bet that there is a proper way of doing this. Something like a hidden field or things of that nature.

    # models.py
    class Entry(models.Model):
        user = models.ForeignKey(
                    User,
                    related_name="%(class)s",
                    null=False
        )
    
        name = models.CharField(
                    blank=False, 
                    max_length=58,
        )
    
        is_active = models.BooleanField(default=False)
    
        class Meta:
            ordering = ['name',]
    
        def __unicode__(self):
            return u'%s' % self.name
    
    # forms.py
    class EntryForm(forms.ModelForm):
        class Meta:
            model = Entry
            exclude = ('user',)
    
    # views.py
    class UpdateEntry(UpdateView):
        model = Entry
        form_class = EntryForm
        template_name = "entry/entry_update.html"
        success_url = reverse_lazy('entry_update')
    
        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(UpdateEntry, self).dispatch(*args, **kwargs)
    
    # urls.py
    url(r'^entry/edit/(?P<pk>\d+)/$',
        UpdateEntry.as_view(),
        name='entry_update'
    ),
    
  • Bryson
    Bryson about 12 years
    I recommend removing the last two lines (.save() and return) and replacing them with super(MyUpdateView, self).form_valid(form). This will simply execute the default form_valid() (identical to your last two lines) upon your modified form instance, keeping your code in sync with any future changes the Django project makes to the this method.
  • Yuji 'Tomita' Tomita
    Yuji 'Tomita' Tomita about 12 years
    @Bryson, nice! I've actually never done that before. I suppose it's not immediately intuitive that the instance is referenced on form.instance and thus modifying affects the future save() call again.
  • Bryson
    Bryson about 12 years
    Something I missed: instead of using just instance = i believe you should be using form.instance =. This will add the user from the request object (assuming your form was made with this field, otherwise it'll error something like "Form has no attribute 'user'") to the form instance, which will then be passed back through the super(). As it's written write now, the modified form with the user data is saved in a variable called instance that never leaves your version of the form_valid, so because you switched to super() it never gets saved. Sorry it got complicated. haha
  • Yuji 'Tomita' Tomita
    Yuji 'Tomita' Tomita about 12 years
    @Bryson, the returned instance (variable: instance) refers to the same python object as form.instance (not a copy) so it works. I thought that was what was so cool about your solution! I originally thought it wouldn't work either :)
  • Amyth
    Amyth over 10 years
    Just to add, If you use commit=false and your model has many to many fields as well, you'll need to explicitly call the form.save_m2m() to save many to many objects.
  • Peterino
    Peterino over 7 years
    This case is now covered by the Django docs in the "Class-based Views" topic. Note that instead of the decorator you can also use the LoginRequiredMixin from django.contrib.auth.mixins.