Changeset - c2400c4695ae
[Not reviewed]
4 6 5
Christopher Neugebauer - 9 years ago 2016-01-22 05:29:41
chrisjrn@gmail.com
Moves the controller modules into their own subpackage. There's going to be a lot of stuff in there.
11 files changed with 9 insertions and 9 deletions:
0 comments (0 inline, 0 general)
registrasion/controllers/__init__.py
Show inline comments
 
new file 100644
registrasion/controllers/cart.py
Show inline comments
 
file renamed from registrasion/cart.py to registrasion/controllers/cart.py
 
import datetime
 
import itertools
 

	
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.db.models import Avg, Min, Max, Sum
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 

	
 
from conditions import ConditionController
 
from controllers import ProductController
 
from product import ProductController
 

	
 

	
 
class CartController(object):
 

	
 
    def __init__(self, cart):
 
        self.cart = cart
 

	
 
    @staticmethod
 
    def for_user(user):
 
        ''' Returns the user's current cart, or creates a new cart
 
        if there isn't one ready yet. '''
 

	
 
        try:
 
            existing = rego.Cart.objects.get(user=user, active=True)
 
        except ObjectDoesNotExist:
 
            existing = rego.Cart.objects.create(
 
                user=user,
 
                time_last_updated=timezone.now(),
 
                reservation_duration=datetime.timedelta(),
 
                 )
 
            existing.save()
 
        return CartController(existing)
 

	
 

	
 
    def extend_reservation(self):
 
        ''' Updates the cart's time last updated value, which is used to
 
        determine whether the cart has reserved the items and discounts it
 
        holds. '''
 

	
 
        reservations = [datetime.timedelta()]
 

	
 
        # If we have vouchers, we're entitled to an hour at minimum.
 
        if len(self.cart.vouchers.all()) >= 1:
 
            reservations.append(rego.Voucher.RESERVATION_DURATION)
 

	
 
        # Else, it's the maximum of the included products
 
        items = rego.ProductItem.objects.filter(cart=self.cart)
 
        agg = items.aggregate(Max("product__reservation_duration"))
 
        product_max = agg["product__reservation_duration__max"]
 

	
 
        if product_max is not None:
 
            reservations.append(product_max)
 

	
 
        self.cart.time_last_updated = timezone.now()
 
        self.cart.reservation_duration = max(reservations)
 

	
 

	
 
    def add_to_cart(self, product, quantity):
...
 
@@ -161,50 +161,50 @@ class CartController(object):
 
            self._add_discount(item.product, item.quantity)
 

	
 

	
 
    def _add_discount(self, product, quantity):
 
        ''' Calculates the best available discounts for this product.
 
        NB this will be super-inefficient in aggregate because discounts will be
 
        re-tested for each product. We should work on that.'''
 

	
 
        prod = ProductController(product)
 
        discounts = prod.available_discounts(self.cart.user)
 
        discounts.sort(key=lambda discount: discount.value)
 

	
 
        for discount in reversed(discounts):
 
            if quantity == 0:
 
                break
 

	
 
            # Get the count of past uses of this discount condition
 
            # as this affects the total amount we're allowed to use now.
 
            past_uses = rego.DiscountItem.objects.filter(
 
                cart__active=False,
 
                discount=discount.discount,
 
                product=product,
 
            )
 
            agg = past_uses.aggregate(Sum("quantity"))
 
            past_uses = agg["quantity__sum"]
 
            if past_uses is None:
 
                past_uses = 0
 
            if past_uses == discount.condition.quantity:
 
                continue
 

	
 
            # Get a provisional instance for this DiscountItem
 
            # with the quantity set to as much as we have in the cart
 
            discount_item = rego.DiscountItem.objects.create(
 
                product=product,
 
                cart=self.cart,
 
                discount=discount.discount,
 
                quantity=quantity,
 
            )
 

	
 
            # Truncate the quantity for this DiscountItem if we exceed quantity
 
            ours = discount_item.quantity
 
            allowed = discount.condition.quantity - past_uses
 
            if ours > allowed:
 
                discount_item.quantity = allowed
 
                # Update the remaining quantity.
 
                quantity = ours - allowed
 
            else:
 
                quantity = 0
 
                
 

 
            discount_item.save()
registrasion/controllers/conditions.py
Show inline comments
 
file renamed from registrasion/conditions.py to registrasion/controllers/conditions.py
registrasion/controllers/invoice.py
Show inline comments
 
file renamed from registrasion/invoice.py to registrasion/controllers/invoice.py
registrasion/controllers/product.py
Show inline comments
 
file renamed from registrasion/controllers.py to registrasion/controllers/product.py
registrasion/tests/test_cart.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.controllers.cart import CartController
 

	
 
