Skip to content

Instantly share code, notes, and snippets.

@ShreyasKolpe
Last active May 18, 2022 07:21
Show Gist options
  • Save ShreyasKolpe/d5d7271d9fb3b27ce0abe76d3bf7f0bd to your computer and use it in GitHub Desktop.
Save ShreyasKolpe/d5d7271d9fb3b27ce0abe76d3bf7f0bd to your computer and use it in GitHub Desktop.
Adding and handling a field to Django ModelForm that is not part of your Model

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment