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.3, and will not work with any earlier version.
You can download a stable release of plata using pip:
$ sudo pip install 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/matthiask/plata.git
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.
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 f.e. 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'
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 syncdb after you’ve added the required modules to INSTALLED_APPS.
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
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.)
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.defaults import *
from myapp.views import shop
urlpatterns = patterns('',
url(r'^shop/', include(shop.urls)),
url(r'^products/$', 'myapp.views.product_list',
name='plata_product_list'),
url(r'^products/(?P<slug>[-\w]+)/$', 'myapp.views.product_detail',
name='plata_product_detail'),
)
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)