from patch_datetime import SetTimeMixin
 

	
 
UTC = pytz.timezone('UTC')
 

	
 
class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

	
 
    def setUp(self):
 
        super(RegistrationCartTestCase, self).setUp()
 

	
 
    @classmethod
 
    def setUpTestData(cls):
 
        cls.USER_1 = User.objects.create_user(username='testuser',
 
            email='test@example.com', password='top_secret')
 

	
 
        cls.USER_2 = User.objects.create_user(username='testuser2',
 
            email='test2@example.com', password='top_secret')
 

	
 
        cls.CAT_1 = rego.Category.objects.create(
 
            name="Category 1",
 
            description="This is a test category",
 
            order=10,
 
            render_type=rego.Category.RENDER_TYPE_RADIO,
 
        )
 
        cls.CAT_1.save()
 

	
 
        cls.CAT_2 = rego.Category.objects.create(
 
            name="Category 2",
 
            description="This is a test category",
 
            order=10,
 
            render_type=rego.Category.RENDER_TYPE_RADIO,
 
        )
 
        cls.CAT_2.save()
 

	
 
        cls.RESERVATION = datetime.timedelta(hours=1)
 

	
 
        cls.PROD_1 = rego.Product.objects.create(
 
            name="Product 1",
 
            description= "This is a test product. It costs $10. " \
 
                "A user may have 10 of them.",
 
            category=cls.CAT_1,
 
            price=Decimal("10.00"),
 
            reservation_duration=cls.RESERVATION,
 
            limit_per_user=10,
 
            order=10,
 
        )
 
        cls.PROD_1.save()
 

	
registrasion/tests/test_ceilings.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.controllers.cart import CartController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 
class CeilingsTestCases(RegistrationCartTestCase):
 

	
 
    def test_add_to_cart_ceiling_limit(self):
 
        self.make_ceiling("Limit ceiling", limit=9)
 
        self.__add_to_cart_test()
 

	
 
    def test_add_to_cart_ceiling_category_limit(self):
 
        self.make_category_ceiling("Limit ceiling", limit=9)
 
        self.__add_to_cart_test()
 

	
 
    def __add_to_cart_test(self):
 

	
 
        current_cart = CartController.for_user(self.USER_1)
 

	
 
        # User should not be able to add 10 of PROD_1 to the current cart
 
        # because it is affected by limit_ceiling
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_2, 10)
 

	
 
        # User should be able to add 5 of PROD_1 to the current cart
 
        current_cart.add_to_cart(self.PROD_1, 5)
 

	
 
        # User should not be able to add 6 of PROD_2 to the current cart
 
        # because it is affected by CEIL_1
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_2, 6)
 

	
 
        # User should be able to add 5 of PROD_2 to the current cart
 
        current_cart.add_to_cart(self.PROD_2, 4)
 

	
 

	
 
    def test_add_to_cart_ceiling_date_range(self):
 
        self.make_ceiling("date range ceiling",
 
            start_time=datetime.datetime(2015, 01, 01, tzinfo=UTC),
 
            end_time=datetime.datetime(2015, 02, 01, tzinfo=UTC)
 
        )
 

	
 
        current_cart = CartController.for_user(self.USER_1)
 

	
 
        # User should not be able to add whilst we're before start_time
 
        self.set_time(datetime.datetime(2014, 01, 01, tzinfo=UTC))
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
registrasion/tests/test_discount.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.controllers.cart import CartController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 
class DiscountTestCase(RegistrationCartTestCase):
 

	
 
    @classmethod
 
    def add_discount_prod_1_includes_prod_2(cls, amount=Decimal(100)):
 
        discount = rego.IncludedProductDiscount.objects.create(
 
            description="PROD_1 includes PROD_2 " + str(amount) + "%",
 
        )
 
        discount.save()
 
        discount.enabling_products.add(cls.PROD_1)
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=cls.PROD_2,
 
            percentage=amount,
 
            quantity=2
 
        ).save()
 
        return discount
 

	
 

	
 
    @classmethod
 
    def add_discount_prod_1_includes_cat_2(cls, amount=Decimal(100)):
 
        discount = rego.IncludedProductDiscount.objects.create(
 
            description="PROD_1 includes CAT_2 " + str(amount) + "%",
 
        )
 
        discount.save()
 
        discount.enabling_products.add(cls.PROD_1)
 
        discount.save()
 
        rego.DiscountForCategory.objects.create(
 
            discount=discount,
 
            category=cls.CAT_2,
 
            percentage=amount,
 
            quantity=2
 
        ).save()
 
        return discount
 

	
 

	
 
    def test_discount_is_applied(self):
 
        discount = self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = CartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
