Summary syntax

A summary describes the components of a financial statement (such as an order, shopping cart, or invoice). The data behind these components is stored in your models and can take any form you wish. When you define a Summary class, you describe how your data model forms a financial statement.

To do this, all elements of a financial statement are divided into one of three roles:

  • A list of items
  • A single extra cost or deduction
  • A total of one or more items and extra costs

For example, the following is a very simple Summary class:

from rollyourown import commerce

class CartSummary(commerce.Summary):
    products = commerce.Items()
    delivery = commerce.Extra()
    total    = commerce.Total()

In this typical example, the financial statement (a shopping cart) contains a list of products, an additional cost for delivery and a grand total. All possible elements of financial statements should be able to be put in one of these three roles. Here is a small collection of real world examples.

Items
list of products, work sessions, expenses, taxes, deductions/adjustments, discounts, gift vouchers, payments already made, movies watched in hotel room, etc
Extra
single discount, single gift voucher, single tax, shipping/delivery cost, commission, fees/surcharges, etc
Total
grand total, pretax total, total of all taxes, etc

When this is defined, the framework treats Items and Extra as input and provides Total as output. Side benefits of this process include a very clear organisation of the calculation of these totals. If you need to change how this process is done, it should be very clear what changes are required.

Each summary is eventually linked to a Django model instance. The fields of the model provide the data for the Items and Extra input, and these can be specified as explained below.

Items

When you add a list of items to your summary class, you need to specify which field or attribute from the relevant model provides the required data. This field is generally a ManyToManyField, but could also be a reverse ForeignKey field, if that’s how you’ve defined your model.

class rollyourown.commerce.Items(attribute, item_amount_from, cache_amount_as)

All arguments are optional.

Arguments

Items.attribute

Which attribute, field or method provides the list of items. By default it is the same as name you give to the Summary class attribute.

Items.item_amount_from

Which attribute, field or method on each item provides the amount to be used in calculating totals.

  • If you are referencing the Summary instance, use "self.XYZ", which is called with a single argument (the model instance).
  • If you are referencing the model instance, use "model.XYZ". If this is a method, it is called with no arguments.
  • You can also pass a callable, which is called with the model instance as an argument.

The default value is "self.get_X_amount", where X is the name of the summary class attribute.

Items.editable

When the summary is displayed as a formset (see Summary Formsets), these fields will be editable.

  • If editable is set to True, a standard model formset is used with delete=True. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
  • If editable is set to a string, a standard form field is used for the relevant attribute (using the items model as a reference). A delete form field is also provided. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
  • If editable is set to a form, then this form is used. The custom form must accept a keyword argument (instance) and have a .save() method if the data is to be saved.

To Customise the way forms are saved, you can overload the .save_form(formset) method on the Summary class.

Items.cache_amount_as

When a total involving these items is calculated, the amount of each item is stored as an attribute on the relevant model instance. The given string will be the name of this new attribute. The default value is "AMOUNT".

  • This argument cannot be None. Note that, you don’t need to create a field for this, attributes can be added to django models at run time and should not affect the operation of the model. If you do create a field for this value, note that it will not be saved automatically. If you want to store the value, you might like to do so when each item is saved using Django’s usual mechanisms (pre_save signal or overloading the save() method).

Note

One advantage of this framework is that it avoids recalculating things as much as possible. To allow this it makes the assumption that the database does not change once the summary has been created. If you update one of your items or extras after creating the summary, the changes may not appear.

Example

class MySummary(commerce.Summary):
    items    = commerce.Items()
    vouchers = commerce.Items(attribute="gift_vouchers")
    payments = commerce.Items(attribute="payment_set", item_amount_from="model.amount")

Extra

In essence, an extra is simply an amount, which is added to the summary. You can also attach a name and description to this amount to help distinguish it from the others.

class rollyourown.commerce.Extra(verbose_name, amount, included, description)

All arguments are optional.

Arguments

Extra.verbose_name

Human readable name for this extra cost. For example, "VAT" for value added tax.

  • If you are referencing the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • If you are referencing the model instance, use "model.XYZ". If this is callable, it is called with no arguments.
  • You can also pass a callable, which is called with the model instance as an argument.

The default value is the attribute name this Extra is assigned to, untranslated.

Extra.amount

Which attribute, field or method on each item provides the amount to be used in calculating totals.

  • If you are referencing the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • If you are referencing the model instance, use "model.XYZ". If this is callable, it is called with no arguments.
  • You can also pass a callable, which is called with the model instance as an argument.
  • You can pass an integer or decimal, which is used directly

The default value is "self.get_X_amount", where X is the name of the summary class attribute.

Extra.included

Whether or not the value of this extra is already included in other Items or Extra elements. For example, tax is often already included in the price of items, so this Extra is really just calculating how much tax is already in the total, and does not contribute its amount to any total calculations, unless explicitly removed (see Total).

  • If you are referencing the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • If you are referencing the model instance, use "model.XYZ". If this is callable, it is called with no arguments.
  • You can also pass a callable, which is called with the model instance as an argument.
  • You can of course simply pass True or False or anything that bool() accepts

