Merge branch 'category_form'

This commit is contained in:
Christopher Neugebauer 2016-03-23 15:05:44 +11:00
commit 2f4ebc22af
14 changed files with 473 additions and 60 deletions

View file

@ -80,3 +80,15 @@ class VoucherAdmin(nested_admin.NestedAdmin):
VoucherDiscountInline, VoucherDiscountInline,
VoucherEnablingConditionInline, VoucherEnablingConditionInline,
] ]
# Enabling conditions
@admin.register(rego.ProductEnablingCondition)
class ProductEnablingConditionAdmin(nested_admin.NestedAdmin):
model = rego.ProductEnablingCondition
# Enabling conditions
@admin.register(rego.CategoryEnablingCondition)
class CategoryEnablingConditionAdmin(nested_admin.NestedAdmin):
model = rego.CategoryEnablingCondition

View file

@ -54,57 +54,91 @@ class CartController(object):
self.cart.time_last_updated = timezone.now() self.cart.time_last_updated = timezone.now()
self.cart.reservation_duration = max(reservations) self.cart.reservation_duration = max(reservations)
def add_to_cart(self, product, quantity): def end_batch(self):
''' Adds _quantity_ of the given _product_ to the cart. Raises ''' Performs operations that occur occur at the end of a batch of
ValidationError if constraints are violated.''' product changes/voucher applications etc. '''
prod = ProductController(product)
# TODO: Check enabling conditions for product for user
if not prod.can_add_with_enabling_conditions(self.cart.user, quantity):
raise ValidationError("Not enough of that product left (ec)")
if not prod.user_can_add_within_limit(self.cart.user, quantity):
raise ValidationError("Not enough of that product left (user)")
try:
# Try to update an existing item within this cart if possible.
product_item = rego.ProductItem.objects.get(
cart=self.cart,
product=product)
product_item.quantity += quantity
except ObjectDoesNotExist:
product_item = rego.ProductItem.objects.create(
cart=self.cart,
product=product,
quantity=quantity,
)
product_item.save()
self.recalculate_discounts() self.recalculate_discounts()
self.extend_reservation() self.extend_reservation()
self.cart.revision += 1 self.cart.revision += 1
self.cart.save() self.cart.save()
def apply_voucher(self, voucher): def set_quantity(self, product, quantity, batched=False):
''' Applies the given voucher to this cart. ''' ''' Sets the _quantity_ of the given _product_ in the cart to the given
_quantity_. '''
if quantity < 0:
raise ValidationError("Cannot have fewer than 0 items in cart.")
try:
product_item = rego.ProductItem.objects.get(
cart=self.cart,
product=product)
old_quantity = product_item.quantity
if quantity == 0:
product_item.delete()
return
except ObjectDoesNotExist:
if quantity == 0:
return
product_item = rego.ProductItem.objects.create(
cart=self.cart,
product=product,
quantity=0,
)
old_quantity = 0
# Validate the addition to the cart
adjustment = quantity - old_quantity
prod = ProductController(product)
if not prod.can_add_with_enabling_conditions(
self.cart.user, adjustment):
raise ValidationError("Not enough of that product left (ec)")
if not prod.user_can_add_within_limit(self.cart.user, adjustment):
raise ValidationError("Not enough of that product left (user)")
product_item.quantity = quantity
product_item.save()
if not batched:
self.end_batch()
def add_to_cart(self, product, quantity):
''' Adds _quantity_ of the given _product_ to the cart. Raises
ValidationError if constraints are violated.'''
try:
product_item = rego.ProductItem.objects.get(
cart=self.cart,
product=product)
old_quantity = product_item.quantity
except ObjectDoesNotExist:
old_quantity = 0
self.set_quantity(product, old_quantity + quantity)
def apply_voucher(self, voucher_code):
''' Applies the voucher with the given code to this cart. '''
# TODO: is it valid for a cart to re-add a voucher that they have? # TODO: is it valid for a cart to re-add a voucher that they have?
# Is voucher exhausted? # Is voucher exhausted?
active_carts = rego.Cart.reserved_carts() active_carts = rego.Cart.reserved_carts()
# Try and find the voucher
voucher = rego.Voucher.objects.get(code=voucher_code.upper())
carts_with_voucher = active_carts.filter(vouchers=voucher) carts_with_voucher = active_carts.filter(vouchers=voucher)
if len(carts_with_voucher) >= voucher.limit: if len(carts_with_voucher) >= voucher.limit:
raise ValidationError("This voucher is no longer available") raise ValidationError("This voucher is no longer available")
# If successful... # If successful...
self.cart.vouchers.add(voucher) self.cart.vouchers.add(voucher)
self.end_batch()
self.extend_reservation()
self.cart.revision += 1
self.cart.save()
def validate_cart(self): def validate_cart(self):
''' Determines whether the status of the current cart is valid; ''' Determines whether the status of the current cart is valid;
@ -153,7 +187,12 @@ class CartController(object):
# Delete the existing entries. # Delete the existing entries.
rego.DiscountItem.objects.filter(cart=self.cart).delete() rego.DiscountItem.objects.filter(cart=self.cart).delete()
for item in self.cart.productitem_set.all(): # The highest-value discounts will apply to the highest-value
# products first.
product_items = self.cart.productitem_set.all()
product_items = product_items.order_by('product__price')
product_items = reversed(product_items)
for item in product_items:
self._add_discount(item.product, item.quantity) self._add_discount(item.product, item.quantity)
def _add_discount(self, product, quantity): def _add_discount(self, product, quantity):
@ -172,9 +211,7 @@ class CartController(object):
# Get the count of past uses of this discount condition # Get the count of past uses of this discount condition
# as this affects the total amount we're allowed to use now. # as this affects the total amount we're allowed to use now.
past_uses = rego.DiscountItem.objects.filter( past_uses = rego.DiscountItem.objects.filter(
cart__active=False,
discount=discount.discount, discount=discount.discount,
product=product,
) )
agg = past_uses.aggregate(Sum("quantity")) agg = past_uses.aggregate(Sum("quantity"))
past_uses = agg["quantity__sum"] past_uses = agg["quantity__sum"]

