34

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 ...
lajarre
  • 4,910
  • 6
  • 42
  • 69
  • 4
    I don't quite understand why you can't use an `UpdateView`. Could you post your view code? – jproffitt Feb 06 '14 at 15:20
  • @jproffitt is it clear why I can't use an UpadteView now? – lajarre Feb 11 '14 at 10:49
  • Seems that the answer is in `SingleObjectMixin` (or in stopping to try to use django generic class-based views) – lajarre Feb 11 '14 at 10:50
  • It's not clear. An UpdateView inherits from SingleObjectMixin. It seems that is exactly what you need. It doesn't matter if you are performing a clean on a field. Do you not want the form to be ore filled with the existing data? – jproffitt Feb 11 '14 at 13:00
  • @jproffitt Yes I want the form to be filled with the (cleaned) POST data, but I didn't get how to perform a clean and to define a field as not required if using an `UpdateView`. Could you provide an answer performing this with `UpdateView`? – lajarre Feb 11 '14 at 13:13
  • Possible duplicate of [How to update an object from edit form in Django?](http://stackoverflow.com/questions/4673985/how-to-update-an-object-from-edit-form-in-django) – Ciro Santilli OurBigBook.com May 10 '16 at 07:50
  • 1
    For others that may have this issue: if you find that you're having to add `blank=True, null=True` to get things to validate enough to do post-processing, you might be better off decoupling your model and form, and just using a Form, then creating the model instance manually. Works better for me in many use cases like when you need access to `request.user`. – trpt4him Jul 27 '16 at 15:33

3 Answers3

35

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.

José L. Patiño
  • 3,683
  • 2
  • 29
  • 28
  • 2
    Calling `return super(ContactView, self).form_valid(form)` will involve save() for already saved form object. – Sergey Lyapustin Sep 20 '16 at 13:06
  • Perfect, this is useful so I can avoid putting the users ID in the url! – Adam Starrh Jan 17 '19 at 23:10
  • What happens if you omit `**self.get_form_kwargs()`? For me this line `form_class(instance=contact, **self.get_form_kwargs())` yields some error, saying instance kwarg got multiple values. – harryghgim Jul 13 '21 at 07:46
33

After some discussion with you, I still don't see why you can't use an UpdateView. It seems like a very simple use case if I understand correctly. You have a model that you want to update. And you have a custom form to do cleaning before saving it to that model. Seems like an UpdateView would work just fine. Like this:

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(UpdateView):
    template_name = 'sometemplate.html'
    form_class = SaveForm
    model = SomeModel

    # That should be all you need. If you need to do any more custom stuff 
    # before saving the form, override the `form_valid` method, like this:

    def form_valid(self, form):
        self.object = form.save(commit=False)

        # Do any custom stuff here

        self.object.save()

        return render_to_response(self.template_name, self.get_context_data())

Of course, if I am misunderstanding you, please let me know. You should be able to get this to work though.

jproffitt
  • 6,225
  • 30
  • 42
  • 1
    very fine, I just didnt' get that we could use a form_class attribute in a `UpdateView` – lajarre Feb 17 '14 at 16:35
  • thinking about that again, do you really need the `self.object = form.save(commit=False)`? – lajarre Feb 24 '14 at 17:00
  • No you don't if you aren't going to make any additional changes. You could just do `self.object = form.save()` and take out the `self.object.save()`. Better yet, you could just remove the whole `form_valid` method. I just put it there in case you needed to do any other processing with the object before you save it to the db. – jproffitt Feb 24 '14 at 17:02
  • Right, but I meant: isn't `self.object` already defined before the call to `form_valid`? – lajarre Feb 24 '14 at 17:20
  • It is. but `form_valid` is where the object should be saved from the form. If you look at the `form_valid` method of `ModelFormMixin` (which `UpdateView` inherits from), it does just that: `self.object = form.save()`. So either call `super().form_valid` or do save the form yourself. Like I showed. – jproffitt Feb 24 '14 at 18:39
  • But `BaseUpdateView` is already setting `self.object` in either `post` and `get` actually, so this is not necessary – lajarre Feb 24 '14 at 23:42
  • Yes that is correct, but we need to UPDATE the object from the contents of the form once the form is VALID. That's what that line does. You do not need to override form_valid if u want, but if u do, u need that line or any changes you make on the form will not be saved. – jproffitt Feb 25 '14 at 12:46
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48386/discussion-between-lajarre-and-jproffitt) – lajarre Feb 25 '14 at 16:07
  • This is the best and simpler solution than others – Rufat Apr 02 '23 at 09:16
3

You can use the post method of FormView to get the posted data and save to model using form.save(). Hope this will help.

Try this

    class SaveForm(ModelForm):
    somedata = forms.CharField(required=False)

    class Meta:
        model = SomeModel  # with attr somedata
        fields = ('somedata', 'someotherdata')

    def __init__(self, *args, **kwargs):
        super(SaveForm, self).__init__(*args, **kwargs)

    def save(self, id):
        print id   #this id will be sent from the view
        instance = super(SaveForm, self).save(commit=False)
        instance.save()
        return instance


class SaveView(FormView):
    template_name = 'sometemplate.html'
    form_class = SaveForm

    def post(self, request, *args, **kwargs):

        form = self.form_class(request.POST)

        if form.is_valid():
            form.save(kwargs.get('pk'))
        else:
            return self.form_invalid(form)
Shanki
  • 167
  • 3