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)