View file

@ -59,20 +59,23 @@ class InvoiceController(object):
# TODO: calculate line items. # TODO: calculate line items.
product_items = rego.ProductItem.objects.filter(cart=cart) product_items = rego.ProductItem.objects.filter(cart=cart)
product_items = product_items.order_by(
"product__category__order", "product__order"
)
discount_items = rego.DiscountItem.objects.filter(cart=cart) discount_items = rego.DiscountItem.objects.filter(cart=cart)
invoice_value = Decimal() invoice_value = Decimal()
for item in product_items: for item in product_items:
product = item.product
line_item = rego.LineItem.objects.create( line_item = rego.LineItem.objects.create(
invoice=invoice, invoice=invoice,
description=item.product.name, description="%s - %s" % (product.category.name, product.name),
quantity=item.quantity, quantity=item.quantity,
price=item.product.price, price=product.price,
) )
line_item.save() line_item.save()
invoice_value += line_item.quantity * line_item.price invoice_value += line_item.quantity * line_item.price
for item in discount_items: for item in discount_items:
line_item = rego.LineItem.objects.create( line_item = rego.LineItem.objects.create(
invoice=invoice, invoice=invoice,
description=item.discount.description, description=item.discount.description,

55
registrasion/forms.py Normal file
View file

@ -0,0 +1,55 @@
import models as rego
from django import forms
def CategoryForm(category):
PREFIX = "product_"
def field_name(product):
return PREFIX + ("%d" % product.id)
class _CategoryForm(forms.Form):
@staticmethod
def initial_data(product_quantities):
''' Prepares initial data for an instance of this form.
product_quantities is a sequence of (product,quantity) tuples '''
initial = {}
for product, quantity in product_quantities:
initial[field_name(product)] = quantity
return initial
def product_quantities(self):
''' Yields a sequence of (product, quantity) tuples from the
cleaned form data. '''
for name, value in self.cleaned_data.items():
if name.startswith(PREFIX):
product_id = int(name[len(PREFIX):])
yield (product_id, value, name)
def disable_product(self, product):
''' Removes a given product from this form. '''
del self.fields[field_name(product)]
products = rego.Product.objects.filter(category=category).order_by("order")
for product in products:
help_text = "$%d -- %s" % (product.price, product.description)
field = forms.IntegerField(
label=product.name,
help_text=help_text,
)
_CategoryForm.base_fields[field_name(product)] = field
return _CategoryForm
class VoucherForm(forms.Form):
voucher = forms.CharField(
label="Voucher code",
help_text="If you have a voucher code, enter it here",
required=True,
)

View file

@ -9,6 +9,8 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
replaces = [('registrasion', '0001_initial'), ('registrasion', '0002_auto_20160304_1723')]
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@ -225,12 +227,12 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='enablingconditionbase', model_name='enablingconditionbase',
name='categories', name='categories',
field=models.ManyToManyField(to=b'registrasion.Category'), field=models.ManyToManyField(to=b'registrasion.Category', blank=True),
), ),
migrations.AddField( migrations.AddField(
model_name='enablingconditionbase', model_name='enablingconditionbase',
name='products', name='products',
field=models.ManyToManyField(to=b'registrasion.Product'), field=models.ManyToManyField(to=b'registrasion.Product', blank=True),
), ),
migrations.AddField( migrations.AddField(
model_name='discountitem', model_name='discountitem',

View file

@ -99,6 +99,11 @@ class Voucher(models.Model):
def __str__(self): def __str__(self):
return "Voucher for %s" % self.recipient return "Voucher for %s" % self.recipient
def save(self, *a, **k):
''' Normalise the voucher code to be uppercase '''
self.code = self.code.upper()
super(Voucher, self).save(*a, **k)
recipient = models.CharField(max_length=64, verbose_name=_("Recipient")) recipient = models.CharField(max_length=64, verbose_name=_("Recipient"))
code = models.CharField(max_length=16, code = models.CharField(max_length=16,
unique=True, unique=True,
@ -149,13 +154,13 @@ class DiscountForProduct(models.Model):
cats = DiscountForCategory.objects.filter( cats = DiscountForCategory.objects.filter(
discount=self.discount, discount=self.discount,
category=self.product.category) category=self.product.category)
if len(prods) > 1 or self not in prods: if len(prods) > 1:
raise ValidationError( raise ValidationError(
_("You may only have one discount line per product")) _("You may only have one discount line per product"))
if len(cats) != 0: if len(cats) != 0:
raise ValidationError( raise ValidationError(
_("You may only have one discount for " _("You may only have one discount for "
"a product or its category")) "a product or its category"))
discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE) discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE)
@ -184,8 +189,8 @@ class DiscountForCategory(models.Model):
if len(prods) != 0: if len(prods) != 0:
raise ValidationError( raise ValidationError(
_("You may only have one discount for " _("You may only have one discount for "
"a product or its category")) "a product or its category"))
if len(cats) > 1 or self not in cats: if len(cats) > 1:
raise ValidationError( raise ValidationError(
_("You may only have one discount line per category")) _("You may only have one discount line per category"))
@ -257,8 +262,8 @@ class EnablingConditionBase(models.Model):
description = models.CharField(max_length=255) description = models.CharField(max_length=255)
mandatory = models.BooleanField(default=False) mandatory = models.BooleanField(default=False)
products = models.ManyToManyField(Product) products = models.ManyToManyField(Product, blank=True)
categories = models.ManyToManyField(Category) categories = models.ManyToManyField(Category, blank=True)
class TimeOrStockLimitEnablingCondition(EnablingConditionBase): class TimeOrStockLimitEnablingCondition(EnablingConditionBase):

View file

@ -0,0 +1,37 @@
<!--- Sample template. Move elsewhere once it's ready to go. -->
{% extends "site_base.html" %}
{% block body %}
<h1>Invoice {{ invoice.id }}</h1>
<ul>
<li>Void: {{ invoice.void }}</li>
<li>Paid: {{ invoice.paid }}</li>
</ul>
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price/Unit</th>
<th>Total</th>
</tr>
{% for line_item in invoice.lineitem_set.all %}
<tr>
<td>{{line_item.description}}</td>
<td>{{line_item.quantity}}</td>
<td>{{line_item.price}}</td>
<td><!-- multiply --> FIXME</td>
</tr>
{% endfor %}
<tr>
<th>TOTAL</th>
<td></td>
<td></td>
<td>{{ invoice.value }}</td>
</tr>
</table>
{% endblock %}

View file

@ -0,0 +1,28 @@
<!--- Sample template. Move elsewhere once it's ready to go. -->
{% extends "site_base.html" %}
{% block body %}
<h1>Product Category: {{ category.name }}</h1>
<p>{{ category.description }}</p>
<form method="post" action="">
{% csrf_token %}
<table>
{{ voucher_form }}
</table>
<input type="submit">
<table>
{{ form }}
</table>
<input type="submit">
</form>
{% endblock %}

View file

@ -3,6 +3,7 @@ import pytz
from decimal import Decimal from decimal import Decimal
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
@ -81,7 +82,18 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
limit_per_user=10, limit_per_user=10,
order=10, order=10,
) )
cls.PROD_2.save() cls.PROD_3.save()
cls.PROD_4 = rego.Product.objects.create(
name="Product 4",
description="This is a test product. It costs $5. "
"A user may have 10 of them.",
category=cls.CAT_2,
price=Decimal("5.00"),
limit_per_user=10,
order=10,
)
cls.PROD_4.save()
@classmethod @classmethod
def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
@ -159,6 +171,38 @@ class BasicCartTests(RegistrationCartTestCase):
item = items[0] item = items[0]
self.assertEquals(2, item.quantity) self.assertEquals(2, item.quantity)
def test_set_quantity(self):
current_cart = CartController.for_user(self.USER_1)
def get_item():
return rego.ProductItem.objects.get(
cart=current_cart.cart,
product=self.PROD_1)
current_cart.set_quantity(self.PROD_1, 1)
self.assertEqual(1, get_item().quantity)
# Setting the quantity to zero should remove the entry from the cart.
current_cart.set_quantity(self.PROD_1, 0)
with self.assertRaises(ObjectDoesNotExist):
get_item()
current_cart.set_quantity(self.PROD_1, 9)
self.assertEqual(9, get_item().quantity)
with self.assertRaises(ValidationError):
current_cart.set_quantity(self.PROD_1, 11)
self.assertEqual(9, get_item().quantity)
with self.assertRaises(ValidationError):
current_cart.set_quantity(self.PROD_1, -1)
self.assertEqual(9, get_item().quantity)
current_cart.set_quantity(self.PROD_1, 2)
self.assertEqual(2, get_item().quantity)
def test_add_to_cart_per_user_limit(self): def test_add_to_cart_per_user_limit(self):
current_cart = CartController.for_user(self.USER_1) current_cart = CartController.for_user(self.USER_1)

