What is basky not?¶
basky is not a full stack e-commerce solution, it only handles the basket part of the ecommerce stack.
There is no products application because nearly all businesses have their own subtle requirements when it comes to products. Whilst being able write software that handles 99% of use cases displays fine talent, often each project will only use a portion of such code, so we decided to leave that part to you; to reduce complexity.
What is basky?¶
basky is lightweight (but robust) basket application for Django projects. basky is essentially a collection of urls, views, forms, signals and middleware. The goal of basky is that you can use it with zero fuss and minimal head scratching.
basky doesn’t care what you put into it, it only cares that the ‘thing’ you put into the basket has two properties
- Name - a unicode string
- Total - a decimal formatted to two places
The experience of using the basket has been modelled on how you would interact with a shopping basket in the real world and the development has been largely lead by designers and clients. With this in mind some of the behavior make seem a little ‘cooky’, but it’s really easy to get your head around.
You can install basky using pip
pip install django-basky
or you can clone/fork it directly from the main basky repository on Github.
Add basky to INSTALLED_APPS in your settings.py file
INSTALLED_APPS = ( # ... 'basky', # ... )
Add basky.middleware.BasketInSessionMiddleware into MIDDLEWARE_CLASSES in your settings.py file:
MIDDLEWARE_CLASSES = ( # ... 'basky.middleware.BasketInSessionMiddleware', # ... )
Add basky.urls into your urls.py file:
urlpatterns = patterns('', url(r'^basket/', include('basky.urls', namespace='basky')), )
The basky namespace is non-negotiable. If the namespace is not basky then tests will fail, the sky will turn blood red and all humanity as you know it will cease to be. You haz had da warnings.
Now do a syncdb to create the basket tables:
Basky comes with a management command that will delete baskets older than settings.BASKY_AGE. You can either call this via cron or whap it into a task queue like Celery. You’ll probably want to enable this as each time a new request is made without a basket in the session a new basket is created.
I am aware how much this approach sucks, and I’ll be implementing a LazyBasket approach for the 1.0 version of basky.
Finally, (and you don’t have to do this), you can add basky.context_processors.basket into the tuple of CONTEXT_PROCESSORS in your settings.py. Adding basky.context_processors.basket will inject a variable into all templates called basket or whatever you’ve set BASKY_SESSION_KEY_NAME to.
TEMPLATE_CONTEXT_PROCESSORS = ( #... 'basky.context_processors.basket' )
If you’ve not added to the tuple of defaults then you may not see this setting in your settings file
And that’s that, you’re set. So let’s jump in with a few examples.
Meet The Basket¶
Registering A Product With The Basket¶
Basky has a registration pattern and this is used to register products with the basket.
If the thing that you want to use in the basket has properties called name and total then you don’t have to register the product with the basket. A default configuration will be applied and you’re good to go.
The default configuration looks this:
class BasketConfig(object): """Basic configuration""" # the property used for the name name = 'name' # the property used to get the total total = 'total' # the form that is used to post information to the basket form = BasketForm
If your model does not have a property called name or total, or you’d like to provide some custom configuration then you need to create a file called basket.py in the same directory as your models.py and it will be automatically found and applied.
A custom configuration may look like this:
class ProductBasketConfig(object):: name = 'catalogue_name' total = 'cost' form = MyBasketForm
Getting The Basket¶
Remember that you put the middleware into your settings? Great, because that’s what ensures that all users have a basket attached to their session. We can access the the basket from the request object like this:
basket = request.session.get('basket')
If you want the basket to be called something other than basket you can set this in the Configuration options.
The basket is easy to interogate, we can see how many items we have in the basket by asking it:
>>> basket.total_items() >>> 0
This will return an integer, and obviously because we’ve not put anything in it, it will return 0.
If you wanted to know how much the total price of all of the items in the basket comes to, just ask it:
>>> basket.total_price() >>> 0.00
This will return a decimal number formatted to two decimal places. Again, we haven’t got anything in the basket so we’ll get a big fat 0.00
To prove this let’s ask the basket to give us all of the items:
>>> basket.basketitem_set.all() >>> 
It returned an empty BasketItem() queryset. Let’s put something in the basket and then ask it again so we can explore the BasketLine()
Adding To The Basket¶
basky doesn’t provide any models, because that bit is up to you – It’ll be largely dependant on the project you’re working in.
Let’s assume that you have the following model:
class SimpleProduct(models.Model): name = models.CharField(max_length=255) price = models.DecimalField(max_digits=8, decimal_places=2) def __unicode__(self): return u'%s' % self.name @property def total(self): return self.price
Let’s also assume that the following object exists:
book = SimpleProduct( name='Colour Theory', price=Decimal('9.99') )
We can add book to the basket easy like this:
>>> basketitem = basket.add(book)
When we add to a basket we will always get a BasketItem object back, it’s up to you if you want to keep it around or not. There’s no reason not to really.
We didn’t specify a quantity so a quantity of one was assumed. A few things happened behind the scenes as well. Firstly two Signals were emitted – pre_add_to_basket and post_add_to_basket. You could use these signals to attach listeners for weird and wonderful product behavior.
Now we have an item in the basket we can once again get some information back from it:
>>> basket.total_items() >>> 1
Awesome, what about price:
>>> basket.total_price() >>> 9.99
Splendid, what about getting the the basketitems? Easy peasy, it’s just the standard Django ORM API with no shenanigans:
>>> basket.basketitem_set.all() >>> ['BasketItem: 1 × Colour Theory']
Well isn’t that smashing. Let’s add another book into the basket:
>>> basketitem = basket.add(book)
Now we have two items
>>> basket.total_items() >>> 2
With a total of 19.98
>>> basket.total_price() >>> 19.98
And the items
>>> basket.items() >>> ['BasketItem: 2 × Colour Theory']
Emptying The Basket¶
Alright this is all getting a bit contrived, let’s wrap this up by removing the book from the basket
>>> basketitem = basket.add(book) >>> basket.remove(basketitem)
So what’s left in the basket?
>>> basket.basketitem_set.all() >>> 
Nothing is left in basket. See, that was nice and easy.
In all this kerfuffle about emptying the basket we missed the emission of some Signals. The first Signals were pre_remove_from_basket and post_remove_from basket – No prizes for guessing when they were sent. These two Signals were sent each time we removed items from the basket.
However, there were also two more Signals that was sent and that was basket_is_now_not_empty and basket_is_now_empty.
basket_is_now_not_empty is sent when someones basket was empty and now isn’t. basket_is_now_empty is sent when someones basket had items, but now doesn’t.