simplest way to override Django admin inline to request formfield_for_dbfield for each instance
<p>I would like to provide different widgets to input form fields for the same type of model field in a Django admin inline.</p>

<p>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.</p>

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

<pre><code>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 --&gt;
        sv_instance = SpecificationValue.objects.filter(
            product=self.parent_instance).first()
        formfield = sv_instance.parameter.get_value_formfield()
    else:
        formfield = super().formfield_for_dbfield(db_field, **kwargs)
    return formfield
</code></pre>

<p>formfield_for_dbfield is only called once for each admin page.</p>

<p>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?</p>

<p>Edit:</p>

<p>Here is the model layout:</p>

<pre><code>class Product(Model):
    specification = ManyToManyField('SpecificationParameter',
        through='SpecificationValue')

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()
</code></pre>

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

<pre><code>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()
        else:
            self.fields['value'].disabled = True

class SpecificationValueAdminInline(admin.TabularInline):
    form = SpecificationValueForm
</code></pre>

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

<p>Looks like I'll have to live with that!</p>

python django django-admin