If you are in a situation where you have a Django model, and the form created automatically by Django admin is not suitable because you want one or more fields in the form that you don't have in the model, this is the gist for you.
It requires carefully manipulating the interface between the form and the model, the conjoined objects.
Say, you have a model for a Book table that needs to store the year it was published. The database supports a date type (the full date) or the year type is not acceptable (for eg. MySQL limits the year range). So you make do with a date type and hope to handle conversions in code.
So the model in models.py
from django.db import models
class Book(models.Model):
...
pub_date = models.DateField(blank=True, null=True)
and in admin.py
, you have
from django import forms
class BookForm(forms.ModelForm):
pub_year = forms.CharField(max_length=4, required=False, help_text='Enter year in YYYY format')
class Meta:
model = Book
The requirements are two-fold. The form must take existing pub_date
from model and turn it into pub_year
. This is needed
in the change form. The model needs pub_date
from the pub_year
that the user enters. This is needed for the add form to work.
The first requirement is taken care of by overriding the form's __init__()
so that it is populated correctly for the change form.
Exploring the __init__()
of the base class BaseModelForm
, we see two arguments instance
and initial
.
instance
is the very model object to which the form is tied. initial
is a dict
that contains the data that goes in the form.
Under normal circulmstances, the two hold the same data.
But now, we need to substitute pub_date
coming from the model for our pub_year
.
from django.forms import models as forms_models
class BookForm(forms.ModelForm):
pub_year = forms.CharField(max_length=4, required=False, help_text='Enter year in YYYY format')
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
book_instance = kwargs['instance']
book_initial = forms_models.model_to_dict(instance=book_instance, exclude=['pub_date', ])
book_initial['pub_year'] = str(book_instance.pub_date.year)
kwargs['initial'] = source_text_initial
super().__init__(*args, **kwargs)
class Meta:
model = Book
Now for the second part. The user has entered a pub_year
but you need to set the field pub_date
on the model instance.
One place where you can do this is in an overridden clean()
. Add this method to BookForm
def clean(self):
pub_year = self.cleaned_data['pub_year']
self.instance.pub_date = datetime.strptime("{}-01-01".format(pub_year), '%Y-%m-%d')
super().clean()
And all is set!