The default value is False.

Extra.description

Human readable description for this extra cost. For example, "19%" for value added tax if the law requires the relevant rate to be shown. This could also simply be a form of “help text”.

  • If you are referencing the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • If you are referencing the model instance, use "model.XYZ". If this is callable, it is called with no arguments.
  • You can also pass a callable, which is called with the model instance as an argument.

The default value is None.

Extra.editable

When the summary is displayed as a formset (see Summary Formsets), this field will be editable.

  • If editable is set to True, a standard model form is used. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
  • If editable is set to a string, a standard form field is used for the relevant attribute. If the relevant instance is not a Django model instance, then a standard text field will be used, and the instance will be updated (but not saved).
  • If editable is set to a form, then this form is used. The custom form must accept a keyword argument (instance) and have a .save() method if the data is to be saved.

To Customise the way forms are saved, you can overload the .save_form() method on the Summary class.

Example

class MySummary(commerce.Summary):
    my_commission = commerce.Extra()
    tax           = commerce.Extra("GST", amount=get_amount_tax, description="15%", included=True)
    discount      = commerce.Extra(verbose_name="Rabatt", description="Mates Rates", amount="-12.23", included=False)

Total

Totals are the output of the framework, summing together the desired Items and Extra elements.

class rollyourown.commerce.Total(*attribute_names, prevent_negative, model_cache)

All arguments are optional.

Arguments

Total.*attribute_names

Any positional arguments passed when defining the Total are interpreted as attribute names. Each of these are names of Items elements, Extra elements or custom functions or attributes which contribute to the total in question.

Which attribute, field or method on each item provides the amount to be used in calculating totals.

  • All strings are interpreted as referencing an Items, Extra, or a method or attribute on the Summary instance.
  • You can also pass a callable, which is called with the summary instance as an argument.
  • If a string begins with a minus character (for example ‘-tax’), then the amount is removed from the total.

If no attribute names are given, then all Items and Extra elements are summed together for a grand total (excluding of course Extra elements that are flagged as already being included).

Total.prevent_negative

If this is true, then the final amount cannot be negative. If the total of the elements does sum to a negative value, then Decimal(0) is returned. By default, this is False.

Note that this argument must be given as a keyword argument.

Total.model_cache

If this value is set, the given string is taken to be the name of an attribute on the model instance which will be set to this total whenever it is calculated. If the value is set on this attribute, the calculation of a total will be skipped. The actual setting of the value is handled by the save_total(instance, name, field_name, total) template method on the Summary class, which can of course be overloaded.

Note that this argument must be given as a keyword argument.

Example

class MySummary(commerce.Summary):
    ...
    total   = commerce.Total()
    pretax  = commerce.Total('items', 'delivery', '-tax')
    to_pay  = commerce.Total(prevent_negative=True)

Meta Options

Summary classes can be annotated with meta information that helps provide context for what you are doing. These are defined in the same way Django adds meta information to its classes. The following attributes help with automated number formatting, based on the locale, currency and context. Note that if Babel is installed, it’s extensive locale database is used, otherwise the framework falls back to using Django’s own formatting, included in Django 1.2 and upwards.

Attributes

Summary.Meta.locale

Locale to help provide automatic formatting of numbers. For example 9,765.34 would be 9.765,34 in Germany. - If you are referencing the a method in the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance). - You can also pass a callable, which is called with the model instance as an argument.

Summary.Meta.currency

Relevant currency (eg ISO-4217 or just a symbol) for this Summary. Generally, only one currency is used for financial transactions, so the whole summary will be formatted using this currency.

  • If you are referencing the a method in the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • You can also pass a callable, which is called with the model instance as an argument.
Summary.Meta.decimal_html

The template for formatting numbers in html. This can be a string (or a callable producing a string) with placeholders for python string substitution.

  • If you are referencing the a method in the Summary instance, use "self.XYZ". If it is callable, it is called with a single argument (the model instance).
  • You can also pass a callable, which is called with the model instance as an argument.

A dictionary will be used with the following information available:

Key Example
value "1,234.56"
curr_sym "$"
decimal_sym "."
major "1,234"
minor "56"

The default value is:

'span class="money">'
'<span class="currency">%(curr_sym)s</span>%(major)s'
'<span class="cents">%(decimal_sym)s%(minor)s</span>'
'</span>'

Which would produce something equivalent to:

<span class="money">
  <span class="currency">$</span>123<span class="cents">.45</span>
</span>

Example

class MySummary(rollyourown.Summary):
    class Meta:
        locale = 'en-AU'
        currency = 'self.get_currency'

    def get_currency(self, instance):
        return instance.currency or 'AUD'

What next?

Once you have defined your Summary object, you now need to be able to use it. The Summary class usage reference will be useful here.