views:

394

answers:

3

How do I set the value of a field (which is relevant to form, but not based directly on the same model) on a (model) form when it is displayed in the admin?

Here is a simplified version of what I have (my actual app/model/etc is more complicated):

  • A building has many rooms
  • A room has many pieces of equipment

Models:

#spaces/models.py
from django.db import models    

class Building(models.Model):
    name=models.CharField(max_length=32)
    def __unicode__(self):
      return self.name

class Room(models.Model):
    number=models.CharField(max_length=8)
    building=models.ForeignKey(Building)
    def __unicode__(self):
        return self.number

class Equipment(models.Model):
    tech = models.CharField(max_length=64)
    room = models.ForeignKey(Room)
    def __unicode__(self):
     return self.tech

When adding or editing a piece of equipment, it would be nice to be able to limit the room choices down (by building). This I can do: I add a building field to the ModelForm, and then as it gets changed by the user, a bit of JQuery in the template file dynamically updates the list of rooms. The admin.py part looks like:

#spaces/admin.py
from demo.spaces.models import Building, Room, Equipment
from django.contrib import admin
from django import forms


class EquipmentForm(forms.ModelForm):
     class Meta:
      model = Equipment
     building = forms.ModelChoiceField(Building.objects)

class EquipmentAdmin(admin.ModelAdmin):
     form = EquipmentForm

admin.site.register(Equipment, EquipmentAdmin)
admin.site.register(Building)
admin.site.register(Room)

When adding a new piece of equipment, this all works fine--my JQuery code displays ----- until a building is selected, at which point the drop down for the room field is enabled and populated with the appropriate rooms for that building.

The problem is: when displaying an existing piece of equipment (demo.com/admin/spaces/equipment/12/) or if the add form doesn't validate, the Room field properly displays the room --but the building field is wiped out. The equipment.room foreign key value exists in the form instance and the admin automagically takes care of setting the room field to that value, but the value of the building field needs to be set by me.

I can get the value without a problem, since it's easy in the init of the form to check for it being an instance, and then traversing back up to get the value:

def __init__(self, *args, **kwargs):  
     super(EquipmentForm, self).__init__(*args, **kwargs)  
     if 'instance' in kwargs:
        building_value = kwargs['instance'].room.building

But what do I do with that value to make the field display it? The only thing I haven't tried yet is adding more code in my template to use Jquery to get the value and set it at the end, but I'd rather not have to have the javascript in the page hit the database to figure display the value, since it seems like something that should be done when the form is rendered...

Any thoughts? Thank you for you help!

+1  A: 

Check the initial parameter of the Field in forms here. Maybe setting building with initial set to a Building object would help.


Also, do you really need to limit choices down to a building? I'd just found sufficient to modify the Room's __unicode__ method, so it shows both Building and Room number:

def __unicode__(self):
    return "Room nr %s in %s" % (self.number, self.building)

Then you'll get a list of all rooms, but easy to find out to which building they belong to...

kender
I'd much rather be able to limit the the choices for the building, since A) There will be a couple hundred rooms, and B) I want to use the same procedure for limiting other foreign keys in other places.
mightyhal
The field.initial won't help, since the value I want to set for the building is dynamic (depends on the instance). Form.initial looks promising, setting self.initial['building'] in __init__, and a print self.initial spits back a dictionary of all the fields and their values... but when it renders, it doesn't set the value for building (though it looks like it should)
mightyhal
A: 

What about this?

def __init__(self, *args, **kwargs):  
 super(EquipmentForm, self).__init__(*args, **kwargs)  
 if 'instance' in kwargs:
    self.data['building']=kwargs['instance'].room.building
czarchaic
modifying self.data['building'] in the __init__ has no effect. (dropping a print self.data in the __init__ gives me a a {}, so maybe I need to override something besides __init__ to affect the self.data, or self.data isn't the right thing to modify)
mightyhal
A: 

This works for me:

def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None):
        if instance:
            initial = initial or {}
            initial['field_name'] = instance.some_value

        super(MyCustomForm, self).__init__(data, files, auto_id, prefix,initial,
                error_class, label_suffix,empty_permitted, instance)

I intentionally don't use *args, **kwargs cause I didn't check how this constructor is called elswere in the code (instance and initial could be in *args or **kwargs).

Łukasz Korzybski