Adding extra fields to a model form in Django’s admin

By on February 11th, 2010 in Python Comments (23)

I ran into this problem recently and found that the Django documentation wasn’t exactly clear on how to solve it, so I’ve provided an example in case anyone else needs to do the same thing. My goal was to add an extra form field to one of my model objects that represented a property which was not a Django model field. The catch is that Django’s ModelForm only includes Django model fields by default, so we have to overwrite it to manually handle our special fields.

If that doesn’t make sense, perhaps this example will help:

# models.py
from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
 
class Location(models.Model):
    name = models.CharField(max_length=128)
    # GeoDjango fields
    coordinate = models.PointField(null=True, blank=True)
    objects = models.GeoManager()
 
    # Custom latitude property
    def _get_latitude(self): return self.coordinates.y
    def _set_latitude(self, value): self.coordinate.y = value
    latitude = property(_get_latitude,_set_latitude)
 
    # Custom longitude property
    def _get_longitude(self): return self.coordinate.x
    def _set_longitude(self, value): self.coordinate.x = value
    longitude = property(_get_longitude,_set_longitude)

So I’ve got a Location object which represents some physical place in the world, and I’m using GeoDjango to store the actual GPS coordinates of the Location as a PointField. Using GeoDjango to store these coordinates lets me do some pretty neat things like geospatial queries (e.g. find all Locations within 5 miles of this coordinate). GeoDjango’s admin generators also automatically provide a nice drag and drop AJAX map that you can use to edit the Location. But it might also be convenient to be able to directly edit the latitude/longitude of the location using text inputs instead of the map, and the interface for the Location object will also be a little cleaner if I hide away the details of the PointField and simply expose the latitude and longitude as properties.

Since my custom properties are not Django fields, they will not show up in the Django admin panel. So let’s override the LocationForm to make them show up:

# admin.py
from myproject.models import Location
from django.contrib.gis import admin
from django import forms
 
# Custom form definition
class LocationAdminForm(forms.ModelForm):
    # Step 1: Add the extra form fields to the ModelForm
    latitude = forms.FloatField(required=False)
    longitude = forms.FloatField(required=False)
 
    class Meta:
        model = Location
 
    # Step 2: Override the constructor to manually set the form's latitude and
    # longitude fields if a Location instance is passed into the form
    def __init__(self, *args, **kwargs):
        super(LocationAdminForm, self).__init__(*args, **kwargs)
 
        # Set the form fields based on the model object
        if kwargs.has_key('instance'):
            instance = kwargs['instance']
            self.initial['latitude'] = instance.latitude
            self.initial['longitude'] = instance.longitude 
 
    # Step 3: Override the save method to manually set the model's latitude and
    # longitude properties based on what was submitted from the form
    def save(self, commit=True):
        model = super(LocationAdminForm, self).save(commit=False)
 
        # Save the latitude and longitude based on the form fields
        model.latitude = self.cleaned_data['latitude']
        model.longitude = self.cleaned_data['longitude']
 
        if commit:
            model.save()
 
        return model
 
# Custom admin definition
# I'm using admin.GeoModelAdmin because I'm using GeoDjango. Usually
# you would be subclassing admin.ModelAdmin.
class LocationAdmin(admin.GeoModelAdmin):
    form = LocationAdminForm
 
# Register admin
admin.site.register(Location, LocationAdmin)

In summary, the steps to add extra fields to a ModelForm that aren’t in that model’s definition:

  1. Add the extra form fields to the ModelForm definition.
  2. Override the constructor of the ModelForm, set the self.initial properties if a model instance was passed to the form.
  3. Override the save method of the ModelForm, set the properties on the model instance based on the form fields.