View file

@ -29,7 +29,10 @@ class DiscountTestCase(RegistrationCartTestCase):
return discount return discount
@classmethod @classmethod
def add_discount_prod_1_includes_cat_2(cls, amount=Decimal(100)): def add_discount_prod_1_includes_cat_2(
cls,
amount=Decimal(100),
quantity=2):
discount = rego.IncludedProductDiscount.objects.create( discount = rego.IncludedProductDiscount.objects.create(
description="PROD_1 includes CAT_2 " + str(amount) + "%", description="PROD_1 includes CAT_2 " + str(amount) + "%",
) )
@ -40,7 +43,7 @@ class DiscountTestCase(RegistrationCartTestCase):
discount=discount, discount=discount,
category=cls.CAT_2, category=cls.CAT_2,
percentage=amount, percentage=amount,
quantity=2 quantity=quantity,
).save() ).save()
return discount return discount
@ -169,3 +172,31 @@ class DiscountTestCase(RegistrationCartTestCase):
discount_items = list(cart.cart.discountitem_set.all()) discount_items = list(cart.cart.discountitem_set.all())
self.assertEqual(2, discount_items[0].quantity) self.assertEqual(2, discount_items[0].quantity)
def test_category_discount_applies_once_per_category(self):
self.add_discount_prod_1_includes_cat_2(quantity=1)
cart = CartController.for_user(self.USER_1)
cart.add_to_cart(self.PROD_1, 1)
# Add two items from category 2
cart.add_to_cart(self.PROD_3, 1)
cart.add_to_cart(self.PROD_4, 1)
discount_items = list(cart.cart.discountitem_set.all())
# There is one discount, and it should apply to one item.
self.assertEqual(1, len(discount_items))
self.assertEqual(1, discount_items[0].quantity)
def test_category_discount_applies_to_highest_value(self):
self.add_discount_prod_1_includes_cat_2(quantity=1)
cart = CartController.for_user(self.USER_1)
cart.add_to_cart(self.PROD_1, 1)
# Add two items from category 2, add the less expensive one first
cart.add_to_cart(self.PROD_4, 1)
cart.add_to_cart(self.PROD_3, 1)
discount_items = list(cart.cart.discountitem_set.all())
# There is one discount, and it should apply to the more expensive.
self.assertEqual(1, len(discount_items))
self.assertEqual(self.PROD_3, discount_items[0].product)

