Last Updated: February 25, 2016
· egasimus

Make your ModelForms aware of whether they're part of a CreateView or an UpdateView

Even though they're usually part of a View, Django's Forms are, by default, not aware of the context in which they're being used. This makes perfect sense, in harmony with the principle of loose coupling: a Form needs to take care of data validation, as well as rendering (via Widgets), and nothing else.

It is often the case, however, that I need a ModelForm to act a little differently whether it's brought up as part of a CreateView or an UpdateView. Let's look at a quick example - let's say we're creating a model (say, an Appointment) that has a ForeignKey relation to an User - and we only want to have active users shown in our ModelChoiceField. However, when you're editing an existing Appointment that belongs to an inactive user, you obviously need to have that user in the queryset too, or the form won't validate. What do we do?

One way of implementing different behavior for forms is to just use different forms; yet another - to make the form's constructor accept an is_createview keyword argument, and add that to the dict that is returned by a CreateView's get_form_kwargs. Both ways require that you write more code than is necessary, especially when you can simply do:

class AppointmentForm(ModelForm):
    def __init__(self, *args, **kwargs):
        if self.instance.pk is None:  # it's a CreateView
            self.fields['user'].queryset = ...  # active users
        else:  # it's an UpdateView:
            self.fields['user'].queryset = ...  # active users + self.instance.user

instance is a (very useful) class attribute of ModelForms, which holds an instance of your model class (i.e. Appointment). In the context of a CreateView, instance isn't saved to the DB yet, therefore its pk is None. Simple as that!