Roll Your Own documentation contents¶
Getting started¶
Welcome to Roll Your Own¶
The Django Web Development Framework was developed for the perfectionist developer, someone who wants things done in a particular way. Django does a very good job at allowing and almost anything, it is helpful for most things you want to do, but stays out of the way when you want to do something unexpected.
Approach¶
In keeping with this approach, the RollYourOwn frameworks aim to give you the developer as much freedom as possible. A few things are especially important:
- You (the developer) have full control over your database models
- Any inherent complexity is encapsulated in the framework (which is well tested)
- The framework encourages well organised, concise and reusable code
The developer in control of their models¶
Note
- The frameworks do not make any assumptions about your desired database model
Containing complexity¶
Note
- Any inherent complexity in this domain should be in the framework, where it is tested and analysed by more people
Well organised, concise and reusable¶
Note
- Provides an clear, interface for the various components of your application
What next?¶
You might like to read the Quick install guide or start reading through the Commerce tutorial.
Quick install guide¶
The easiest way to install Roll Your Own is to use use easy_install
or pip
you have have them installed. If that’s the case, use one of the following commands:
pip install rollyourown
easy_install rollyourown
If you don’t have easy_install
or pip
installed, you will need to do things manually.
Firstly, install Django by following the installation instructions at http://docs.djangoproject.com/en/dev/intro/install/.
Next download the RollYourOwn release from http://code.google.com/p/rollyourown/. To unpack and install it, run the following from your shell:
tar xvzf rollyourown-1.0.tar.gz
cd rollyourown-1.0/
python setup.py install
That’s it, you’ve installed everything you need to install. You can now try the tutorial.
Development version¶
For those you like to help out, you can check out the development version here:
git clone git@github.com:willhardy/Roll-Your-Own.git
Commerce Framework Tutorial¶
This is designed as a framework, not an app, so you need to write your own models. Once you have done this, define a Summary class, which describes how your cart/order/invoice will operate.
Concept¶
To maximise flexibility, I’ve tried to identify the core features of carts, orders, invoices or any financial summary. These are:
- items (a many-to-many collection of things to sum up, eg products, work sessions, discounts, vouchers)
- extras (an global added cost or discount, eg tax, delivery, discount)
- totals (a sum of some or all of the above, eg pretax total, total)
This is done to keep things as flexible as possible and divide a cart into parts which operate similarly. For example, a discount could be a single “extra” that is applied to a cart, or you could allow a user to apply several discounts to their cart (several discount “items”). The totals are generated by the framework, based on what you define should be included.
Basic shopping cart¶
To begin you need to create your own cart app with an appropriate model. Just as in the Django tutorial, create a new app and edit your models.py
file, adding the following three models:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
class Cart(models.Model):
items = models.ManyToManyField(Product, through="CartItem")
class CartItem(models.Model):
product = models.ForeignKey(Product)
cart = models.ForeignKey(Cart)
quantity = models.PositiveIntegerField(default=1)
def get_item_amount(self, instance):
return self.product.price * self.quantity
def __unicode__(self):
return "%dx %s" % (self.quantity, self.product.name)
As you can see, this is a pretty basic shopping cart. Each product has a price and a name, and can be linked to a number of carts with a varying quantity. You are free to modify these models as you wish later, the commerce framework makes no assumptions as to how things are organised.
Let’s create some data for our cart:
>>> from myapp.models import Cart
>>> guitar = Product.objects.create(name="Guitar", price="329.42")
>>> saxophone = Product.objects.create(name="Saxophone", price="672.23")
>>> triangle = Product.objects.create(name="Triangle", price="4.48")
>>> my_cart = Cart.objects.create()
>>> CartItem.objects.create(product=guitar, cart=my_cart)
>>> CartItem.objects.create(product=triangle, cart=my_cart)
>>> CartItem.objects.create(product=saxophone, cart=my_cart, quantity=3)
With your models in hand, you can now create a Summary of your cart.
Cart Summary¶
This is where the commerce framework comes into the picture. Open up a new file, for example commerce.py
and enter the following:
from rollyourown import commerce
class CartSummary(commerce.Summary):
items = commerce.Items(attribute="items", item_amount_from="get_item_amount")
delivery = commerce.Extra()
total = commerce.Total()
def get_amount_delivery(self, instance):
return "10.00"
This summary will describe our cart. It defines a cart as having a number of items (items), an extra cost (delivery) and a total (total). The summary knows where to find the items (by default it looks for an attribute in the model with the same name, items). The amount of this extra cost (delivery) is found by default by looking for a method call get_amount_X
where X
is the name of the extra. Conveniently, we defined a method get_amount_delivery
which provides this extra cost (fixed at 10.00).
Our total is then generated automatically, by adding everything together in the CartSummary. We can try this out using the shell.
Lets see what the summary can tell us about the cart we filled earlier:
>>> from myapp.commerce import CartSummary
>>> cart_summary = CartSummary(my_cart)
>>> cart_summary.total
Decimal('2360.59')
>>> print cart
1x Guitar 329.42
1x Triangle 4.48
3x Saxophone 2016.69
Delivery 10.00
Total 2360.59
Great! But it’s not especially impressive; The framework merely added up the cost of the products and added a 10.00 delivery fee. Let’s make things a little more interesting.
New delivery pricing¶
Revising our CartSummary
definition, let’s make the delivery calculation more sophisticated:
from rollyourown import commerce
from decimal import Decimal
class CartSummary(commerce.Summary):
items = commerce.Items(attribute="items", item_amount_from="get_item_amount")
delivery = commerce.Extra()
subtotal = commerce.Total('items')
total = commerce.Total()
def get_amount_delivery(self, instance):
" Delivery is 10% of the subtotal "
return (self.subtotal / 10).quantize(Decimal("0.01"))
Now our delivery is calculated as 10% of the cost of the items. We’ve also added a new total (subtotal), which sums only the cost of the items. Let’s see what information our summary provides:
>>> from myapp.commerce import CartSummary
>>> cart_summary = CartSummary(my_cart)
>>> cart_summary.subtotal
Decimal('2350.59')
>>> cart_summary.total
Decimal('2585.65')
>>> cart_summary.delivery.amount
Decimal('235.06')
>>> print cart_summary
1x Guitar 329.42
1x Triangle 4.48
3x Saxophone 2016.69
Delivery 235.06
Total 2360.59
Now things are getting interesting, we’ve changed our delivery pricing structure without touching our data model.
What else is possible?¶
This is just a simple demonstration of how everything fits together. The commerce framework has a number of other features, which you can read about in the Summary class syntax reference and the summary class usage. These include:
- automatic and configurable locale-aware currency formatting for amounts
- already-included values, which are removed from a total (eg TAX)
- optional protection against negative values
- sophisticated total calculation
- denormalisation (calculated value caching using model instance)
- utility functions for tax calculation, unique IDs etc
Populate Framework Tutorial¶
See also
If you’re new to Django and Python, you may want to read up about them. A useful starting guide is in the Django documentation.
Best Practices Guides¶
eCommerce Best Practices¶
See also
If you’re new to Django and Python, you may want to read up about them. A useful starting guide is in the Django documentation.
API Reference¶
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.
Summary instance methods¶
Having defined our Summary class, we can now put it to use in querying and formatting our financial information. Given the following summary definition:
class MySummary(commerce.Summary):
products = commerce.Items()
delivery = commerce.Delivery()
total = commerce.Total()
class Meta:
currency = "USD"
locale = "de-DE"
The simplest use is of course getting the relevant amounts:
>>> my_summary = MySummary(my_model_instance)
>>> my_summary.total
1234.56
>>> my_summary.delivery
129.90
>>> my_summary.products
[<Product: First Product>, <Product: Second Product>]
>>> my_summary.products[0].AMOUNT
933.12
Warning
Any changes to your data after the summary has been created may not be reflected in the summary. This is a deliberate assumption to make optimisation simpler, and is not difficult to abide by. If the summary must be updated, you can recreate it using the updated model instance.
Note
The instance you give to your summary class need not actually be a Django model instance. It can be any python object that has the attributes required by the summary class. Instead of a Many-To-Many relationship, your python object can simply have an attribute with a list of item objects (which can simply be another python object).
Numbers can be formatted to the relevant locale (in this case German):
>>> print my_summary.total
$1.234,56
>>> print my_summary
First Product $ 933,12
Second Product $ 171,54
Delivery $ 129,90
Total $ 1.234,56
>>> print my_summary.total.html
<span class="money"><span class="currency">$</span>1.234<span class="cents">,56</span></span>
Accessing elements¶
Each type of element (Items
, Extra
, Total
) has a slightly different form.
When you access an Items
attribute (eg. my_summary.products
) you get a Django QuerySet in return. The queryset is identical to a QuerySet returned when using Django’s model API, except that the relevant amount for each item (see item_amount_from
) is included as an additional attribute. The name of the attribute is by default AMOUNT
, but can be defined by setting the cache_amount_as
parameter when defining the Summary class. The queryset is retrieved only once, and the amount is calculated only once.
Extra
elements are returned as a special object with four attributes:
Attribute | Type |
---|---|
.extra.verbose_name |
unicode |
.extra.amount |
FormattedDecimal |
.extra.included |
bool |
.extra.description |
unicode |
Total
elements are simply FormattedDecimal
objects.
Each of the elements can be programmatically accessed using the _meta
attribute of the summary. The _meta
attribute may change in the future, but will contain at least the following attributes:
Attribute | Type |
---|---|
._meta.locale |
unicode |
._meta.currency |
unicode |
._meta.decimal_html |
unicode |
._meta.extras |
OrderedDict of all Extra elements |
._meta.items |
OrderedDict of all Items elements |
._meta.totals |
OrderedDict of all Total elements |
Formatting¶
A FormattedDecimal
works exactly like a decimal, except it has a few extra formatting abilities attached:
>>> my_summary.total
Decimal("1234.56")
>>> my_summary.total + 7
Decimal("1241.56")
>>> print my_summary.total
$1.234,56
>>> my_summary.total.html
u'<span class="money"><span class="currency">$</span>1.234<span class="cents">,56</span></span>'
Summary Formsets¶
Editable statements using forms can be easily generated, once you have defined your fields as being editable (see Summary Syntax):
>>> my_summary.formset
<Formset: >
>>> print my_summary.formset
<tr><td> ... etc
>>> print my_summary.forms.as_ul
<li> ... etc
Note that non-editable fields are included for convenience. The remaining functionality should be familiar to Django developers:
>>> my_summary.forms.is_valid()
True
>>> my_summary.forms.save()
Populator syntax¶
See also
If you’re new to Django and Python, you may want to read up about them. A useful starting guide is in the Django documentation.