Hope this helps!


  • AK

    I’ve been looking for this exact solution for days and here it is, posted the day before I was about to give up. Awesome. :)

  • http://www.postparrot.com Matthew C. Kriner

    Thanks for your article. I am new at django and this was a big help.

  • Simon Gray

    Outstanding – been looking to do this and you’ve just solved all my problems :o )

    One point worth noting for anybody extending this: if you use a forms.BooleanField(), remember to set it to required=False if you want to see it in cleaned_data, otherwise it won’t show up as its validation is handled by Django.
    Caught myself out in the glee of finding this above solution :-D

  • Christine Meranda

    Thanks for the additional info, glad we could help!

  • Steve Mulligan

    Thank you so much, this really saved me a lot of time.

  • jinsei

    Thanks, you saved me *tons* of time!

  • http://davidqhogan.com David Hogan

    Very useful post, thank you.

  • Derek

    Great stuff! Thanks.

    Just one question; how and where do you control the position of the new field (in my case, its added to a tabular inline form in the Admin)?

  • powderflask

    Thanks – very instructive and just what I was looking for.

    One tip – in my case I have a field that stores a data structure (stored as a picklefield). The form field needs to be a TextField – a serialized representation of the data structure.

    I am overriding the form’s __init__() to serialize the data structure to the TextField. Sweet. But a better choice for parsing the text back into the data structure is to define the clean_foo() method. This method parses and validates the input text and returns the de-serialized data structure, which gets saved in the model. No need to override the form’s save() method in this case.

    hope that helps someone.

  • powderflask

    Oops. I lied.
    clean_foo() is the right place to de-serialize the form data, but still need to override save() to save it!! 8-P

  • Filip Dupanović

    Great post!

    I just wanted to bump the thread to not that Django will only use those properties that are instances of `models.Field`. It actually uses the class’ `_meta` object to get all the model fields. It makes perfect sense because it cannot introspect your custom model properties (they don’t provide a information about their database representation, form widget, validation, etc.)

    Additionally, another point the docs fail to make is how custom `ModelForm` fields are handled. When the form is saved/validated, form fields whose names aren’t part of the model’s fields do not get stored/validated on the model.

  • http://www.facebook.com/mihneasim Mihnea Simian

    Great info, excellent instructions! Thanks!

  • http://profiles.google.com/guandalino dave guandalino

    Awesome, thanks!

  • Ismael Solis

    Many thanks for this valuable article, it took me a while to find it, but I’m glad I did.

    Really can’t thank-/a>you enough !!

  • Leah Madison

    This is a fantastic site – if you are interested in graffiti and street art, please check out Big Street Art 
    Banksy Prints Many thanks!

  • http://www.bigstreetart.com Matt Harper

    For graffiti and Banksy prints – please check out Big Street Art at 
    Banksy Prints thanks

  • Leah madison

    After you are interested in a relaxing massage in Coleraine. Please contact Healing Hands for the best Massage in Coleraine.
    Many thanks! 

  • http://banksyart.org/banksy-prints.html Banksy Prints – The Best Prints, Posters & Shirts

    [...] Banksy prints are created using street art graffiti stencils.  There are many Online Graffiti Websites and street art exhibitions that sell Banksy prints. Click Here To View Top Rated Sellers Of Banksy Prints and Street Art At The Best Prices Banksy prints have a high price and often depict urban graffiti art scenes that were previously painted on a wall by the street artist. Banksy does not sell any of his graffiti street art himself due to his anonymity. Instead he has a manager whom organizes sales of Banksy prints and canvas prints through websites, exhibitions and auctions. There are 100's of Banksy prints to choose from, ranging form moderately priced to vastly expensive. The value of the graffiti prints is often high especially if signed by the street artist. Many of the prints have a political, urban or social theme, often featuring a rat or monkey, such as the Banksy Gangsta Rat 2004 print. For those who cannot afford original Banksy prints there are replica prints available. Banksy prints can be bought through online sites such as Ebay and Amazon whose websites used to offer prints and Banksy stencils at a very reasonable prices. before Banksy received so much media attention and his street art graffiti pieces soared in popularity. Now it is difficult to find a piece of Banksy art from a shop or Urban Art Gallery at a price that an average person can afford. If you search around you can find signed original Banksy prints. [amazon_carousel widget_type="SearchAndAdd" width="600" height="220" title="" market_place="" shuffle_products="True" show_border="True" keywords="banksy print" browse_node="" search_index="All" /]Here are top suppliers of Banksy Prints, Graffiti and Street Art with the best prices anywhere."[/phpbay] [...]

  • Crc32

    I followed the example and I’m gessing this doesn’t work for django 1.3
    I’m still getting ‘LbaasManagementCredAdmin.fields’ refers to field ‘passwd’ that is missing from the form.
    Basically I’m trying to do encryption during datbase storage. I can use the properties just fine when working with the models directly I just can’t get the django amin panel to display this field.

  • Leah Madison

    This is one of the best blogs I’ve read on headphones. Thanks again.

    Dr Dre Headphones

  • Anonymous

    Thanks for the tip, was very useful!

  • Brian Buck

    This really helped me out on a calculated field that just needed to be shown but not edited. Thanks!

  • http://meitham.com Meitham

    Thank you. This is very helpful.