View file

@ -91,7 +91,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
).save() ).save()
current_cart = CartController.for_user(self.USER_1) current_cart = CartController.for_user(self.USER_1)
current_cart.apply_voucher(voucher) current_cart.apply_voucher(voucher.code)
# Should be able to create an invoice after the product is added # Should be able to create an invoice after the product is added
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)

View file

@ -3,6 +3,7 @@ import pytz
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import IntegrityError
from registrasion import models as rego from registrasion import models as rego
from registrasion.controllers.cart import CartController from registrasion.controllers.cart import CartController
@ -15,10 +16,10 @@ UTC = pytz.timezone('UTC')
class VoucherTestCases(RegistrationCartTestCase): class VoucherTestCases(RegistrationCartTestCase):
@classmethod @classmethod
def new_voucher(self): def new_voucher(self, code="VOUCHER"):
voucher = rego.Voucher.objects.create( voucher = rego.Voucher.objects.create(
recipient="Voucher recipient", recipient="Voucher recipient",
code="VOUCHER", code=code,
limit=1 limit=1
) )
voucher.save() voucher.save()
@ -30,18 +31,18 @@ class VoucherTestCases(RegistrationCartTestCase):
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC)) self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
cart_1 = CartController.for_user(self.USER_1) cart_1 = CartController.for_user(self.USER_1)
cart_1.apply_voucher(voucher) cart_1.apply_voucher(voucher.code)
self.assertIn(voucher, cart_1.cart.vouchers.all()) self.assertIn(voucher, cart_1.cart.vouchers.all())
# Second user should not be able to apply this voucher (it's exhausted) # Second user should not be able to apply this voucher (it's exhausted)
cart_2 = CartController.for_user(self.USER_2) cart_2 = CartController.for_user(self.USER_2)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
cart_2.apply_voucher(voucher) cart_2.apply_voucher(voucher.code)
# After the reservation duration # After the reservation duration
# user 2 should be able to apply voucher # user 2 should be able to apply voucher
self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2) self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
cart_2.apply_voucher(voucher) cart_2.apply_voucher(voucher.code)
cart_2.cart.active = False cart_2.cart.active = False
cart_2.cart.save() cart_2.cart.save()
@ -49,7 +50,7 @@ class VoucherTestCases(RegistrationCartTestCase):
# voucher, as user 2 has paid for their cart. # voucher, as user 2 has paid for their cart.
self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2) self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
cart_1.apply_voucher(voucher) cart_1.apply_voucher(voucher.code)
def test_voucher_enables_item(self): def test_voucher_enables_item(self):
voucher = self.new_voucher() voucher = self.new_voucher()
@ -69,7 +70,7 @@ class VoucherTestCases(RegistrationCartTestCase):
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)
# Apply the voucher # Apply the voucher
current_cart.apply_voucher(voucher) current_cart.apply_voucher(voucher.code)
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)
def test_voucher_enables_discount(self): def test_voucher_enables_discount(self):
@ -89,6 +90,20 @@ class VoucherTestCases(RegistrationCartTestCase):
# Having PROD_1 in place should add a discount # Having PROD_1 in place should add a discount
current_cart = CartController.for_user(self.USER_1) current_cart = CartController.for_user(self.USER_1)
current_cart.apply_voucher(voucher) current_cart.apply_voucher(voucher.code)
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)
self.assertEqual(1, len(current_cart.cart.discountitem_set.all())) self.assertEqual(1, len(current_cart.cart.discountitem_set.all()))
def test_voucher_codes_unique(self):
voucher1 = self.new_voucher(code="VOUCHER")
with self.assertRaises(IntegrityError):
voucher2 = self.new_voucher(code="VOUCHER")
def test_multiple_vouchers_work(self):
voucher1 = self.new_voucher(code="VOUCHER1")
voucher2 = self.new_voucher(code="VOUCHER2")
def test_vouchers_case_insensitive(self):
voucher = self.new_voucher(code="VOUCHeR")
current_cart = CartController.for_user(self.USER_1)
current_cart.apply_voucher(voucher.code.lower())

