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.- If you are referencing the Summary instance, use
-
Items.
editable
¶ When the summary is displayed as a formset (see Summary Formsets), these fields will be editable.
- If
editable
is set toTrue
, a standard model formset is used withdelete=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). Adelete
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.- If
-
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 thesave()
method).
- This argument cannot be
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.- If you are referencing the Summary instance, use
-
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.- If you are referencing the Summary instance, use
-
Extra.
included
¶ Whether or not the value of this extra is already included in other
Items
orExtra
elements. For example, tax is often already included in the price of items, so thisExtra
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 (seeTotal
).- 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
.- If you are referencing the Summary instance, use
-
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
.- If you are referencing the Summary instance, use
-
Extra.
editable
¶ When the summary is displayed as a formset (see Summary Formsets), this field will be editable.
- If
editable
is set toTrue
, 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.- If
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 ofItems
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
andExtra
elements are summed together for a grand total (excluding of courseExtra
elements that are flagged as already being included).- All strings are interpreted as referencing an
-
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 isFalse
.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 theSummary
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.
- If you are referencing the a method in the Summary instance, use
-
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>
- If you are referencing the a method in the Summary instance, use
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.