Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django - update model with FormView and ModelForm

I can't figure out how to use a ModelForm in a FormView so that it updates an already existing instance??

The form POSTs on this URL: r'/object/(?P<pk>)/'

I use a ModelForm (and not directly an UpdateView) because one of the fields is required and I perform a clean on it.

I'd basically like to provide the kwarg instance=... when initializing the form in the FormView (at POST) so that it's bound to the object whose pk is given in the url. But I can't figure out where to do that...

class SaveForm(ModelForm):     somedata = forms.CharField(required=False)     class Meta:         model = SomeModel  # with attr somedata         fields = ('somedata', 'someotherdata')     def clean_somedata(self):         return sometransformation(self.cleaned_data['somedata'])  class SaveView(FormView):     form_class = SaveForm     def form_valid(self, form):         # form.instance here would be == SomeModel.objects.get(pk=pk_from_kwargs)         form.instance.save()         return ... 
like image 469
lajarre Avatar asked Feb 06 '14 15:02

lajarre


1 Answers

For any further visitors of this thread, yes, you can make a FormView that acts like both a CreateView and an UpdateView. This, despite some opinions of other users, can make a lot of sense if you want to have a single form/URL/page for a web form to save some user data which can be optional but needs to be saved once and only once. You don't want to have 2 URLs/views for this, but just only one page/URL which shows a form, filled with previous data to be updated if a model was already saved by the user.

Think in a kind of "contact" model like this one:

from django.conf import settings from django.db import models   class Contact(models.Model):     """     Contact details for a customer user.     """     user = models.OneToOneField(settings.AUTH_USER_MODEL)     street = models.CharField(max_length=100, blank=True)     number = models.CharField(max_length=5, blank=True)     postal_code = models.CharField(max_length=7, blank=True)     city = models.CharField(max_length=50, blank=True)     phone = models.CharField(max_length=15)     alternative_email = models.CharField(max_length=254) 

So, you write a ModelForm for it, like this:

from django import forms  from .models import Contact   class ContactForm(forms.ModelForm):     class Meta:         model = Contact         exclude = ('user',)  # We'll set the user later. 

And your FormView with both "create" and "update" capabilities will look like this:

from django.core.urlresolvers import reverse from django.views.generic.edit import FormView  from .forms import ContactForm from .models import Contact   class ContactView(FormView):     template_name = 'contact.html'     form_class = ContactForm     success_url = reverse('MY_URL_TO_REDIRECT')      def get_form(self, form_class):         """         Check if the user already saved contact details. If so, then show         the form populated with those details, to let user change them.         """         try:             contact = Contact.objects.get(user=self.request.user)             return form_class(instance=contact, **self.get_form_kwargs())         except Contact.DoesNotExist:             return form_class(**self.get_form_kwargs())      def form_valid(self, form):         form.instance.user = self.request.user         form.save()         return super(ContactView, self).form_valid(form) 

You don't even need to use a pk in the URL of this example, because the object is retrieved from the DB via the user one-to-one field. If you have a case similar than this, in which the model to be created/updated has a unique relationship with the user, it is very easy.

Hope this helps somebody...

Cheers.

like image 161
José L. Patiño Avatar answered Sep 21 '22 00:09

José L. Patiño