Plata - the lean and mean Django-based Shop¶
This is the documentation for fiee’s fork of Feinheit’s project. Find the sources at https://github.com/fiee/plata
Overview¶
Instead of fighting against a big and all-knowing shop system, which never does the right thing although it “only requires pushing a few configuration buttons here and there”, Plata caters to the programmer by only providing order, payment and discount models and some code for cart, checkout and payment views. Building a shop on top of Django and Plata still requires programming – but that’s what we do after all.
Contents¶
Installation instructions¶
Installation¶
This document describes the steps needed to get Plata up and running.
Plata is based on Django, so you need a working Django installation first. Plata is developed using Django 1.10, and is not tested against any earlier version.
You can download a version of plata using pip
:
$ pip install -e git+https://github.com/fiee/plata@next#egg=Plata
Please note that the package installable with pip
only
contains the files needed to run plata. It does not include documentation,
tests or the example project which comes with the development version,
which you can download using the Git version control system:
$ git clone git://github.com/fiee/plata.git -b next
Plata requires a version of simplejson >=2.1 because older versions cannot serialize decimal values, only floats.
In addition, you will need PDFDocument if you want to generate PDFs.
Configuration¶
Writing your product model¶
First, you need to write your own product and price model. There is a small (and documented) interface contract described in Contracts.
The smallest possible implementation while still following best practice follows here:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from plata.product.models import ProductBase
from plata.shop.models import PriceBase
class Product(ProductBase):
name = models.CharField(_('name'), max_length=100)
slug = models.SlugField(_('slug'), unique=True)
description = models.TextField(_('description'), blank=True)
class Meta:
ordering = ['name']
verbose_name = _('product')
verbose_name_plural = _('products')
def __unicode__(self):
return self.name
@models.permalink
def get_absolute_url(self):
return ('plata_product_detail', (), {'slug': self.slug})
class ProductPrice(PriceBase):
product = models.ForeignKey(Product, verbose_name=_('product'),
related_name='prices')
class Meta:
get_latest_by = 'id'
ordering = ['-id']
verbose_name = _('price')
verbose_name_plural = _('prices')
Plata has to know the location of your shop model, because it is referenced
e.g. in the product
ForeignKey of order items. If the product model exists
in the myapp
Django application, add the following setting:
PLATA_SHOP_PRODUCT = 'myapp.Product'
Adding the modules to INSTALLED_APPS
¶
INSTALLED_APPS = (
...
'myapp',
'plata',
'plata.contact', # Not strictly required (contact model can be exchanged)
'plata.discount',
'plata.payment',
'plata.shop',
...
)
You should run ./manage.py makemigrations plata
and
./manage.py migrate plata
after you’ve added the required modules
to INSTALLED_APPS
.
Creating the Shop
object¶
Most of the shop logic is contained inside Shop
.
This class implements cart handling, the checkout process and handing control
to the payment modules when the order has been confirmed. There should exist
exactly one Shop instance in your site (for now).
The Shop class requires three models and makes certain assumptions about them. The aim is to reduce the set of assumptions made or at least make them either configurable or overridable.
The models which need to be passed when instantiating the Shop object are
Contact
Order
Discount
Example:
from plata.contact.models import Contact
from plata.discount.models import Discount
from plata.shop.models import Order
from plata.shop.views import Shop
shop = Shop(
contact_model=Contact,
order_model=Order,
discount_model=Discount,
)
The shop objects registers itself in a central place, and can be fetched from anywhere using:
import plata
shop = plata.shop_instance()
The Shop
class instantiation may be in myapp.urls
or myapp.views
or somewhere similar, it’s recommended to put the statement into the views.py
file because the Shop
class mainly offers views (besides a few helper
functions.)
Adding views and configuring URLs¶
The Shop
class itself does not define any product
views. You have to do this yourself. You may use Django’s generic views or
anything else fitting your needs.
Generic views using plata.shop_instance()
could look like this:
from django import forms
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import ugettext_lazy as _
from django.views import generic
import plata
from plata.contact.models import Contact
from plata.discount.models import Discount
from plata.shop.models import Order
from plata.shop.views import Shop
from myapp.models import Product
shop = Shop(
contact_model=Contact,
order_model=Order,
discount_model=Discount,
)
product_list = generic.ListView.as_view(
queryset=Product.objects.all(),
paginate_by=10,
template_name='product/product_list.html',
)
class OrderItemForm(forms.Form):
quantity = forms.IntegerField(label=_('quantity'), initial=1,
min_value=1, max_value=100)
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug)
if request.method == 'POST':
form = OrderItemForm(request.POST)
if form.is_valid():
# Referencing the shop object instantiated above
order = shop.order_from_request(request, create=True)
try:
order.modify_item(product, relative=form.cleaned_data.get('quantity'))
messages.success(request, _('The cart has been updated.'))
except forms.ValidationError, e:
if e.code == 'order_sealed':
[messages.error(request, msg) for msg in e.messages]
else:
raise
return redirect('plata_shop_cart')
else:
form = OrderItemForm()
return render(request, 'product/product_detail.html', {
'object': product,
'form': form,
})
Next, you need to add the Shop’s URLs to your URLconf:
from django.conf.urls import include, url
from myapp.views import shop, product_list, product_detail
urlpatterns = [
url(r'^shop/', include(shop.urls)),
url(r'^products/$',
product_list,
name='plata_product_list'),
url(r'^products/(?P<slug>[-\w]+)/$',
product_detail,
name='plata_product_detail'),
]
The context processor¶
You can add plata.context_processors.plata_context
to your settings.TEMPLATE_CONTEXT_PROCESSORS
.
This will add the following variables to your template context if they are available:
plata.shop
: The currentplata.shop.views.Shop
instanceplata.order
: The current orderplata.contact
: The current contact instanceplata.price_includes_tax
: Whether prices include tax or not
Alternatively you can also just overwrite the get_context
method in your shop class.
Setting up logging¶
Plata uses Python’s logging module for payment processing, warnings and
otherwise potentially interesting status changes. The logging module is
very versatile and sometimes difficult to configure, because of this an
example configuration is provided here. Put the following lines into
your settings.py
, adjusting the logfile path:
import logging, os
import logging.handlers
LOG_FILENAME = os.path.join(APP_BASEDIR, 'log', 'plata.log')
plata_logger = logging.getLogger('plata')
plata_logger.setLevel(logging.DEBUG)
plata_logging_handler = logging.handlers.RotatingFileHandler(LOG_FILENAME,
maxBytes=10*1024*1024, backupCount=15)
plata_logging_formatter = logging.Formatter('%(asctime)s %(levelname)s:%(name)s:%(message)s')
plata_logging_handler.setFormatter(plata_logging_formatter)
plata_logger.addHandler(plata_logging_handler)
Implementing the shop as FeinCMS application content¶
To use the shop as application content you have to
overwrite the render
and redirect
methods on your shop class.
Take a look at this example in myshop.views
:
from feincms.content.application.models import app_reverse
from plata.shop.views import Shop
class CustomShop(Shop):
def render(self, request, template, context):
""" render for application content """
return template, context
def redirect(self, url_name):
return redirect(app_reverse(url_name, 'myshop.urls'))
base_template = 'site_base.html'
shop = CustomShop(Contact, Order, Discount)
Payment support¶
Plata supports the following means of payment:
- Cash on delivery
- Payment in advance
- Check / Bank Transfer
- Paypal
- Postfinance (Switzerland)
- Ogone
- Datatrans (Switzerland)
- Billogram (Sweden)
- Payson (Sweden)
- PagSeguro (Brazil)
- Stripe Checkout
‘’Beware’‘, most of these are not up to date and their general state of usefulness is unknown. Always try to understand the source code.
Cash on delivery¶
This payment module does not need any configuration. It simply creates an order payment with the order total as amount and confirms the order.
Payment in advance¶
Send an invoice to the customer and wait for clearance by an admin. This is just a copy of the check module without mentioning checques.
Configuration: PLATA_PAYMENT_PREPAY_NOTIFICATIONS
Check / Bank Transfer¶
Configuration: PLATA_PAYMENT_CHECK_NOTIFICATIONS
Paypal¶
The Paypal payment module needs two configuration values:
PAYPAL = {
'LIVE': False, # Use sandbox or live payment interface?
'BUSINESS': 'paypal@example.com',
'IPN_SCHEME': 'https',
'RETURN_SCHEME': 'https'
}
The default IPN URL is payment/paypal/ipn/
; the base path is defined
by where you added shop.urls
.
You should also specify ‘RETURN_SCHEME’ and ‘IPN_SCHEME’ within the PAYPAL settings and use SSL; IPN_SCHEME defaults to ‘http’, and RETURN_SCHEME will auto-select ‘http’ or ‘https’ based on the request.is_secure() value of the payment initiating request.
Never use unencrypted communication in production!
Stripe Checkout¶
Latest addition to Plata’s payment modules, not yet tested in production.
Configuration values:
STRIPE = {
'PUBLIC_KEY': '…',
'SECRET_KEY': '<secret>',
'LOGO': '%simg/yoursiteslogo.png' % STATIC_URL,
'template': 'myshop/payment/stripe_form.html',
}
Testing or live payments depend on the keys; you might use different setup files. Don’t keep secrets (passwords etc.) in setting files that get checked in into source control!
If you use zero-decimal currencies like ‘JPY’ or ‘KRW’, you should setup:
CURRENCIES_WITHOUT_CENTS = ('JPY', 'KRW') # these two are default
And this is stripe_form.html:
{% if not callback %}
{% trans "Thank you for your payment!" %}
{% include "plata/_order_overview.html" %}
<form action="{{ post_url }}" method="POST">
{% csrf_token %}
{{ form.management_form }}
{{ form.id }}
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-label="{% trans 'Pay with Card' %}"
data-key="{{ public_key }}"
data-amount="{{ amount }}"
data-currency="{{ currency }}"
data-name="{{ name }}"
data-description="{{ description }}"
data-image="{{ logo }}"
data-locale="auto"
{% if user.email %}data-email="{{ user.email }}"{% endif %}
data-zip-code="true">
</script>
</form>
{% endif %}
More information: https://stripe.com/docs/checkout
Postfinance (Switzerland)¶
The Postfinance payment module requires the following configuration values:
POSTFINANCE = {
'LIVE': False,
'PSPID': 'exampleShopID',
'SHA1_IN': '<shared secret>',
'SHA1_OUT': '<shared secret>',
}
This module implements payments using SHA-1 hash validation with the simpler SHA-1 IN hashing algorithm. (Someone should update this to a safer method!)
The default server to server notification URL is payment/postfinance/ipn/
;
the base path is defined by where you added shop.urls
.
More information:
Datatrans (Switzerland)¶
The Datatrans payment module requires the following configuration values:
DATATRANS = {
'MERCHANT_ID': '1000000000000',
'LIVE': False
}
To work with them, you must inquire about contracts.
In their lowest pay scale they charge about 500 CHF for setup plus 20 CHF per month plus transaction fees (in 2017). That was too much for my customer, so I stopped updating the datatrans payment module.
Billogram (Sweden)¶
Requires billogram.api
and the following configuration:
BILLOGRAM = {
'API_USER': '',
'API_AUTHKEY': '',
'API_BASE': '',
'SIGN_KEY': ''
}
Payson (Sweden)¶
Requires payson_api
and the following configuration values:
PAYSON = {
'USER_ID': '',
'USER_KEY': '',
'EMAIL': 'you@example.com'
}
PagSeguro (Brazil)¶
The PagSeguro payment module is looking for the following configuration values:
PAGSEGURO = {
'EMAIL': 'you@example.com',
'TOKEN': '???',
'LOG': 'pagseguro.log' # file name
}
Ogone¶
The Ogone payment module requires the following configuration values:
OGONE = {
'LIVE': False,
'PSPID': 'exampleShopID',
'SHA1_IN': '<shared secret>',
'SHA1_OUT': '<shared secret>',
}
This module implements payments using SHA-1 hash validation with the simpler SHA-1 IN hashing algorithm.
The default server to server notification URL is payment/ogone/ipn/
;
the base path is defined by where you added shop.urls
.
This payment provider is part of Ingenico since 2014, thus it’s questionable if the module still works.
Settings¶
PLATA_PRICE_INCLUDES_TAX
:Determines whether prices are shown with tax included by default. This setting does not influence internal calculations in any way.
Defaults to
True
PLATA_ORDER_PROCESSORS
:The list of order processors which are used to calculate line totals, taxes, shipping cost and order totals.
The classes can be added directly or as a dotted python path. All classes should extend
ProcessorBase
.PLATA_PAYMENT_MODULES
:- The list of payment modules which can be used to pay the order. Currently, all available modules are enabled too.
PLATA_PAYMENT_MODULE_NAMES
The user-visible names of payment modules can be modified here. Example:
PLATA_PAYMENT_MODULE_NAMES = {'paypal': _('Paypal and credit cards')}
PLATA_SHIPPING_FIXEDAMOUNT
:If you use
FixedAmountShippingProcessor
, you should fill in the cost incl. tax and tax rate here.Defaults to
{'cost': Decimal('8.00'), 'tax': Decimal('7.6')}
PLATA_ZIP_CODE_LABEL
:- Since ZIP code is far from universal, and more an L10N than I18N issue…
Defaults to
'ZIP code'
. PLATA_SHIPPING_WEIGHT_UNIT
andPLATA_SHIPPING_LENGTH_UNIT
:- If you use
Postage
and don’t like metric units, you can change them here. Defaults to'g'
resp.'mm'
. No calculations involved, just a display string. PLATA_REPORTING_STATIONERY
:- Stationery used by PDFDocument to render invoice and packing slip PDFs.
PLATA_PDF_FONT_NAME
:- Custom regular font name to be used by PDFDocument for rendering PDF invoices. Defaults to
''
(using default ofreportlab
). PLATA_PDF_FONT_PATH
- Custom regular font path to be used by PDFDocument for rendering PDF invoices. Defaults to
''
. PLATA_PDF_FONT_BOLD_NAME
- Custom bold font path to be used by PDFDocument for rendering PDF invoices. Defaults to
''
. PLATA_PDF_FONT_BOLD_PATH
- Custom bold font path to be used by PDFDocument for rendering PDF invoices. Defaults to
''
. PLATA_STOCK_TRACKING
:Accurate transactional stock tracking. Needs the
plata.product.stock
Django application.Each stock change will be recorded as a distinct entry in the database. Products will be locked when an order is confirmed for 15 minutes, which means that it’s not possible to begin or end the checkout process when stock is limited and someone else has already started paying.
CURRENCIES
:- A list of available currencies. Defaults to
('CHF', 'EUR', 'USD', 'CAD')
. You should set this variable for your shop. PLATA_SHOP_PRODUCT
:- Target of order item product foreign key in
app_label.model_name
notation. Defaults to'product.Product'
Contracts¶
This document describes the minimum contract the shop models are supposed to fulfill.
The simple example at examples/simple/
is supposed to demonstrate the
most simple use of Plata.
Product model¶
The (base) product model has to be specified as PLATA_SHOP_PRODUCT
using Django’s app_label.model_name
notation. The model referenced
will be the model pointed to by the OrderItem
order line items.
Since it’s your responsibility to write all views and URLs for your product catalogue, there aren’t many things Plata’s products have to fulfill.
Plata provides an abstract model at plata.product.models.ProductBase
.
You do not have to use it - it just provides the standard interface to
the price determination routines already.
get_price(currency=None, orderitem=None)
:The
get_price
method has to accept at least the current currency and optionally more arguments too, such as the current line item for the implementation of price tiers.This method must return a
Price
instance. It has to raise aPrice.DoesNotExist
exception if no price could be found for the passed arguments.handle_order_item(self, orderitem)
:This method is called when modifying order items. This method is responsible for filling in the
name
andsku
(Stock Keeping Unit) field on the order item.
Different product models¶
If you need different product models, consider using Django’s model inheritance and something like django-polymorphic.
Price model¶
Plata has a bundled abstract price model which does almost everything required. You have to provide the concrete price model yourself though.
The only thing it lacks for a basic price is a foreign key to product.
ProductBase.get_price
assumes that the foreign key is defined with
related_name='prices'
.
When modifying a cart or an order, Plata will call the price object’s
handle_order_item
method, passing the order item instance as the only
argument. This method is responsible for filling the following attributes
on the order item:
_unit_price
: Unit price excl. tax._unit_tax
: The tax on a single unit.tax_rate
: The applied tax rate as a percentage. This is slightly redundant as it could be calculated from_unit_price
and_unit_tax
.tax_class
: (Optional) a foreign key toplata.shop.models.TaxClass
. This is purely informative. This field is not mandatory.is_sale
: Boolean flag denoting whether the price is a sale price or not. This flag is unconditionally set toFalse
by the price base class.
The price model offers the following attributes:
unit_price_excl_tax
A
decimal.Decimal
describing the unit price with tax excluded.unit_price_incl_tax
andunit_price
are offered by the default implementation too, but they aren’t mandatory.unit_tax
A
decimal.Decimal
too. This is the tax amount per unit, not the tax rate.tax_class.rate
The tax rate as a percentage, meaning
19.6
for a tax rate of 19.6%.tax_class
A
plata.shop.models.TaxClass
object for the given price.
Contact model¶
The example at examples/custom
shows how the contact model might be
replaced with a different model. The following fields and methods are
mandatory:
Contact.user
:ForeignKey
orOneToOneField
toauth.User
.Contact.update_from_order(self, order, request=None)
: Updates the contact instance with data from the passed order (and from the optional request).Contact.ADDRESS_FIELDS
: A list of fields which are available as billing and/or shipping address fields.
The last point isn’t strictly necessary and can be circumvented by
overriding plata.shop.views.Shop.checkout_form()
.
plata.contact.models.Contact
offers all that, and more.
Taxes¶
Plata supports different tax rates, even different tax rates in the same order. When calculating the order total, tax amounts with the same tax rate are grouped and can be shown separately on an invoice document.
The tax details are stored in the data
attribute on the order instance.
The format is as follows:
order.data['tax_details'] = [
[<tax_rate>, {
'discounts': <sum of all discounts>,
'prices': <sum of line item prices>,
'tax_amount': <sum of line item tax amounts>,
'tax_rate': <tax rate (redundant)>,
'total': <sum of line item totals>,
}],
# Another [tax_rate, {details}] instance etc.
]
The PDF code in plata.reporting.order
demonstrates how the tax details
might be used when generating an invoice.
Discounts¶
Plata comes with a discount implementation which already implements a wide range of possible discounting strategies. Several factors decide whether a certain discount is active:
- Its
is_active
flag. - The validity period, determined by two dates,
valid_from
andvalid_until
. - The number of allowed uses,
allowed_uses
. An usage example is limiting a discount to the first ten customers using it.
Plata knows three distinct discount types:
- An amount discount (given with tax included or excluded) which is applied to the subtotal. This type reduces the subtotal, the total and the tax amount.
- A percentage discount which is applied to the subtotal. Like the aforementioned amount type, this type reduces the subtotal, the total and the tax amount.
- A discount code usable as a means of payment. This discount type only leads to a reduced total and does not change the subtotal or the tax amount.
Discounts should not always be applicable to all products which can be bought. Because Plata does not come with a product model it also does not come with many strategies for selecting eligible products.
Bundled strategies are:
all
: All products are eligible.exclude_sale
: Order items which have theiris_sale
flag set toTrue
are excluded from discounting.
It’s quite easy to add additional strategies. If you have a category model and only want to allow products from a set of categories for discounting, that’s the code you need:
from django import forms
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from plata.discount.models import DiscountBase
from myapp.models import Category
DiscountBase.CONFIG_OPTIONS.append(('only_categories', {
'title': _('Only products from selected categories'),
'form_fields': [
('categories', forms.ModelMultipleChoiceField(
Category.objects.all(),
label=_('categories'),
required=True,
)),
],
'product_query': lambda categories: Q(categories__in=categories),
}))
A different requirement might be that discounts can only be applied if a product will be bought several times (granted, that’s a bit a stupid idea but it demonstrates how eligible products can be determined by their cart status):
from django import forms
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from plata.discount.models import DiscountBase
from myapp.models import Category
DiscountBase.CONFIG_OPTIONS.append(('only_multiple', {
'title': _('Only products which will be bought several times'),
'orderitem_query': lambda **values: Q(quantity__gt=1),
}))
Discounts are configured with the following variables:
- The key, given as first argument in the
(key, configuration)
tuple. title
: A human-readable title.form_fields
: A list of(field_name, field_instance)
form fields which will be shown in the Django administration. Note: The fields are only visible after saving the discount once.product_query
: The values from the form fields above will be passed as keyword arguments, the return value is passed to afilter()
call on the product model queryset.orderitem_query
: The values frmo the form fields above will be passed as keyword arguments, the return value is passed to afilter()
call on the order items queryset.
form_fields
is optional, one of product_query
and orderitem_query
should always be provided. This is not enforced by the code however.
Shipping¶
This (new and still incomplete) module will provide the calculation of shipping costs, depending on the size and weight of your products.
You can register the tariffs of several shipping providers (postal services) for groups of countries that cost the same postage and several package sizes.
Setup:
- Include plata.shipping in your INSTALLED_APPS.
- Run manage.py makemigrations shipping and manage.py migrate.
- Login to your admin interface and …
- Create some country groups – the first will be your home country, others depend on the tariff groups of your shipping provider, like “European Union”, “World 1”, “World 2” etc.
- Define the countries that belong to these groups.
- Setup the shipping providers you work with, depending on the countries they serve.
- At last it’s the most work to register all the different postage tariffs.
Some providers have maximum sizes for each dimension (length, weidth, height), others calculate by adding these. Some calculate by “tape measure”, i.e. combined length and girth (in German: Gurtmaß), you must translate that to our “3D” sum yourself. Postage.max_size is the smaller of either the sum of length, width and height or max_3d.
Because also the packaging has a considerable weight that depends on the package size, this is also a property of Postage. If you don’t need it, leave it at 0.
Configuration¶
You can change the displayed units, e.g. if someone insists in obsolete non-metric units:
PLATA_SHIPPING_WEIGHT_UNIT = 'g'
PLATA_SHIPPING_LENGTH_UNIT = 'mm'
Database migrations¶
Plata does not include database migrations. The reason for that is that
Django does not support the concept of lazy foreign keys. Because you’re
free to choose any model as product for Plata (configurable using
PLATA_SHOP_PRODUCT
) we do not know beforehand, how the database
constraints for the order item product foreign key should look
beforehand. Because of that it’s easier for everyone to just not include
any migrations and instead provide instructions in the release notes
when a database migration has to be performed.
Note
This isn’t strictly true anymore now that Django supports swappable models. This guide has been written in 2012 long before Django officially supported a swappable user model.
The following issues on Github will shed further light upon this issue:
- https://github.com/matthiask/plata/issues/33
- https://github.com/matthiask/plata/issues/27
- https://github.com/matthiask/plata/issues/26
Note
Despite not bundling database migrations, using them is very much recommended and officially supported. Any pitfalls will be documented here and in the release notes.
Configuring migrations for Plata¶
Since the migrations are project-specific, the migration files should be added to your apps, and not to Plata itself. This can be achieved using the following setting:
MIGRATION_MODULES = {
'contact': 'yourapp.migrate.contact',
'discount': 'yourapp.migrate.discount',
'stock': 'yourapp.migrate.stock', # If you're using stock tracking
'shop': 'yourapp.migrate.shop',
}
You’ll have to add a folder migrate
in your app containing only an
empty __init__.py
file for these instructions to work. After that
run makemigrations
on each Plata app and you’re hopefully done.
Note
You can use any other name for the migrate
folder except for
migrations
.
Examples¶
Plata’s repository contains a few example projects, which are used both to demonstrate specific features and allow experimentation.
The purpose of each example project will be briefly described in the following.
simple
example¶
This example strives to be the simplest shop implementation with Plata while still following best practice.
custom
example¶
The custom example demonstrates how the bundled contact model might be replaced with a custom model – in this case, the replacement model only has one address record, not two addresses, one for shipping and one for billing.
staggered
example¶
This example demonstrates how a staggered (or “tiered”) price scheme might be implemented outside of Plata’s core.
oneprice
example¶
This example demonstrates how a product model might be made even simpler:
By extending ProductBase
and PriceBase
at the same time, all
product related information is kept in one Django model. This (obviously)
means that each product can only have one price.
generic
example¶
Plata accepts only one product model. But you might want to sell widely differing stuff, like physical things and downloads, that need different models. This example shows how to setup a product model with a generic relationship to the real product.
The data entry is somewhat complicated – first create some “Things” and “Downloads”, then edit the “Products” for their prices.
This example also uses the CountryGroup
from plata.shipping
to implement
prices that depend on the customer’s country.
setup for examples¶
- make sure that
examples/manage.py
is configured to use your example of choice, e.g. ‘simple’. - setup basic Django models:
./manage.py migrate
- prepare shop models (important: all at once, otherwise you’ll run into
a dependency hell):
./manage.py makemigrations shop contact discount simple
- setup shop models:
./manage.py migrate
- create an user:
./manage.py createsuperuser
(the examples contain no user management and depend on admin staff users.) - create tax classes, products, discounts etc. at the admin site
If you want to try different examples, you must delete the migrations from shop, contact and discount!
For your convenience, we included two (bash) shell scripts, to be called from within the examples directory of a git checkout.
- reset-migrations.sh lets you delete the migrations from all examples and all plata modules.
- startexample.sh automates the necessary migrations setup and starts one example.
There are some checks and prompts to avoid damage, but of course we can’t be held responsible for anything that happens.
API Documentation¶
Core¶
-
plata.
product_model
()[source] Return the product model defined by the
PLATA_SHOP_PRODUCT
setting.
-
plata.
shop_instance
()[source] This method ensures that all views and URLs are properly loaded, and returns the centrally instantiated
plata.shop.views.Shop
object.
-
plata.
stock_model
()[source] Return the stock transaction model definded by the
PLATA_STOCK_TRACKING_MODEL
setting orNone
in case stock transactions are turned off.
Products¶
Product model base implementation – you do not need to use this
It may save you some typing though.
-
class
plata.product.models.
ProductBase
(*args, **kwargs)[source] Product models must have two methods to be usable with Plata:
get_price
: Return a price instancehandle_order_item
: Fill in fields on the order item from the product, i.e. the name and the stock keeping unit.
-
get_price
(currency=None, orderitem=None)[source] This method is part of the public, required API of products. It returns either a price instance or raises a
DoesNotExist
exception.If you need more complex pricing schemes, override this method with your own implementation.
-
handle_order_item
(orderitem)[source] This method has to ensure that the information on the order item is sufficient for posteriority. Old orders should always be complete even if the products have been changed or deleted in the meantime.
Product extensions¶
Exact, transactional stock tracking for Plata¶
Follow these steps to enable this module:
Ensure your product model has an
items_in_stock
field with the following definiton:items_in_stock = models.IntegerField(default=0)
Add
'plata.product.stock'
toINSTALLED_APPS
.Set
PLATA_STOCK_TRACKING = True
to enable stock tracking in the checkout and payment processes.Optionally modify your add-to-cart forms on product detail pages to take into account
items_in_stock
.
-
class
plata.product.stock.models.
Period
(*args, **kwargs)[source] A period in which stock changes are tracked
You might want to create a new period every year and create initial amount transactions for every variation.
StockTransaction.objects.open_new_period
does this automatically.
-
class
plata.product.stock.models.
StockTransaction
(*args, **kwargs)[source] Stores stock transactions transactionally :-)
Stock transactions basically consist of a product variation reference, an amount, a type and a timestamp. The following types are available:
StockTransaction.INITIAL
: Initial amount, used when filling in the stock databaseStockTransaction.CORRECTION
: Use this for any errorsStockTransaction.PURCHASE
: Product purchase from a supplierStockTransaction.SALE
: Sales, f.e. through the webshopStockTransaction.RETURNS
: Returned products (i.e. from lending)StockTransaction.RESERVATION
: ReservationsStockTransaction.INCOMING
: Generic warehousingStockTransaction.OUTGOING
: Generic warehousingStockTransaction.PAYMENT_PROCESS_RESERVATION
: Product reservation during payment process
Most of these types do not have a significance to Plata. The exceptions are:
INITIAL
transactions are created byopen_new_period
SALE
transactions are created when orders are confirmedPAYMENT_PROCESS_RESERVATION
transactions are created by payment modules which send the user to a different domain for payment data entry (f.e. PayPal). These transactions are also special in that they are only valid for 15 minutes. After 15 minutes, other customers are able to put the product in their cart and proceed to checkout again. This time period is a security measure against customers buying products at the same time which cannot be delivered afterwards because stock isn’t available.
-
plata.product.stock.models.
validate_order_stock_available
(order)[source] Check whether enough stock is available for all selected products, taking into account payment process reservations.
Discounts¶
-
class
plata.discount.models.
AppliedDiscount
(*args, **kwargs)[source] Stores an applied discount, so that deletion of discounts does not affect orders.
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
exception
-
class
plata.discount.models.
AppliedDiscountManager
[source] Default manager for the
AppliedDiscount
model-
remaining
(order=None)[source] Calculate remaining discount excl. tax
Can either be used as related manager:
order.applied_discounts.remaining()
or directly:
AppliedDiscount.objects.remaining(order)
-
-
class
plata.discount.models.
Discount
(id, name, type, value, currency, tax_class, config, code, is_active, valid_from, valid_until, allowed_uses, used)[source] -
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
add_to
(order, recalculate=True)[source] Add discount to passed order
Removes the previous discount if a discount with this code has already been added to the order before.
-
validate
(order)[source] Validate whether this discount can be applied on the given order
-
exception
-
class
plata.discount.models.
DiscountBase
(*args, **kwargs)[source] Base class for discounts and applied discounts
-
CONFIG_OPTIONS
= [(u'all', {u'title': <django.utils.functional.__proxy__ object>}), (u'exclude_sale', {u'orderitem_query': <function <lambda>>, u'title': <django.utils.functional.__proxy__ object>})] You can add and remove options at will, except for ‘all’: This option must always be available, and it cannot have any form fields
-
Shop¶
-
class
plata.shop.models.
BillingShippingAddress
(*args, **kwargs)[source] Abstract base class for all models storing a billing and a shipping address
-
addresses
()[source] Return a
dict
containing a billing and a shipping address, taking into account the value of theshipping_same_as_billing
flag
-
-
class
plata.shop.models.
Order
(*args, **kwargs)[source] The main order model. Used for carts and orders alike.
-
CART
= 10 Order object is a cart.
-
CHECKOUT
= 20 Checkout process has started.
-
COMPLETED
= 50 Order has been completed. Plata itself never sets this state, it is only meant for use by the shop owners.
-
CONFIRMED
= 30 Order has been confirmed, but it not (completely) paid for yet.
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
PAID
= 40 Order has been completely paid for.
-
PENDING
= 35 For invoice payment methods, when waiting for the money
-
VALIDATE_ALL
= 100 This should not be used while registering a validator, it’s mostly useful as an argument to
validate()
when you want to run all validators.
-
VALIDATE_BASE
= 10 This validator is always called; basic consistency checks such as whether the currencies in the order match should be added here.
-
VALIDATE_CART
= 20 A cart which fails the criteria added to the
VALIDATE_CART
group isn’t considered a valid cart and the user cannot proceed to the checkout form. Stuff such as stock checking, minimal order total checking, or maximal items checking might be added here.
-
balance_remaining
Returns the balance which needs to be paid by the customer to fully pay this order. This value is not necessarily the same as the order total, because there can be more than one order payment in principle.
-
discount
Returns the discount total.
-
discount_remaining
Remaining discount amount excl. tax
-
is_confirmed
()[source] Returns
True
if this order has already been confirmed and therefore cannot be modified anymore.
-
items_in_order
()[source] Returns the item count in the order
This is different from
order.items.count()
because it counts items, not distinct products.
-
modify_item
(product, relative=None, absolute=None, recalculate=True, data=None, item=None, force_new=False)[source] Updates order with the given product
relative
orabsolute
: Add/subtract or define order item amount exactlyrecalculate
: Recalculate order after cart modification (defaults toTrue
)data
: Additional data for the order item; replaces the contents of the JSON field if it is notNone
. Pass an empty dictionary if you want to reset the contents.item
: The order item which should be modified. Will be automatically detected using the product if unspecified.force_new
: Force the creation of a new order item, even if the product exists already in the cart (especially useful if the product is configurable).
Returns the
OrderItem
instance; if quantity is zero, the order item instance is deleted, thepk
attribute set toNone
but the order item is returned anyway.
-
order_id
Returns
_order_id
(if it has been set) or a generic ID for this order.
-
recalculate_total
(save=True)[source] Recalculates totals, discounts, taxes.
-
classmethod
register_validator
(validator, group)[source] Registers another order validator in a validation group
A validator is a callable accepting an order (and only an order).
There are several types of order validators:
- Base validators are always called
- Cart validators: Need to validate for a valid cart
- Checkout validators: Need to validate in the checkout process
-
reload
()[source] Return this order instance, reloaded from the database
Used f.e. inside the payment processors when adding new payment records etc.
-
save
(*args, **kwargs)[source] Sequential order IDs for completed orders.
-
shipping
Returns the shipping cost, with or without tax depending on this order’s
price_includes_tax
field.
-
subtotal
Returns the order subtotal.
-
tax
Returns the tax total for this order, meaning tax on order items and tax on shipping.
-
update_status
(status, notes)[source] Update the order status
-
validate
(group)[source] Validates this order
The argument determines which order validators are called:
Order.VALIDATE_BASE
Order.VALIDATE_CART
Order.VALIDATE_CHECKOUT
Order.VALIDATE_ALL
-
-
class
plata.shop.models.
OrderItem
(*args, **kwargs)[source] Single order line item
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
exception
-
class
plata.shop.models.
OrderPayment
(*args, **kwargs)[source] Order payment
Stores additional data from the payment interface for analysis and accountability.
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
exception
-
class
plata.shop.models.
OrderStatus
(*args, **kwargs)[source] Order status
Stored in separate model so that the order status changes stay visible for analysis after the fact.
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
exception
-
class
plata.shop.models.
PriceBase
(*args, **kwargs)[source] Price for a given product, currency, tax class and time period
Prices should not be changed or deleted but replaced by more recent prices. (Deleting old prices does not hurt, but the price history cannot be reconstructed anymore if you’d need it.)
The concrete implementation needs to provide a foreign key to the product model.
-
handle_order_item
(item)[source] Set price data on the
OrderItem
passed
-
-
class
plata.shop.models.
TaxClass
(*args, **kwargs)[source] Tax class, storing a tax rate
TODO informational / advisory currency or country fields?
-
exception
DoesNotExist
-
exception
MultipleObjectsReturned
-
exception
-
plata.shop.models.
validate_order_currencies
(order)[source] Check whether order contains more than one or an invalid currency
Order processors¶
-
class
plata.shop.processors.
ApplyRemainingDiscountToShippingProcessor
(shared_state)[source] Apply the remaining discount to the shipping (if shipping is non-zero and there are any remaining discounts left)
-
class
plata.shop.processors.
DiscountProcessor
(shared_state)[source] Apply all discounts which do not act as a means of payment but instead act on the subtotal
-
class
plata.shop.processors.
FixedAmountShippingProcessor
(shared_state)[source] Set shipping costs to a fixed value. Uses
PLATA_SHIPPING_FIXEDAMOUNT
. If you have differing needs you should probably implement your own shipping processor (and propose it for inclusion if you like) instead of extending this one.PLATA_SHIPPING_FIXEDAMOUNT = { 'cost': Decimal('8.00'), 'tax': Decimal('19.6'), }
-
class
plata.shop.processors.
InitializeOrderProcessor
(shared_state)[source] Zero out all relevant order values and calculate line item prices excl. tax.
-
class
plata.shop.processors.
ItemSummationProcessor
(shared_state)[source] Sum up line item prices, discounts and taxes.
-
class
plata.shop.processors.
MeansOfPaymentDiscountProcessor
(shared_state)[source] Apply all discounts which act as a means of payment.
-
class
plata.shop.processors.
OrderSummationProcessor
(shared_state)[source] Sum up order total by adding up items and shipping totals.
-
process
(order, items)[source] The value must be quantized here, because otherwise f.e. the payment modules will be susceptible to rounding errors giving f.e. missing payments of 0.01 units.
-
-
class
plata.shop.processors.
ProcessorBase
(shared_state)[source] Order processor class base. Offers helper methods for order total aggregation and tax calculation.
-
add_tax_details
(tax_details, tax_rate, price, discount, tax_amount)[source] Add tax details grouped by tax_rate. Especially useful if orders potentially use more than one tax class. These values are not used for the order total calculation – they are only needed to show the tax amount for different tax rates if this is necessary for your invoices.
tax_details
: The tax details dict, most often stored asorder.data['tax_details'] = tax_details.items()
tax_rate
: The tax rate of the current entryprice
: The price excl. taxdiscount
: The discount amount (will be subtracted from the price before applying the tax)tax_amount
: The exact amount; a bit redundant because this could be calculated using the values above as well
See the taxes documentation or the standard invoice PDF generation code if you need to know more about the use of these values.
-
process
(order, items)[source] This is the method which must be implemented in order processor classes.
-
split_cost
(cost_incl_tax, tax_rate)[source] Split a cost incl. tax into the part excl. tax and the tax
-
-
class
plata.shop.processors.
TaxProcessor
(shared_state)[source] Calculate taxes for every line item and aggregate tax details.
-
class
plata.shop.processors.
ZeroShippingProcessor
(shared_state)[source] Set shipping costs to zero.
Template tags¶
-
plata.shop.templatetags.plata_tags.
form_errors
(parser, token)[source] Show all form and formset errors:
{% form_errors form formset1 formset2 %}
Silently ignores non-existant variables.
-
plata.shop.templatetags.plata_tags.
form_item
(item, additional_classes=None)[source] Helper for easy displaying of form items:
{% for field in form %}{% form_item field %}{% endfor %}
-
plata.shop.templatetags.plata_tags.
form_item_plain
(item, additional_classes=None)[source] Helper for easy displaying of form items without any additional tags (table cells or paragraphs) or labels:
{% form_item_plain field %}
-
plata.shop.templatetags.plata_tags.
form_items
(form)[source] Render all form items:
{% form_items form %}
-
plata.shop.templatetags.plata_tags.
load_plata_context
(context)[source] Conditionally run plata’s context processor using {% load_plata_context %}
Rather than having the overheads involved in globally adding it to TEMPLATE_CONTEXT_PROCESSORS.
-
plata.shop.templatetags.plata_tags.
quantity_ordered
(product, order)[source] e.g. {% if product|quantity_ordered:plata.order > 0 %} … {% endif %}
Signals¶
-
plata.shop.signals.
contact_created
= <django.dispatch.dispatcher.Signal object> Emitted upon contact creation. Receives the user and contact instance and the new password in cleartext.
-
plata.shop.signals.
order_confirmed
= <django.dispatch.dispatcher.Signal object> Emitted upon order confirmation. Receives an order instance.
-
plata.shop.signals.
order_paid
= <django.dispatch.dispatcher.Signal object> Emitted when an order has been completely paid for. Receives the order and payment instances and the remaining discount amount excl. tax, if there is any.
Notifications¶
Even though these shop signal handlers might be useful, you might be better off writing your own handlers for the three important signals:
contact_created
: A new contact has been created during the checkout processorder_confirmed
: The order has been confirmed, a payment method has been selectedorder_paid
: The order is fully paid
A real-world example follows:
from django.utils.translation import activate
from plata.shop import notifications, signals as shop_signals
class EmailHandler(notifications.BaseHandler):
ALWAYS = ['shopadmin@example.com']
SHIPPING = ['warehouse@example.com']
def __call__(self, sender, order, **kwargs):
cash_on_delivery = False
try:
if (order.payments.all()[0].payment_module_key == 'cod'):
cash_on_delivery = True
except:
pass
if order.language_code:
activate(order.language_code)
invoice_message = self.create_email_message(
'plata/notifications/order_paid.txt',
order=order,
**kwargs)
invoice_message.attach(order.order_id + '.pdf',
self.invoice_pdf(order), 'application/pdf')
invoice_message.to.append(order.email)
invoice_message.bcc.extend(self.ALWAYS)
packing_slip_message = self.create_email_message(
'plata/notifications/packing_slip.txt',
order=order,
**kwargs)
packing_slip_message.attach(
order.order_id + '-LS.pdf',
self.packing_slip_pdf(order),
'application/pdf')
packing_slip_message.to.extend(self.ALWAYS)
if cash_on_delivery:
invoice_message.bcc.extend(self.SHIPPING)
else:
packing_slip_message.to.extend(self.SHIPPING)
invoice_message.send()
packing_slip_message.send()
shop_signals.contact_created.connect(
notifications.ContactCreatedHandler(),
weak=False)
shop_signals.order_paid.connect(
EmailHandler(),
weak=False)
-
class
plata.shop.notifications.
ContactCreatedHandler
(always_to=None, always_bcc=None)[source] Send an e-mail message to a newly created contact, optionally BCC’ing the addresses passed as
always_bcc
upon handler initialization.Usage:
signals.contact_created.connect( ContactCreatedHandler(), weak=False)
or:
signals.contact_created.connect( ContactCreatedHandler(always_bcc=['owner@example.com']), weak=False)
-
class
plata.shop.notifications.
SendInvoiceHandler
(always_to=None, always_bcc=None)[source] Send an e-mail with attached invoice to the customer after successful order completion, optionally BCC’ing the addresses passed as
always_bcc
to the handler upon initialization.Usage:
signals.order_paid.connect( SendInvoiceHandler(always_bcc=['owner@example.com']), weak=False)
-
class
plata.shop.notifications.
SendPackingSlipHandler
(always_to=None, always_bcc=None)[source] Send an e-mail with attached packing slip to the addresses specified upon handler initialization. You should pass at least one address in either the
always_to
or thealways_bcc
argument, or else the e-mail will go nowhere.Usage:
signals.order_paid.connect( SendPackingSlipHandler(always_to=['warehouse@example.com']), weak=False)
Payment¶
-
class
plata.payment.modules.base.
ProcessorBase
(shop)[source] Payment processor base class
-
already_paid
(order, request=None)[source] Handles the case where a payment module is selected but the order is already completely paid for (f.e. because an amount discount has been used which covers the order).
Does nothing if the order status is
PAID
already.
-
clear_pending_payments
(order)[source] Clear pending payments
-
create_pending_payment
(order)[source] Create a pending payment
-
create_transactions
(order, stage, **kwargs)[source] Create transactions for all order items. The real work is offloaded to
StockTransaction.objects.bulk_create
.
-
default_name
= u'unnamed' Human-readable name for this payment module. You may use i18n here.
-
enabled_for_request
(request)[source] Decides whether this payment module is available for a given request.
Defaults to
True
. If you need to disable payment modules for certain visitors or group of visitors, that is the method you are searching for.
-
get_urls
()[source] Defines URLs for this payment processor
Note that these URLs are added directly to the shop views URLconf without prefixes. It is your responsability to namespace these URLs so they don’t clash with shop views and other payment processors.
-
key
= u'unnamed' Safe key for this payment module (shouldn’t contain special chars, spaces etc.)
-
name
Returns name of this payment module suitable for human consumption
Defaults to
default_name
but can be overridden by placing an entry inPLATA_PAYMENT_MODULE_NAMES
. Example:PLATA_PAYMENT_MODULE_NAMES = { 'paypal': _('Paypal and credit cards'), }
-
order_paid
(order, payment=None, request=None)[source] Call this when the order has been fully paid for.
This method does the following:
- Sets order status to
PAID
. - Calculates the remaining discount amount (if any) and calls the
order_paid
signal. - Clears pending payments which aren’t interesting anymore anyway.
- Sets order status to
-
process_order_confirmed
(request, order)[source] This is the initial entry point of payment modules and is called when the user has selected a payment module and accepted the terms and conditions of the shop.
Must return a response which is presented to the user, i.e. a form with hidden values forwarding the user to the PSP or a redirect to the success page if no further processing is needed.
-
urls
Returns URLconf definitions used by this payment processor
This is especially useful for processors offering server-to-server communication such as Paypal’s IPN (Instant Payment Notification) where Paypal communicates payment success immediately and directly, without involving the client.
Define your own URLs in
get_urls
.
-
Cash on delivery¶
Payment module for cash on delivery handling
Automatically completes every order passed.
Check support¶
Payment module for check/transfer
Author: jpbraun@mandriva.com
Configuration: PLATA_PAYMENT_CHECK_NOTIFICATIONS
PayPal support¶
Payment module for PayPal integration
Needs the following settings to work correctly:
PAYPAL = {
'BUSINESS': 'yourbusiness@paypal.com',
'LIVE': True, # Or False
}
Postfinance support¶
Payment module for Postfinance integration
Needs the following settings to work correctly:
POSTFINANCE = {
'PSPID': 'your_shop_id',
'LIVE': True, # Or False
'SHA1_IN': 'yourhash',
'SHA1_OUT': 'yourotherhash',
}
Ogone support¶
Payment module for Ogone integration
Needs the following settings to work correctly:
OGONE = {
'PSPID': 'your_shop_id',
'LIVE': True, # Or False
'SHA1_IN': 'yourhash',
'SHA1_OUT': 'yourotherhash',
}
Datatrans support¶
Payment module for Datatrans integration
Needs the following settings to work correctly:
DATATRANS = {
'MERCHANT_ID': '1000000000000',
'LIVE': False
}
Billogram support¶
Payson support¶
PagSeguro support¶
Pagseguro payment module for django-plata Authors: alexandre@mandriva.com.br, jpbraun@mandriva.com Date: 03/14/2012
Stripe support¶
Shipping¶
Views¶
-
class
plata.shop.views.
Shop
(contact_model, order_model, discount_model, default_currency=None, **kwargs)[source] Plata’s view and shop processing logic is contained inside this class.
Shop needs a few model classes with relations between them:
- Contact model linking to Django’s auth.user
- Order model with order items and an applied discount model
- Discount model
- Default currency for the shop (if you do not override default_currency in your own Shop subclass)
Example:
shop_instance = Shop(Contact, Order, Discount) urlpatterns = [ url(r'^shop/', include(shop_instance.urls)), ]
-
base_template
= u'base.html' The base template used in all default checkout templates
-
cart
(request, order)[source] Shopping cart view
-
checkout
(request, order)[source] Handles the first step of the checkout process
-
checkout_form
(request, order)[source] Returns the address form used in the first checkout step
-
confirmation
(request, order)[source] Handles the order confirmation and payment module selection checkout step
Hands off processing to the selected payment module if confirmation was successful.
-
confirmation_form
(request, order)[source] Returns the confirmation and payment module selection form
-
contact_from_user
(user)[source] Return the contact object bound to the current user if the user is authenticated. Returns
None
if no contact exists.
-
create_order_for_user
(user, request=None)[source] Creates and returns a new order for the given user.
-
default_currency
(request=None)[source] Return the default currency for instantiating new orders
Override this with your own implementation if you have a multi-currency shop with auto-detection of currencies.
-
discounts
(request, order)[source] Handles the discount code entry page
-
discounts_form
(request, order)[source] Returns the discount form
-
get_context
(request, context, **kwargs)[source] Helper method returning a context dict. Override this if you need additional context variables.
-
get_payment_modules
(request=None)[source] Import and return all payment modules defined in
PLATA_PAYMENT_MODULES
If request is given only applicable modules are loaded.
-
order_from_request
(request, create=False)[source] Instantiate the order instance for the current session. Optionally creates a new order instance if
create=True
.Returns
None
if unable to find an offer.
-
order_new
(request)[source] Forcibly create a new order and redirect user either to the frontpage or to the URL passed as
next
GET parameter
-
order_payment_failure
(request)[source] Handles order payment failures
-
order_payment_pending
(request)[source] Handles order successes for invoice payments where payment is still pending.
-
order_success
(request)[source] Handles order successes (e.g. when an order has been successfully paid for)
-
price_includes_tax
(request=None)[source] Return if the shop should show prices including tax
This returns the PLATA_PRICE_INCLUDES_TAX settings by default and is meant to be overridden by subclassing the Shop.
-
redirect
(url_name, *args, **kwargs)[source] Hook for customizing the redirect function when used as application content
-
render
(request, template, context)[source] Helper which just passes everything on to
django.shortcuts.render
-
render_cart
(request, context)[source] Renders the shopping cart
-
render_cart_empty
(request, context)[source] Renders a cart-is-empty page
-
render_checkout
(request, context)[source] Renders the checkout page
-
render_confirmation
(request, context)[source] Renders the confirmation page
-
render_discounts
(request, context)[source] Renders the discount code entry page
-
reverse_url
(url_name, *args, **kwargs)[source] Hook for customizing the reverse function
-
set_order_on_request
(request, order)[source] Helper method encapsulating the process of setting the current order in the session. Pass
None
if you want to remove any defined order from the session.
-
urls
Property offering access to the Shop-managed URL patterns
-
user_is_authenticated
(user)[source] Overwrite this for custom authentication check. This is needed to support lazysignup
-
plata.shop.views.
cart_not_empty
(order, shop, request, **kwargs)[source] Redirect to cart if later in checkout process and cart empty
-
plata.shop.views.
checkout_process_decorator
(*checks)[source] Calls all passed checkout process decorators in turn:
@checkout_process_decorator(order_already_confirmed, order_cart_validates)
All checkout process decorators are called with the order, the shop instance and the request as keyword arguments. In the future, additional keywords might be added, your decorators should accept
**kwargs
as well for future compatibility.
-
plata.shop.views.
order_already_confirmed
(order, shop, request, **kwargs)[source] Redirect to confirmation or already paid view if the order is already confirmed
-
plata.shop.views.
order_cart_validates
(order, shop, request, **kwargs)[source] Redirect to cart if stock is insufficient and display an error message
-
plata.shop.views.
order_cart_warnings
(order, shop, request, **kwargs)[source] Show warnings in cart, but don’t redirect (meant as a replacement for
order_cart_validates
, but usable on the cart view itself)
-
plata.shop.views.
user_is_authenticated
(order, shop, request, **kwargs)[source] ensure the user is authenticated and redirect to checkout if not
Context processors¶
-
plata.context_processors.
plata_context
(request)[source] Adds a few variables from Plata to the context if they are available:
plata.shop
: The currentplata.shop.views.Shop
instanceplata.order
: The current orderplata.contact
: The current contact instanceplata.price_includes_tax
: Whether prices include tax or not
Fields¶
-
plata.fields.
CurrencyField
(*moreargs, **morekwargs) Field offering all defined currencies
-
class
plata.fields.
JSONField
(verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=<class django.db.models.fields.NOT_PROVIDED>, editable=True, serialize=True, unique_for_date=None, unique_for_month=None, unique_for_year=None, choices=None, help_text=u'', db_column=None, db_tablespace=None, auto_created=False, validators=[], error_messages=None)[source] TextField which transparently serializes/unserializes JSON objects
See: http://www.djangosnippets.org/snippets/1478/
-
from_db_value
(value, expression, connection, context)[source] Convert the input JSON value into python structures, raises django.core.exceptions.ValidationError if the data can’t be converted.
-
get_prep_value
(value)[source] Convert our JSON object to a string before we save
-
to_python
(value)[source] Convert our string value to JSON after we load it from the DB
-
value_to_string
(obj)[source] Extract our value from the passed object and return it in string form
-
Utilities¶
-
plata.utils.
jsonize
(v)[source] Convert the discount configuration into a state in which it can be stored inside the JSON field.
Some information is lost here; f.e. we only store the primary key of model objects, so you have to remember yourself which objects are meant by the primary key values.
Reporting¶
Order reports¶
-
plata.reporting.order.
invoice_pdf
(pdf, order)[source] PDF suitable for use as invoice
-
plata.reporting.order.
packing_slip_pdf
(pdf, order)[source] PDF suitable for use as packing slip
Product reports¶
-
plata.reporting.product.
product_xls
()[source] Create a list of all product variations, including stock and aggregated stock transactions (by type)
Reporting views¶
-
plata.reporting.views.
invoice
(request, *args, **kwargs)[source] Returns the invoice PDF
-
plata.reporting.views.
invoice_pdf
(request, *args, **kwargs)[source] Returns the invoice PDF
-
plata.reporting.views.
packing_slip_pdf
(request, *args, **kwargs)[source] Returns the packing slip PDF
-
plata.reporting.views.
product_xls
(request, *args, **kwargs)[source] Returns an XLS containing product information
Settings¶
The settings here should be accessed using plata.settings.<KEY>
. All
settings can be overridden by putting them in your standard settings.py
module.
-
plata.default_settings.
CURRENCIES
= (u'CHF', u'EUR', u'USD', u'CAD') All available currencies. Use ISO 4217 currency codes in this list only.
-
plata.default_settings.
CURRENCIES_WITHOUT_CENTS
= (u'JPY', u'KRW') If you use currencies that don’t have a minor unit (zero-decimal currencies) At the moment only relevant to Stripe payments.
-
plata.default_settings.
PLATA_ORDER_PROCESSORS
= [u'plata.shop.processors.InitializeOrderProcessor', u'plata.shop.processors.DiscountProcessor', u'plata.shop.processors.TaxProcessor', u'plata.shop.processors.MeansOfPaymentDiscountProcessor', u'plata.shop.processors.ItemSummationProcessor', u'plata.shop.processors.ZeroShippingProcessor', u'plata.shop.processors.OrderSummationProcessor'] List of order processors
Plata does not check whether the selection makes any sense. This is your responsibility.
-
plata.default_settings.
PLATA_PAYMENT_MODULES
= [u'plata.payment.modules.cod.PaymentProcessor', u'plata.payment.modules.paypal.PaymentProcessor'] Activated payment modules
-
plata.default_settings.
PLATA_PAYMENT_MODULE_NAMES
= {} Override payment module names without modifying the payment module code
The key in this dictionary should use the
key
variable of the respective payment module.Example:
PLATA_PAYMENT_MODULE_NAMES = { 'paypal': 'PayPal and Credit Cards', }
-
plata.default_settings.
PLATA_PDF_FONT_NAME
= u'' Custom font for PDF generation
-
plata.default_settings.
PLATA_PRICE_INCLUDES_TAX
= True Are prices shown with tax included or not? (Defaults to
True
) Please note that this setting is purely presentational and has no influence on the values stored in the database.
-
plata.default_settings.
PLATA_REPORTING_ADDRESSLINE
= u'' PDF address line
-
plata.default_settings.
PLATA_REPORTING_STATIONERY
= u'pdfdocument.elements.ExampleStationery' Stationery for invoice and packing slip PDF generation
-
plata.default_settings.
PLATA_SHIPPING_FIXEDAMOUNT
= {u'cost': Decimal('8.00'), u'tax': Decimal('7.6')} FixedAmountShippingProcessor
example configurationThe cost must be specified with tax included.
-
plata.default_settings.
PLATA_SHIPPING_WEIGHT_UNIT
= u'g' shipping.Postage
configuration change this if you insist in obsolete non-metric units
-
plata.default_settings.
PLATA_SHOP_PRODUCT
= u'product.Product' Target of order item product foreign key (Defaults to
'product.Product'
)
-
plata.default_settings.
PLATA_STOCK_TRACKING
= False Transactional stock tracking
'plata.product.stock'
has to be included inINSTALLED_APPS
for this to work.
-
plata.default_settings.
PLATA_ZIP_CODE_LABEL
= <django.utils.functional.__proxy__ object> Since ZIP code is far from universal, and more an L10N than I18N issue: