'How to add a calculated field to a django admin inline

I am trying to add a calculated field to a Django Admin Inline. Consider this set of classes:

models.py:

class MyGroup(models.Model):
  group_name = models.CharField()
  size = models.IntegerField()

class MyUser(models.Model):
  user_name = models.CharField()
  groups = models.ManyToMany(MyGroup, related_name="users", through=MyMembership)
    
class MyMembership(models.Model):
  group = models.ForeignKey(MyGroup)
  user = models.ForeignKey(MyUser)
  timestamp = models.DateTimeField(auto_add_now=True)

I want to show the group.size field in the inline. I thought the following code would work (based on this answer):

admin.py:


class MyMembershipInline(admin.TabularInline):
  model = MyUser.groups.through
  fields = (
    "group",
    "timestamp",
    "size",
  )
  readonly_fields = (
    "timestamp",
    "size",
  )

  def size(self, instance):
    return instance.group.size

@admin.register(MyUser):
class MyUserAdmin(admin.ModelAdmin)
  fields = ("user_name",)
  inlines = (MyMembershipInline,)

But I am getting the following error:

Unknown field(s) (size) specified for MyMembership

Any advice?



Solution 1:[1]

I wound up having to use a custom ModelForm in the inline. I did it like this:

admin.py:

class MyMembershipInlineForm(forms.ModelForm):
  class Meta:
    model = MyUser.groups.through
    fields = ("group", )
    readonly_fields = (
      "timestamp",
      "size",
    )
  size = forms.IntegerField(disabled=True)

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    my_membership = self.instance
    self.initial = {
      "size": my_membership.group.size if my_membership.pk else None
    }

class MyMembershipInline(admin.TabularInline):
  model = MyUser.groups.through
  fields = (
    "group",
    "timestamp",
    "size",
  )
  form = MyMembershipInlineForm

@admin.register(MyUser):
class MyUserAdmin(admin.ModelAdmin)
  fields = ("user_name",)
  inlines = (MyMembershipInline,)

Solution 2:[2]

While looking for a solution to this problem, I found a solution that worked for me at the following URL: https://www.boris.co/2013/04/computed-field-on-tabularinline.html

The example given is:

class StatsInline(admin.TabularInline):
    model = Stats
    fields = ('clicked', 'shown', 'avg')
    readonly_fields = ('avg',)
    verbose_name = 'Stats'
    verbose_name_plural = 'Stats'
    can_delete = False

    def avg(self, obj):
        return float(obj.clicked) / obj.shown if obj.shown else 0

The calculated field needs to be provided in both the fields and readonly_fields attributes.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 trubliphone
Solution 2 spble