SyntaxStudy
Sign Up
Django ModelForms and Saving to the Database
Django Beginner 1 min read

ModelForms and Saving to the Database

ModelForm is a form class that is automatically generated from a Django model. Instead of manually defining each field you specify the model and the fields you want to include in the Meta class. Django introspects the model and creates the corresponding form fields with appropriate validation. This dramatically reduces boilerplate when building forms that map directly to database records. The save() method on a ModelForm either creates a new model instance or updates an existing one. Passing commit=False returns the model instance without saving it to the database, allowing you to modify attributes (such as setting the author field from request.user) before calling instance.save(). After calling save(commit=False) you must also call form.save_m2m() to save any many-to-many relationships. ModelForms can be further customised using the Meta class. The fields attribute lists which model fields to include, or you can use exclude to list fields to omit. The widgets, labels, help_texts, and error_messages attributes let you override the defaults for specific fields. You can also add form-only fields not present in the model, which are available in cleaned_data but not automatically saved by save().
Example
# blog/forms.py — ModelForm
from django import forms
from .models import Post, Tag


class PostModelForm(forms.ModelForm):
    # Extra form-only field (not in the model)
    tag_names = forms.CharField(
        required=False,
        help_text='Comma-separated tags (e.g. python, django).',
        widget=forms.TextInput(attrs={'placeholder': 'python, django, web'}),
    )

    class Meta:
        model   = Post
        fields  = ['title', 'body', 'category', 'status']
        widgets = {
            'title':  forms.TextInput(attrs={'class': 'form-control'}),
            'body':   forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
            'status': forms.Select(attrs={'class': 'form-select'}),
        }
        labels  = {'body': 'Content'}

    def save(self, commit=True):
        post = super().save(commit=False)
        if commit:
            post.save()
            # Handle the extra tag_names field
            tag_names = self.cleaned_data.get('tag_names', '')
            if tag_names:
                for name in [t.strip() for t in tag_names.split(',') if t.strip()]:
                    tag, _ = Tag.objects.get_or_create(
                        name=name,
                        defaults={'slug': name.lower().replace(' ', '-')},
                    )
                    post.tags.add(tag)
            self.save_m2m()
        return post