Forms

Prefer ModelForm to Form

ModelForm already know the correct UI widgets for your underlying Models. In most of the cases ModelForm would suffice instead of Forms.

Some common scenarios

Hiding some fields from ModelForm which are needed for a DB save.

Eg, you want to create a profile for the logged in user.:

#in models.py
class Profile(models.Model):
    user = models.OneToOneField(User)
    company = models.CharField(max_length=50)

#in forms.py
class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['company',]

#In views.py:
form = ProfileForm(request.POST)
profile = form.save(commit = False)
profile.user = request.user
profile.save()

Or:

class ProfileForm(forms.ModelForm):

    class Meta:
        model = Profile
        fields =['company',]

    def __init__(self, user, *args, **kwargs)
        self.user = user
        super(ProfileForm, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.instance.user = self.user
        super(ProfileForm, self).save(*args, **kwargs)

Customizing widgets in ModelForm fields

Sometimes you just need to override the widget of a field that’s already on your ModelForm. Instead of duplicating the field definition (with help_text, required, max_length, etc). You can do this:

from django.contrib.admin.widgets import AdminFileWidget

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['picture', 'company']

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        # note that self.fields is available just after calling super's __init__
        self.fields['picture'].widget = AdminFileWidget()

Saving multiple Objects in one form

As:

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['company',]

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = [...]

#in views.py
userform = UserForm(request.POST)
profileform =  ProfileForm(request.POST)
if userform.is_valid() and profileform.is_valid():
    #Only if both are valid together
    user = userform.save()
    profile = profileform.save(commit = False)
    profile.user = user
    profile.save()

{# In templates #}
<form ...>
{{ userform }}
{{ profileform }}
<input type="submit" />
</form>

Forms should know how to save themselves.

If your forms is a forms.ModelForm, it already knows how to save its data. If you write a forms.Form, it should have a .save(). This keeps things symmetrical with ModelForms, and allows you to do:

#in views.py
def view_func(request):
    if request.method == 'POST':
        form  = FormClass(request.POST)
        if form.is_valid():
            obj = form.save()
            ...
        ...

Instead of:

if form.is_valid():
    #handle the saving in DB inside of views.

The .save() should return a Model Object

The form should know what to do with it’s data

If you’re building a contact form, or something like this, the goal of your form is to send an email. So this logic should stay in the form:

class ContactForm(forms.Form):
    subject = forms.CharField(...)
    message = forms.TextField(...)
    email = forms.EmailField(...)
    ...

    def save(self):
        mail_admins(self.cleaned_data['subject'], self.cleaned_data['message'])

I’ve used save(), and not send(), even when i’m not really saving anything. This is just a convention, people prefer to use save() to keep the same interface to ModelForms. But it doesn’t really matter, call it whatever you want.