Каковы наилучшие методы расчета счетов в Django 1.10+?

Если есть две модели:

class Invoice(models.Model):
    class Meta:
        ordering = ('-date_added', )

    number = models.CharField(max_length=10,)
    comments = models.TextField(blank=True, help_text="Notes about this invoice." )
    total = models.DecimalField(max_digits=9, decimal_places=2, default="0" )
    date_added = models.DateTimeField(_('date added'), auto_now_add=True)
    date_modified = models.DateTimeField(_('date modified'), auto_now=True)

def __unicode__(self):
    return "%s: total %s" % (self.number, self.total)

class Part(models.Model):
    for_invoice = models.ForeignKey(Invoice)
    description = models.CharField(max_length=200, blank=True, help_text=_("Briefly describe the part.") )
    supplier = models.CharField(max_length=100, blank=True, help_text=_("Supplier Name.") )
    supplier_number = models.CharField(max_length=100, blank=False, help_text=_("Supplier's order number.") )
    qty = models.DecimalField(max_digits=3, decimal_places=0, blank=False, null=False, help_text=_("How many are needed?") )
    cost = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, help_text=_("Price paid per unit") )
    line_total = models.DecimalField(max_digits=9, decimal_places=2, default="0")
    date_added = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

def __unicode__(self):
    return "%s: total %s" % (self.for_invoice, self.line_total)

Первый вариант, который я вижу, - это реализовать «line_total» или «total» в качестве вычисляемого поля модели. Но если вы сделаете это, вы никогда не сможете отсортировать список изменений по «line_total» или «total», и пользователи хотят иметь возможность это делать. Поэтому я сделал их сохраненным полем на модели.

Читая документы Django 1.10, я вижу 4 места, где можно определить код для вычисления и обновления полей «total» и «line_total»:

  1. ModelAdmin.save_model(запрос, объект, форма, изменение) и ModelAdmin.save_formset(запрос, форма, набор форм, изменение)
  2. ModelAdmin.save_related(запрос, форма, наборы форм, изменение)
  3. model.save(я, *args, **kwargs))
  4. Сохранить сигнал

Мне кажется, что сигнал сохранения слишком низкоуровневый — привязан к одному событию сохранения модели, и у него никогда не будет доступа к запросу или сессии. Значит, было бы "раздражающе" пытаться собрать все записи "Part"?

Точно так же кажется, что метод model.save также будет слишком гранулированным. Было бы «удобно», если бы я мог использовать метод Invoice.save, так как он имеет легкий доступ ко всем связанным частям через Invoice.part_set.all(), цикл по этому набору запросов был бы простым способом обновить line_total, затем основной общий. Но опять же, будут ли в этот момент сохранены все новые/измененные записи Part? И у него не будет доступа к HttpRequest.

Я думаю, то же самое относится и к save_model администратора. Тем не менее, у меня смутное воспоминание о том, что можно сначала сохранить связанные части. Я перечисляю части, используя встроенную строку на странице добавления/редактирования счета-фактуры в Admin.

Я только что добавил в ModelAdmin.save_related! Я забыл об этом. Поскольку на этом этапе будет сохранен основной счет, может быть, это лучшее место для обновления всех записей о деталях, а затем обновления родительской суммы?

Заранее спасибо!


person Richard Cooke    schedule 25.04.2017    source источник
comment
Вы можете сортировать по вычисляемому полю. См. этот ответ.   -  person Alasdair    schedule 25.04.2017


Ответы (1)


Благодаря предложению Alasdair этому клиенту не нужно полностью индивидуальное решение. Поэтому я придерживаюсь настроек администратора.

Я тестирую вариант 2: функция save_related администратора. Код ниже:

from decimal import Decimal

  def save_related(self, request, form, formsets, change):
    total = Decimal('0.00')
    for myform in formsets:
      for mf in myform.forms:
        # Skip empty rows (if any):
        if len(mf.cleaned_data) > 0:
          # Update item line total from item quantity and price:
          mf.instance.line_total = mf.instance.qty * mf.instance.cost
          # Add line item total to grand total:
          total += mf.instance.line_total
          # re-save line item:
          mf.instance.save()
    # Update grand total on the invoice:
    form.instance.total = total
    # re-save the invoice record:
    form.instance.save()
    # Chain to inherited code:
    super(InvoiceAdmin, self).save_related(request, form, formsets, change)

Извините за плохие имена переменных. Я был застигнут врасплох тем, что встроенные формы имеют 2 уровня глубины. Я предполагаю, что первый слой представляет собой группу наборов форм (только один), а внутри первого набора форм находятся мои формы. У всего есть объекты .instance, что, я думаю, указывает на то, что они уже были сохранены. Удивительно, так как я думал, что прочитал, что действие этой функции по умолчанию - сохранить наборы форм (основная форма уже сохранена).

Как бы то ни было, это работает, поэтому я предполагаю, что неправильно понял документы.

person Richard Cooke    schedule 26.04.2017
comment
Я должен реорганизовать это. Обратной стороной этого решения является тестирование. Трудно тестировать бизнес-логику в функции сохранения администратора. На самом деле, единственным способом является полноценное тестирование Selenium-клиента в реальном браузере. Должен ли я проголосовать за свой собственный ответ? - person Richard Cooke; 18.02.2019