racitup racitup - 1 year ago 130
Python Question

simplest way to override Django admin inline to request formfield_for_dbfield for each instance

I would like to provide different widgets to input form fields for the same type of model field in a Django admin inline.

I have implemented a version of the Entity-Attribute-Value paradigm in my shop application (I tried eav-django and it wasn't flexible enough). In my model it is Product-Parameter-Value (see Edit below).
Everything works as I want except that when including an admin inline for the Parameter-Value pair, the same input formfield is used for every value. I understand that this is the default Django admin behaviour because it uses the same formset for each Inline row.

I have a callback on my Parameter that I would like to use (get_value_formfield). I currently have:

class SpecificationValueAdminInline(admin.TabularInline):
model = SpecificationValue
fields = ('parameter', 'value')
readonly_fields = ('parameter',)
max_num = 0

def get_formset(self, request, instance, **kwargs):
"""Take a copy of the instance"""
self.parent_instance = instance
return super().get_formset(request, instance, **kwargs)

def formfield_for_dbfield(self, db_field, **kwargs):
"""Override admin function for requesting the formfield"""
if self.parent_instance and db_field.name == 'value':

# Notice first() on the end -->
sv_instance = SpecificationValue.objects.filter(
formfield = sv_instance.parameter.get_value_formfield()
formfield = super().formfield_for_dbfield(db_field, **kwargs)
return formfield

formfield_for_dbfield is only called once for each admin page.

How would I override the default behaviour so that formfield_for_dbfield is called once for each SpecificationValue instance, preferably passing the instance in each time?


Here is the model layout:

class Product(Model):
specification = ManyToManyField('SpecificationParameter',

class SpecificationParameter(Model):
"""Other normal model fields here"""
type = models.PositiveSmallIntegerField(choices=TUPLE)

def get_value_formfield(self):
Return the type of form field for parameter instance
with the correct widget for the value

class SpecificationValue(Model):
product = ForeignKey(Product)
parameter = ForeignKey(SpecificationParameter)
# To store and retrieve all types of value, overrides CharField
value = CustomValueField()

Answer Source

The way I eventually solved this is using the form = attribute of the Admin Inline. This skips the form generation code of the ModelAdmin:

class SpecificationValueForm(ModelForm):
    class Meta:
        model = SpecificationValue

    def __init__(self, instance=None, **kwargs):
        super().__init__(instance=instance, **kwargs)
        if instance:
            self.fields['value'] = instance.parameter.get_value_formfield()
            self.fields['value'].disabled = True

class SpecificationValueAdminInline(admin.TabularInline):
    form = SpecificationValueForm

Using standard forms like this, widgets with choices (e.g. RadioSelect and CheckboxSelectMultiple) have list bullets next to them in the admin interface because the <ul> doesn't have the radiolist class. You can almost fix the RadioSelect by using AdminRadioSelect(attrs={'class': 'radiolist'}) but there isn't an admin version of the CheckboxSelectMultiple so I preferred consistency. Also there is an aligned class missing from the <fieldset> wrapper element.

Looks like I'll have to live with that!