How to get the sum of inlines in Django Admin

I want to show in admin the total money used in a computer by adding inline parts.

models.py

class Material(models.Model):
  name = models.CharField(max_length=30, null=False, blank=False)

class Computer(models.Model):
  name = models.CharField(max_length=90)

class UsedMaterial(models.Model):
  computer = models.ForeignKey(Computer, on_delete=models.SET_NULL, null=True)
  material = models.ForeignKey(Material, on_delete=models.SET_NULL, null=True)
  cost = models.DecimalField(max_digits=11, decimal_places=2, null=False, blank=False, default=0)
  quantity = models.IntegerField(null=False, blank=False, default=0)

admin.py

class UsedMaterialAdmin(admin.TabularInline):
  model = UsedMaterial
  def total(self, obj):
    return obj.cost * obj.quantity
  readonly_fields = ("total",)
  fields = ("material", "cost", "quantity", "total" )

@admin.register(Computer)
class ComputerAdmin(admin.ModelAdmin):
  inlines = [UsedMaterialInline]
  fields = ("name",)

I have found the way to show the total per article in the inline as you can see show Screen shot But how do I get the total of all the materials in the inline?

I have a solution, I believe you can fetch the information you need. This way, you can see the total number of items (if you modify it, the total cost of items), and have it written out to the list_display of the profiles.

Since it's an 'in-progress' project of mine, I didn't bother changing too much of it, so I commented and explained as well as I could. English is not my first language, so if something is hard to understand, feel free to ask down below! I tested everything, it works just fine and might be a good solution until you find a way to have it inside your model.

edit: If you want to have 1 item for each inventory (to avoid having duplicate items (example)

item qty
car_license 1
car_license 1

last row is 'accidental'

This will be accepted, but if you want to make them unique (example)

item qty
car_license 1

Then using the Meta down below will help you throw an error if you accidentally specified the same item multiple times.

include this in your Inventory model

class Meta:
        unique_together = ('profile', 'item',) # so you can have multiple profiles with multiple items, but you can't have the same item listed 73 times

Rest:

My admin.py

from django.contrib import admin
from core.models import Profile, Item, Achievement, Inventory
# Register your models here.

# specify the item inline
class InventoryInline(admin.TabularInline):
    model = Inventory # which remember, has fields called "profile" (Profile), "item" (item) and "quantity" (quantity).
    extra = 0 # just so you can add as many as you want, without removing pre-added ones.
    fields = ("item", "quantity",) # You want to be able to specify the 'item' and the 'quantity', the Profile will be selected by default.

class ProfileAdmin(admin.ModelAdmin):
    list_display = ('user', 'total_items',) # list_display can call functions too, so we're good to go
    inlines = [InventoryInline] # here you specify the inline(s)
    filter_horizontal = ['achievements'] # 'items', ignore this comment
    raw_id_fields = ['user'] # just so you get a pop-up window instead of a dropdown, it's easier to use in my opinion.

    def total_items(self, obj):
        items = Inventory.objects.filter(profile=obj) # query the inventory (which had 'profile' and 'items' linked to eachother and had a quantity)
        qty = 0 # This is going to be the value we return
        for item in items: # we iterate through the queryset we got
            qty += item.quantity # we add the quantity of it to the qty variable. If you want to get the sum of costs, you could just do 'item.quantity * item.cost', or make a read-only field that has the value already.
        return qty
    
    total_items.short_description = 'Total items' # This is going to be the name of the column.

admin.site.register(Profile, ProfileAdmin) # register so you can access the Profile model as you specified it above.
admin.site.register(Item) # register so you can make items from the admin
admin.site.register(Achievement) # ignore
admin.site.register(Inventory) # if you want to see all the items, register this one too

and

My models.py

from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
# Create your models here.

# Items model, here you can make unique items.
class Item(models.Model):
    name = models.CharField(max_length=32, blank=False, unique=True)
    price = models.IntegerField(validators=[MinValueValidator(0)], default=0, help_text="0 means Free")

    # IGNORE THIS
    # inv = models.ForeignKey(Profile, on_delete=models.CASCADE, blank=False, verbose_name="Item", null=True)
    # IGNORE THIS

    # Including this you can return the object's name, instead of "Item Object (1)".
    def __str__(self) -> str:
        return "%s" %(self.name)

# Here you can make achievements with the mentioned method.
class Achievement(models.Model):
    name = models.CharField(max_length=32, blank=False, unique=True)

    def __str__(self) -> str:
        return "%s" %(self.name)

# Here you can make inventories, linkind one Profile to one user.
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, blank=False, verbose_name="Belongs to")
    achievements = models.ManyToManyField(Achievement, blank=True)

    # IGNORE THIS
    # items = models.ManyToManyField(Item, blank=True)
    # IGNORE THIS

    def __str__(self) -> str:
        return "Profile of %s" %(self.user)

# You need to register a new model where you store the items you give in the inventories.
class Inventory(models.Model):
    profile = models.ForeignKey(Profile, on_delete=models.SET_NULL, null=True) # to which Profile you want to link the item
    item = models.ForeignKey(Item, on_delete=models.SET_NULL, null=True) # which item
    quantity = models.IntegerField(null=False, blank=False, default=0, validators=[MinValueValidator(0)]) # how many of it

    def __str__(self) -> str:
        return "%s's %s of %s" %(self.profile.user, self.item, self.quantity) # This is just so you'll see the user, the given item and quantity better instead of just "Inventory Item (1)", etc...
Back to Top