9
registrasion/urls.py Normal file
View file

@ -0,0 +1,9 @@
from django.conf.urls import url, patterns
urlpatterns = patterns(
"registrasion.views",
url(r"^category/([0-9]+)$", "product_category", name="product_category"),
url(r"^checkout$", "checkout", name="checkout"),
url(r"^invoice/([0-9]+)$", "invoice", name="invoice"),
url(r"^invoice/([0-9]+)/pay$", "pay_invoice", name="pay_invoice"),
)

135
registrasion/views.py Normal file
View file

@ -0,0 +1,135 @@
from registrasion import forms
from registrasion import models as rego
from registrasion.controllers.cart import CartController
from registrasion.controllers.invoice import InvoiceController
from registrasion.controllers.product import ProductController
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.db import transaction
from django.shortcuts import redirect
from django.shortcuts import render
@login_required
def product_category(request, category_id):
''' Registration selections form for a specific category of items '''
PRODUCTS_FORM_PREFIX = "products"
VOUCHERS_FORM_PREFIX = "vouchers"
category_id = int(category_id) # Routing is [0-9]+
category = rego.Category.objects.get(pk=category_id)
current_cart = CartController.for_user(request.user)
CategoryForm = forms.CategoryForm(category)
products = rego.Product.objects.filter(category=category)
products = products.order_by("order")
if request.method == "POST":
cat_form = CategoryForm(request.POST, request.FILES, prefix=PRODUCTS_FORM_PREFIX)
voucher_form = forms.VoucherForm(request.POST, prefix=VOUCHERS_FORM_PREFIX)
if voucher_form.is_valid():
# Apply voucher
# leave
voucher = voucher_form.cleaned_data["voucher"]
try:
current_cart.apply_voucher(voucher)
except Exception as e:
voucher_form.add_error("voucher", e)
elif cat_form.is_valid():
try:
with transaction.atomic():
for product_id, quantity, field_name \
in cat_form.product_quantities():
product = rego.Product.objects.get(pk=product_id)
try:
current_cart.set_quantity(
product, quantity, batched=True)
except ValidationError as ve:
cat_form.add_error(field_name, ve)
if cat_form.errors:
raise ValidationError("Cannot add that stuff")
current_cart.end_batch()
except ValidationError as ve:
pass
else:
# Create initial data for each of products in category
items = rego.ProductItem.objects.filter(
product__category=category,
cart=current_cart.cart,
)
quantities = []
for product in products:
# Only add items that are enabled.
prod = ProductController(product)
try:
quantity = items.get(product=product).quantity
except ObjectDoesNotExist:
quantity = 0
quantities.append((product, quantity))
initial = CategoryForm.initial_data(quantities)
cat_form = CategoryForm(prefix=PRODUCTS_FORM_PREFIX, initial=initial)
voucher_form = forms.VoucherForm(prefix=VOUCHERS_FORM_PREFIX)
for product in products:
# Remove fields that do not have an enabling condition.
prod = ProductController(product)
if not prod.can_add_with_enabling_conditions(request.user, 0):
cat_form.disable_product(product)
data = {
"category": category,
"form": cat_form,
"voucher_form": voucher_form,
}
return render(request, "product_category.html", data)
@login_required
def checkout(request):
''' Runs checkout for the current cart of items, ideally generating an
invoice. '''
current_cart = CartController.for_user(request.user)
current_invoice = InvoiceController.for_cart(current_cart.cart)
return redirect("invoice", current_invoice.invoice.id)
@login_required
def invoice(request, invoice_id):
''' Displays an invoice for a given invoice id. '''
invoice_id = int(invoice_id)
inv = rego.Invoice.objects.get(pk=invoice_id)
current_invoice = InvoiceController(inv)
data = {
"invoice": current_invoice.invoice,
}
return render(request, "invoice.html", data)
@login_required
def pay_invoice(request, invoice_id):
''' Marks the invoice with the given invoice id as paid.
WORK IN PROGRESS FUNCTION. Must be replaced with real payment workflow.
'''
invoice_id = int(invoice_id)
inv = rego.Invoice.objects.get(pk=invoice_id)
current_invoice = InvoiceController(inv)
if not inv.paid and not current_invoice.is_valid():
current_invoice.pay("Demo invoice payment", inv.value)
return redirect("invoice", current_invoice.invoice.id)