Суммирование внутри модели корзины промежуточного итога каждой записи TabularInline в Django Admin

Не уверен, что это возможно, но в моем подробном представлении корзины (класс модели) в Django Admin я хотел бы суммировать все промежуточные итоги класса модели TabularInline Entry и сохранить это значение в атрибуте total_price моей корзины. Есть ли способ сделать это или как-то использовать фильтр или виджет формы, чтобы добавить все промежуточные итоги вместе для той же цели? Заранее благодарю за любую помощь!

Ниже приведен пример того, что я хотел бы сделать. Вы можете видеть в поле итоговой цены, которое я вручную ввел 130 (итого: 90 + 20 + 20 = 130). Я хотел бы, чтобы это вычислялось автоматически каждый раз, когда запись добавляется из инвентаря, и ее количество редактируется.

введите описание изображения здесь

До сих пор в моем admin.py у меня был административный класс TabularInline, который возвращает промежуточный итог для каждой записи, умножая ее количество на соответствующую цену. Затем мой класс CartAdmin отображает эти встроенные отношения в подробном представлении модели корзины.

admin.py

class EntryInline(admin.TabularInline):
    model = Entry
    extra = 0
    readonly_fields = ['subtotal']
    exclude = ['price']
    def subtotal(self, obj):
        return "$" + str(obj.quantity * obj.inventory.price)


class CartAdmin(admin.ModelAdmin):
    inlines = [EntryInline]
    fieldsets = (
        (None, {
            'fields':('user', 'total_price')
            }),
    )

models.py

class Inventory(models.Model):
    quantity        = models.IntegerField()
    price           = models.DecimalField(max_digits=5, decimal_places=2)


class Cart(models.Model):
    user            = models.OneToOneField(User)
    total_price     = models.DecimalField(max_digits=4, decimal_places=2, blank=True, null=True)


class Entry(models.Model):
    cart            = models.ForeignKey(Cart, related_name="entries")
    inventory       = models.ForeignKey(Inventory, related_name="entries")
    quantity        = models.PositiveSmallIntegerField(default=1)
    price           = models.DecimalField(max_digits=4, decimal_places=2, blank=True, null=True)

3 ответа

Вы можете попробовать получить total_price в админке корзины и заполнить поле следующим образом:

class CartAdmin(admin.ModelAdmin):
    inlines = [EntryInline]
    fieldsets = (
    (None, {
        'fields':('user', 'total_price')
        }),
    )

    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        # check if the cart object exists
        if obj:
            try:
                _price = Entry.objects.filter(cart=obj).aggregate(sum=Sum(F('quantity')*F('inventory__price'), output_field=FloatField()))
                total = _price['sum']
                obj.total_price = total
            except:
                pass
    return form

Что касается вашей ошибки импорта, импортируйте F и Sum как:

from django.db.models import Sum, F

Или, если вы хотите более динамичный контроль, чтобы всякий раз, когда пользователь редактировал количество во встроенной записи, total_price обновлялся автоматически, вы можете написать собственный javascript для него.

Надеюсь, поможет.

Ниже приведено то, что я использовал, чтобы обновить страницу после сохранения в админке, чтобы обновить общее количество:

cart.js

if (!$) {
    $ = django.jQuery;
 }
function addSecs(d, s) {
    return new Date(d.valueOf() + s * 1000);
}
function doRun() {
    document.getElementById("msg").innerHTML = "Processing JS...";
    setTimeout(function() {
        start = new Date();
        end = addSecs(start, 5);
        do {
            start = new Date();
        } while (end - start > 0);
        document.getElementById("msg").innerHTML = "Finished Processing";
    }, 10);
 }
$(function() {
    $(".change_form_save").click(doRun);

    if (window.localStorage) {
        if (!localStorage.getItem("firstLoad")) {
            localStorage["firstLoad"] = true;
            window.location.reload();
        } else localStorage.removeItem("firstLoad");
    }
});

Затем в моем admin.py под моим классом для CartAdmin(admin.ModelAdmin):

class Media:
    js = ('js/cart.js',)

Хорошо, я все еще работаю над тем, как обновить браузер, отправляющий AJAX-запрос, когда я редактирую количество в подробном административном представлении, чтобы увидеть обновленные изменения в общей цене корзины на стороне сервера. Не уверен, сколько времени это займет, но я обновлю этот ответ, как только выясню.

А пока вот как я смог получить промежуточные итоги и итоги:


В models.py я добавил поле "промежуточный итог " в мою модель Entry:

subtotal = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)

В admin.py:

class EntryInline(admin.TabularInline):
    model = Entry
    extra = 0
    # calls my helper method
    readonly_fields = ['get_subtotal']
    # exclude redundant field being replaced by helper method
    exclude = ['subtotal']


    # gets subtotal of each entry
    def get_subtotal(self, obj):
        # for a given entry, find that id
        entry = Entry.objects.get(id=obj.id)
        # subtotal the entry quantity * its related price
        entry_subtotal = obj.quantity * obj.inventory.price
        # check if entry obj has a subtotal and try to save to db
        if entry.subtotal != entry_subtotal:
            try:
                entry.subtotal = entry_subtotal
                entry.save()
            except DecimalException as e:
                print(e)
        # return a str representation of entry subtotal
        return "$" + str(entry_subtotal)
    # set helper method's description display
    get_subtotal.short_description = 'subtotal'


# initialization of variable
total_price = 0

class CartAdmin(admin.ModelAdmin):
    model = Cart
    inlines = [EntryInline]
    # calls get_total helper method below
    readonly_fields = ['get_total']
    exclude = ['total_price']


    def get_total(self, obj):
        # extend scope of variable
        global total_price
        # get all entries for the given cart
        entries = Entry.objects.filter(cart=Cart.objects.get(id=obj.id))
        # iterate through entries
        for entry in entries:
            # if entry obj has a subtotal add it to total_price var
            if entry.subtotal:
                total_price += entry.subtotal
        print(obj.total_price)
        # assign cart obj's total_price field to total_price var
        obj.total_price = total_price
        # save to db
        obj.save()
        # reset total_price var
        total_price = 0
        # return cart's total price to be displayed
        return obj.total_price
    # give the helper method a description to be displayed
    get_total.short_description = 'total'

** Следует отметить, что промежуточные итоги загружаются динамически, когда я редактирую количество и сохраняю его, потому что он использует вспомогательный метод. Мне все еще нужно обновить браузер еще раз, чтобы сохранить в базе данных, однако дисплей все еще там. Я не уверен, почему get_total() не работает так же; Там нет дисплея И я должен обновить браузер, чтобы сохранить в базе данных. Логика кажется противоречивой...

Другие вопросы по тегам