简体   繁体   中英

Multiple annotate with sum and display data in admin - Django

I'm new to both Django and Python. Currently I'm trying the Django Admin by doing.

I've three models for a Django app, which are GoodsItem , SoldGoodsItem and FinishedGoodsItem . The models.py is:

from django.db import models


class GoodsItem(models.Model):
    name = models.CharField(max_length=255)
    size = models.DecimalField(max_digits=4, decimal_places=2)
    INCHES = 'IN'
    NUMBER = 'NUM'
    GOODS_ITEM_SIZE_UNITS = (
        (INCHES, 'Inches'),
        (NUMBER, '#'),
    )
    size_unit = models.CharField(
        max_length=4,
        choices=GOODS_ITEM_SIZE_UNITS,
        default=INCHES,
    )

    def __str__(self):
        if(self.size_unit == self.NUMBER):
            return "%s #%s" % (self.name, (self.size).normalize())
        else:
            return "%s %s\"" % (self.name, (self.size).normalize())


class FinishedGoodsItem(models.Model):
    date = models.DateField()
    goods_item = models.ForeignKey(GoodsItem, on_delete=models.CASCADE, related_name="finished_name")
    weight = models.DecimalField(max_digits=6, decimal_places=3)

    def __str__(self):
        return str(self.goods_item)


class SoldGoodsItem(models.Model):
    goods_item = models.ForeignKey(GoodsItem, on_delete=models.CASCADE, related_name="sold_name")
    date = models.DateField()
    weight = models.DecimalField(max_digits=6, decimal_places=3)

    def __str__(self):
        return str(self.goods_item)

And here is admin.py:

from django.contrib import admin
from django.db.models import Sum

from .models import GoodsItem, FinishedGoodsItem, SoldGoodsItem

@admin.register(SoldGoodsItem)
@admin.register(FinishedGoodsItem)
class FinishedGoodsItemAdmin(admin.ModelAdmin):
    fields = ('date', 'goods_item', 'weight')
    list_display = ('date', 'goods_item', 'weight')

@admin.register(GoodsItem)
class GoodsItemAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'finished_good', 'sold_good', 'stock_available')

    def get_queryset(self, request):
        qs = super(GoodsItemAdmin, self).get_queryset(request)
        qs = qs.annotate(
            finished_good=Sum('finished_name__weight'),
            sold_good=Sum('sold_name__weight'),
            stock_available=Sum('finished_name__weight') - Sum('sold_name__weight'),
        )
        return qs

    def finished_good(self, obj):
        return obj.finished_good

    def sold_good(self, obj):
        return obj.sold_good

    def stock_available(self, obj):
        return obj.stock_available

In stock_available for each GoodsItem , I want to display the difference between all entries of FinishedGoodsItem and all entries of SoldGoodsItem . For now, I'm getting incorrect value for all three annotated fields which are finished_good , sold_good and stock_available . I'm unable to find the reason for that. In Django Debug Toolbar suggest that duplicate queries are being executed.

This is known issue and occurs when we try to combine multiple aggregation, as mentioned in docs .

As a workaround for this particular problem, we can use Subquery expression. Here is my updated admin.py using Subquery expression in get_queryset method of GoodsItemAdmin .

from django.contrib import admin
from django.db.models import Subquery, Sum, OuterRef

from .models import GoodsItem, FinishedGoodsItem, SoldGoodsItem

@admin.register(SoldGoodsItem)
class SoldGoodsItemAdmin(admin.ModelAdmin):
    fields = ('date', 'goods_item', 'weight')
    list_display = ('date', 'goods_item', 'weight')

@admin.register(FinishedGoodsItem)
class FinishedGoodsItemAdmin(admin.ModelAdmin):
    fields = ('date', 'goods_item', 'weight')
    list_display = ('date', 'goods_item', 'weight')

@admin.register(GoodsItem)
class GoodsItemAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'finished_good', 'sold_good', 'stock_available')

    def get_queryset(self, request):
        qs = super(GoodsItemAdmin, self).get_queryset(request)
        qs = qs.annotate(
            finished_good = Subquery(FinishedGoodsItem.objects.filter(goods_item=OuterRef('pk'))\
                .values('goods_item_id').annotate(sum=Sum('weight')).values('sum')[:1]),
            sold_good = Subquery(SoldGoodsItem.objects.filter(goods_item=OuterRef('pk'))\
                .values('goods_item_id').annotate(sum=Sum('weight')).values('sum')[:1])
        )
        return qs

    def finished_good(self, obj):
        return obj.finished_good

    def sold_good(self, obj):
        return obj.sold_good

    def stock_available(self, obj):
        finished_good = 0 if self.finished_good(obj) is None else self.finished_good(obj)
        sold_good = 0 if self.sold_good(obj) is None else self.sold_good(obj)
        return '-' if (finished_good == 0 and sold_good == 0) else finished_good - sold_good

Hope someone finds this useful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM