Skip to content

Commit

Permalink
Replaced tuple (label, value) against structure ExtraEntryLine which
Browse files Browse the repository at this point in the history
contains an extra field data to hold custom data for displaying in
orders and invoices.
  • Loading branch information
jrief committed Feb 28, 2012
1 parent 44ec15d commit 9f96684
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 37 deletions.
50 changes: 35 additions & 15 deletions shop/cart/cart_modifiers_base.py
@@ -1,6 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-




class ExtraEntryLine(object):
"""
This object holds all the data which is added to an item as extra line when
the cart is transformed into an order.
"""
def __init__(self, label, value, data=None):
"""
@param label: A descriptive text about this line useful for the administrator.
@param value: The decimal should be the amount that should get added to
the current subtotal. It can be a negative value.
The price difference for this item.
@param data: A dictionary created by the modifier to store custom data.
TODO: add modifier_classpath: The full qualified class path of the modifier
which created this extra entry line. Useful when reprocessing orders,
so that we know who is able to interpret the ``data`` field.
"""
self.label = label
self.value = value
self.data = data


class BaseCartModifier(object): class BaseCartModifier(object):
""" """
Price modifiers are the cart's counterpart to backends. Price modifiers are the cart's counterpart to backends.
Expand Down Expand Up @@ -72,7 +93,7 @@ def process_cart_item(self, cart_item, state):
""" """
field = self.get_extra_cart_item_price_field(cart_item) field = self.get_extra_cart_item_price_field(cart_item)
if field != None: if field != None:
price = field[1] price = field.value
cart_item.current_total = cart_item.current_total + price cart_item.current_total = cart_item.current_total + price
cart_item.extra_price_fields.append(field) cart_item.extra_price_fields.append(field)
return cart_item return cart_item
Expand All @@ -95,7 +116,7 @@ def process_cart(self, cart, state):
""" """
field = self.get_extra_cart_price_field(cart) field = self.get_extra_cart_price_field(cart)
if field != None: if field != None:
price = field[1] price = field.value
cart.current_total = cart.current_total + price cart.current_total = cart.current_total + price
cart.extra_price_fields.append(field) cart.extra_price_fields.append(field)
return cart return cart
Expand All @@ -106,41 +127,40 @@ def process_cart(self, cart, state):


def get_extra_cart_item_price_field(self, cart_item): def get_extra_cart_item_price_field(self, cart_item):
""" """
Get an extra price field tuple for the current cart_item: Get an extra item line for the current cart_item:
This allows to modify the price easily, simply return a This allows to modify the price easily, simply return an object of
('Label', Decimal('amount')) from an override. This is expected to be type ExtraEntryLine or None if no extra line shall be added.
a tuple. The decimal should be the amount that should get added to the
current subtotal. It can be a negative value.
In case your modifier is based on the current price (for example in In case your modifier is based on the current price (for example in
order to compute value added tax for this cart item only) your order to compute value added tax for this cart item only) your
override can access that price via ``cart_item.current_total``. override can access that price via ``cart_item.current_total``.
A tax modifier would do something like this: A tax modifier would do something like this:
>>> return ('taxes', Decimal(9)) >>> data = { 'foo': 'bar' }
>>> return ExtraEntryLine(label='taxes', value=Decimal(9), data=data)
And a rebate modifier would do something along the lines of: And a rebate modifier would do something along the lines of:
>>> return ('rebate', Decimal(-9)) >>> data = { 'foo': 'bar' }
>>> return ExtraEntryLine(label='rebate', value=Decimal(-9), data=data)
More examples can be found in shop.cart.modifiers.* More examples can be found in shop.cart.modifiers.*
""" """
return None # Does nothing by default return None # Does nothing by default


def get_extra_cart_price_field(self, cart): def get_extra_cart_price_field(self, cart):
""" """
Get an extra price field tuple for the current cart: Get an extra line for the current cart:
This allows to modify the price easily, simply return a This allows to modify the price easily, simply return an object of
('Label', Decimal('amount')) from an override. This is expected to be type ExtraEntryLine or None if no extra line shall be added.
a tuple. The decimal should be the amount that should get added to the
current subtotal. It can be a negative value.
In case your modifier is based on the current price (for example in In case your modifier is based on the current price (for example in
order to compute value added tax for the whole current price) your order to compute value added tax for the whole current price) your
override can access that price via ``cart.current_total``. That is the override can access that price via ``cart.current_total``. That is the
subtotal, updated with all cart modifiers so far) subtotal, updated with all cart modifiers so far)
>>> return ('Taxes total', 19.00) >>> data = { 'foo': 'bar' }
>>> return ExtraEntryLine(label='Taxes total', Decimal(19.00), data=data)
""" """
return None return None
8 changes: 4 additions & 4 deletions shop/cart/modifiers/rebate_modifiers.py
@@ -1,6 +1,6 @@
#-*- coding: utf-8 -*- #-*- coding: utf-8 -*-
from decimal import Decimal from decimal import Decimal
from shop.cart.cart_modifiers_base import BaseCartModifier from shop.cart.cart_modifiers_base import BaseCartModifier, ExtraEntryLine




class BulkRebateModifier(BaseCartModifier): class BulkRebateModifier(BaseCartModifier):
Expand All @@ -16,8 +16,8 @@ def get_extra_cart_item_price_field(self, cart_item):
""" """
REBATE_PERCENTAGE = Decimal('10') REBATE_PERCENTAGE = Decimal('10')
NUMBER_OF_ITEMS_TO_TRIGGER_REBATE = 5 NUMBER_OF_ITEMS_TO_TRIGGER_REBATE = 5
result_tuple = None result = None
if cart_item.quantity >= NUMBER_OF_ITEMS_TO_TRIGGER_REBATE: if cart_item.quantity >= NUMBER_OF_ITEMS_TO_TRIGGER_REBATE:
rebate = (REBATE_PERCENTAGE / 100) * cart_item.line_subtotal rebate = (REBATE_PERCENTAGE / 100) * cart_item.line_subtotal
result_tuple = ('Rebate', -rebate) result = ExtraEntryLine(label='Rebate', value=-rebate)
return result_tuple # Returning None is ok return result # Returning None is ok
8 changes: 3 additions & 5 deletions shop/cart/modifiers/tax_modifiers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from decimal import Decimal from decimal import Decimal
from shop.cart.cart_modifiers_base import BaseCartModifier from shop.cart.cart_modifiers_base import BaseCartModifier, ExtraEntryLine




class TenPercentGlobalTaxModifier(BaseCartModifier): class TenPercentGlobalTaxModifier(BaseCartModifier):
Expand All @@ -19,8 +19,7 @@ def get_extra_cart_price_field(self, cart):
Add a field on cart.extra_price_fields: Add a field on cart.extra_price_fields:
""" """
taxes = (self.TAX_PERCENTAGE / 100) * cart.current_total taxes = (self.TAX_PERCENTAGE / 100) * cart.current_total
result_tuple = ('Taxes total', taxes) return ExtraEntryLine(label='Taxes total', value=taxes)
return result_tuple




class TenPercentPerItemTaxModifier(BaseCartModifier): class TenPercentPerItemTaxModifier(BaseCartModifier):
Expand All @@ -37,5 +36,4 @@ class TenPercentPerItemTaxModifier(BaseCartModifier):
def get_extra_cart_item_price_field(self, cart_item): def get_extra_cart_item_price_field(self, cart_item):
tax_amount = (self.TAX_PERCENTAGE / 100) * cart_item.current_total tax_amount = (self.TAX_PERCENTAGE / 100) * cart_item.current_total


result_tuple = ('Taxes (10%)', tax_amount) return ExtraEntryLine(label='Taxes (10%)', value=tax_amount)
return result_tuple
2 changes: 1 addition & 1 deletion shop/models/defaults/bases.py
Expand Up @@ -267,7 +267,7 @@ def __init__(self, *args, **kwargs):
# That will hold extra fields to display to the user # That will hold extra fields to display to the user
# (ex. taxes, discount) # (ex. taxes, discount)
super(BaseCartItem, self).__init__(*args, **kwargs) super(BaseCartItem, self).__init__(*args, **kwargs)
self.extra_price_fields = [] # list of tuples (label, value) self.extra_price_fields = [] # list of ExtraEntryLine's
# These must not be stored, since their components can be changed # These must not be stored, since their components can be changed
# between sessions / logins etc... # between sessions / logins etc...
self.line_subtotal = Decimal('0.0') self.line_subtotal = Decimal('0.0')
Expand Down
14 changes: 8 additions & 6 deletions shop/models/defaults/managers.py
Expand Up @@ -99,11 +99,12 @@ def create_from_cart(self, cart):
order.save() order.save()


# Let's serialize all the extra price arguments in DB # Let's serialize all the extra price arguments in DB
for label, value in cart.extra_price_fields: for extra_entry in cart.extra_price_fields:
eoi = ExtraOrderPriceField() eoi = ExtraOrderPriceField()
eoi.order = order eoi.order = order
eoi.label = str(label) eoi.label = str(extra_entry.label)
eoi.value = value eoi.value = extra_entry.value
eoi.data = extra_entry.data
eoi.save() eoi.save()


# There, now move on to the order items. # There, now move on to the order items.
Expand All @@ -121,12 +122,13 @@ def create_from_cart(self, cart):
order_item.line_subtotal = item.line_subtotal order_item.line_subtotal = item.line_subtotal
order_item.save() order_item.save()
# For each order item, we save the extra_price_fields to DB # For each order item, we save the extra_price_fields to DB
for label, value in item.extra_price_fields: for extra_entry in item.extra_price_fields:
eoi = ExtraOrderItemPriceField() eoi = ExtraOrderItemPriceField()
eoi.order_item = order_item eoi.order_item = order_item
# Force unicode, in case it has àö... # Force unicode, in case it has àö...
eoi.label = unicode(label) eoi.label = unicode(extra_entry.label)
eoi.value = value eoi.value = extra_entry.value
eoi.data = extra_entry.data
eoi.save() eoi.save()


processing.send(self.model, order=order, cart=cart) processing.send(self.model, order=order, cart=cart)
Expand Down
3 changes: 3 additions & 0 deletions shop/models/ordermodel.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.version import LooseVersion from distutils.version import LooseVersion
from jsonfield.fields import JSONField
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete
Expand Down Expand Up @@ -59,6 +60,7 @@ class ExtraOrderPriceField(models.Model):
order = models.ForeignKey(Order, verbose_name=_('Order')) order = models.ForeignKey(Order, verbose_name=_('Order'))
label = models.CharField(max_length=255, verbose_name=_('Label')) label = models.CharField(max_length=255, verbose_name=_('Label'))
value = CurrencyField(verbose_name=_('Amount')) value = CurrencyField(verbose_name=_('Amount'))
data = JSONField(null=True, blank=True, verbose_name=_('Serialized extra data'))
# Does this represent shipping costs? # Does this represent shipping costs?
is_shipping = models.BooleanField(default=False, editable=False, is_shipping = models.BooleanField(default=False, editable=False,
verbose_name=_('Is shipping')) verbose_name=_('Is shipping'))
Expand All @@ -77,6 +79,7 @@ class ExtraOrderItemPriceField(models.Model):
order_item = models.ForeignKey(OrderItem, verbose_name=_('Order item')) order_item = models.ForeignKey(OrderItem, verbose_name=_('Order item'))
label = models.CharField(max_length=255, verbose_name=_('Label')) label = models.CharField(max_length=255, verbose_name=_('Label'))
value = CurrencyField(verbose_name=_('Amount')) value = CurrencyField(verbose_name=_('Amount'))
data = JSONField(null=True, blank=True, verbose_name=_('Serialized extra data'))


class Meta(object): class Meta(object):
app_label = 'shop' app_label = 'shop'
Expand Down
8 changes: 4 additions & 4 deletions shop/templates/shop/cart.html
Expand Up @@ -35,8 +35,8 @@ <h1>{% trans "Your shopping cart" %}</h1>
{% for extra_price_field in form.instance.extra_price_fields %} {% for extra_price_field in form.instance.extra_price_fields %}
<tr> <tr>
<td colspan="2">&nbsp;</td> <td colspan="2">&nbsp;</td>
<td>{{ extra_price_field.0 }}</td> <td>{{ extra_price_field.label }}</td>
<td>{{ extra_price_field.1 }}</td> <td>{{ extra_price_field.value }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr><td colspan="2">&nbsp;</td><td>{% trans "Line Total" %}:</td><td>{{ form.instance.line_total }}</td></tr> <tr><td colspan="2">&nbsp;</td><td>{% trans "Line Total" %}:</td><td>{{ form.instance.line_total }}</td></tr>
Expand All @@ -49,8 +49,8 @@ <h1>{% trans "Your shopping cart" %}</h1>
{% for extra_price_field in cart.extra_price_fields %} {% for extra_price_field in cart.extra_price_fields %}
<tr> <tr>
<td colspan="2">&nbsp;</td> <td colspan="2">&nbsp;</td>
<td>{{ extra_price_field.0 }}</td> <td>{{ extra_price_field.label }}</td>
<td>{{ extra_price_field.1 }}</td> <td>{{ extra_price_field.value }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr><td colspan="2">&nbsp;</td><td><b>{% trans "Cart Total" %}</b></td><td><b>{{cart.total_price}}</b></td></tr> <tr><td colspan="2">&nbsp;</td><td><b>{% trans "Cart Total" %}</b></td><td><b>{{cart.total_price}}</b></td></tr>
Expand Down
4 changes: 2 additions & 2 deletions shop/tests/cart_modifiers.py
Expand Up @@ -112,7 +112,7 @@ def test_tax_amount_is_correct(self):
modifier = TenPercentPerItemTaxModifier() modifier = TenPercentPerItemTaxModifier()
item = self.MockItem() item = self.MockItem()
field = modifier.get_extra_cart_item_price_field(item) field = modifier.get_extra_cart_item_price_field(item)
self.assertTrue(field[1] == Decimal('10')) self.assertTrue(field.value == Decimal('10'))


def test_tax_amount_is_correct_after_modifier(self): def test_tax_amount_is_correct_after_modifier(self):
modifier = TenPercentPerItemTaxModifier() modifier = TenPercentPerItemTaxModifier()
Expand All @@ -121,4 +121,4 @@ def test_tax_amount_is_correct_after_modifier(self):
item.extra_price_fields.append(previous_option) item.extra_price_fields.append(previous_option)
item.current_total = item.current_total + previous_option[1] item.current_total = item.current_total + previous_option[1]
field = modifier.get_extra_cart_item_price_field(item) field = modifier.get_extra_cart_item_price_field(item)
self.assertTrue(field[1] == Decimal('11')) self.assertTrue(field.value == Decimal('11'))

0 comments on commit 9f96684

Please sign in to comment.