registrasion/tests/test_enabling_condition.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.controllers.cart import CartController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 
class EnablingConditionTestCases(RegistrationCartTestCase):
 

	
 
    @classmethod
 
    def add_product_enabling_condition(cls, mandatory=False):
 
        ''' Adds a product enabling condition: adding PROD_1 to a cart is
 
        predicated on adding PROD_2 beforehand. '''
 
        enabling_condition = rego.ProductEnablingCondition.objects.create(
 
            description="Product condition",
 
            mandatory=mandatory,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.products.add(cls.PROD_1)
 
        enabling_condition.enabling_products.add(cls.PROD_2)
 
        enabling_condition.save()
 

	
 

	
 
    @classmethod
 
    def add_product_enabling_condition_on_category(cls, mandatory=False):
 
        ''' Adds a product enabling condition that operates on a category:
 
        adding an item from CAT_1 is predicated on adding PROD_3 beforehand '''
 
        enabling_condition = rego.ProductEnablingCondition.objects.create(
 
            description="Product condition",
 
            mandatory=mandatory,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.categories.add(cls.CAT_1)
 
        enabling_condition.enabling_products.add(cls.PROD_3)
 
        enabling_condition.save()
 

	
 

	
 
    def add_category_enabling_condition(cls, mandatory=False):
 
        ''' Adds a category enabling condition: adding PROD_1 to a cart is
 
        predicated on adding an item from CAT_2 beforehand.'''
 
        enabling_condition = rego.CategoryEnablingCondition.objects.create(
 
            description="Category condition",
 
            mandatory=mandatory,
 
            enabling_category=cls.CAT_2,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.products.add(cls.PROD_1)
 
        enabling_condition.save()
 

	
 

	
registrasion/tests/test_invoice.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.invoice import InvoiceController
 
from registrasion.controllers.cart import CartController
 
from registrasion.controllers.invoice import InvoiceController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class InvoiceTestCase(RegistrationCartTestCase):
 

	
 
    def test_create_invoice(self):
 
        current_cart = CartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 
        # That invoice should have a single line item
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice)
 
        self.assertEqual(1, len(line_items))
 
        # That invoice should have a value equal to cost of PROD_1
 
        self.assertEqual(self.PROD_1.price, invoice_1.invoice.value)
 

	
 
        # Adding item to cart should void all active invoices and produce
 
        # a new invoice
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 
        # Invoice should have two line items
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice)
 
        self.assertEqual(2, len(line_items))
 
        # Invoice should have a value equal to cost of PROD_1 and PROD_2
 
        self.assertEqual(
 
            self.PROD_1.price + self.PROD_2.price,
 
            invoice_2.invoice.value)
 

	
 
    def test_create_invoice_fails_if_cart_invalid(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 
        current_cart = CartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        self.add_timedelta(self.RESERVATION * 2)
 
        cart_2 = CartController.for_user(self.USER_2)
 
        cart_2.add_to_cart(self.PROD_1, 1)
 

	
 
        # Now try to invoice the first user
 
        with self.assertRaises(ValidationError):
 
            InvoiceController.for_cart(current_cart.cart)
 

	
 
    def test_paying_invoice_makes_new_cart(self):
registrasion/tests/test_voucher.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.cart import CartController
 
from registrasion.controllers.cart import CartController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class VoucherTestCases(RegistrationCartTestCase):
 

	
 
    @classmethod
 
    def new_voucher(self):
 
        voucher = rego.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code="VOUCHER",
 
            limit=1
 
        )
 
        voucher.save()
 
        return voucher
 

	
 
    def test_apply_voucher(self):
 
        voucher = self.new_voucher()
 

	
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 

	
 
        cart_1 = CartController.for_user(self.USER_1)
 
        cart_1.apply_voucher(voucher)
 
        self.assertIn(voucher, cart_1.cart.vouchers.all())
 

	
 
        # Second user should not be able to apply this voucher (it's exhausted)
 
        cart_2 = CartController.for_user(self.USER_2)
 
        with self.assertRaises(ValidationError):
 
            cart_2.apply_voucher(voucher)
 

	
 
        # After the reservation duration, user 2 should be able to apply voucher
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        cart_2.apply_voucher(voucher)
 
        cart_2.cart.active = False
 
        cart_2.cart.save()
 

	
 
        # After the reservation duration, user 1 should not be able to apply
 
        # voucher, as user 2 has paid for their cart.
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        with self.assertRaises(ValidationError):
 
            cart_1.apply_voucher(voucher)
 

	
 
    def test_voucher_enables_item(self):
 
        voucher = self.new_voucher()
 

	
 
        enabling_condition = rego.VoucherEnablingCondition.objects.create(
0 comments (0 inline, 0 general)