Changeset - 162db2481781
[Not reviewed]
0 9 0
Christopher Neugebauer - 8 years ago 2016-04-29 01:08:45
chrisjrn@gmail.com
Flake8 fixes
9 files changed with 86 insertions and 35 deletions:
0 comments (0 inline, 0 general)
registrasion/controllers/cart.py
Show inline comments
 
import collections
 
import datetime
 
import discount
 
import functools
 
import itertools
 

	
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.db import transaction
 
from django.db.models import Max
 
from django.utils import timezone
 

	
 
from registrasion.exceptions import CartValidationError
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 

	
 
from .category import CategoryController
 
from .conditions import ConditionController
 
from .discount import DiscountController
 
from .flag import FlagController
 
from .product import ProductController
 

	
 

	
 
def _modifies_cart(func):
 
    ''' Decorator that makes the wrapped function raise ValidationError
 
    if we're doing something that could modify the cart. '''
...
 
@@ -357,49 +356,53 @@ class CartController(object):
 
        available = set(ProductController.available_products(
 
            self.cart.user,
 
            products=products,
 
        ))
 

	
 
        not_available = products - available
 
        zeros = [(product, 0) for product in not_available]
 

	
 
        self.set_quantities(zeros)
 

	
 
    @_modifies_cart
 
    @transaction.atomic
 
    def recalculate_discounts(self):
 
        ''' Calculates all of the discounts available for this product.
 
        '''
 

	
 
        # Delete the existing entries.
 
        commerce.DiscountItem.objects.filter(cart=self.cart).delete()
 

	
 
        product_items = self.cart.productitem_set.all().select_related(
 
            "product", "product__category", "product__price"
 
        )
 

	
 
        products = [i.product for i in product_items]
 
        discounts = DiscountController.available_discounts(self.cart.user, [], products)
 
        discounts = DiscountController.available_discounts(
 
            self.cart.user,
 
            [],
 
            products,
 
        )
 

	
 
        # The highest-value discounts will apply to the highest-value
 
        # products first.
 
        product_items = reversed(product_items)
 
        for item in product_items:
 
            self._add_discount(item.product, item.quantity, discounts)
 

	
 
    def _add_discount(self, product, quantity, discounts):
 
        ''' Applies the best discounts on the given product, from the given
 
        discounts.'''
 

	
 
        def matches(discount):
 
            ''' Returns True if and only if the given discount apples to
 
            our product. '''
 
            if isinstance(discount.clause, conditions.DiscountForCategory):
 
                return discount.clause.category == product.category
 
            else:
 
                return discount.clause.product == product
 

	
 
        def value(discount):
 
            ''' Returns the value of this discount clause
 
            as applied to this product '''
 
            if discount.clause.percentage is not None:
 
                return discount.clause.percentage * product.price
registrasion/controllers/conditions.py
Show inline comments
 
import itertools
 
import operator
 

	
 
from collections import defaultdict
 
from collections import namedtuple
 

	
 
from django.db.models import Case
 
from django.db.models import Count
 
from django.db.models import F, Q
 
from django.db.models import Sum
 
from django.db.models import Value
 
from django.db.models import When
 
from django.utils import timezone
 

	
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 

	
 

	
 

	
 
_BIG_QUANTITY = 99999999  # A big quantity
 

	
 

	
 
class ConditionController(object):
 
    ''' Base class for testing conditions that activate Flag
 
    or Discount objects. '''
 

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

	
 
    @staticmethod
 
    def _controllers():
...
 
@@ -213,51 +208,51 @@ class TimeOrStockLimitConditionController(
 

	
 
        queryset = queryset.annotate(remainder=remainder)
 
        queryset = queryset.filter(remainder__gt=0)
 

	
 
        return queryset
 

	
 
    @classmethod
 
    def _relevant_carts(cls, user):
 
        reserved_carts = commerce.Cart.reserved_carts()
 
        reserved_carts = reserved_carts.exclude(
 
            user=user,
 
            status=commerce.Cart.STATUS_ACTIVE,
 
        )
 
        return reserved_carts
 

	
 

	
 
class TimeOrStockLimitFlagController(
 
        TimeOrStockLimitConditionController):
 

	
 
    @classmethod
 
    def _calculate_quantities(cls, user):
 
        reserved_carts = cls._relevant_carts(user)
 

	
 
        # Calculate category lines
 
        cat_items = F('categories__product__productitem__product__category')
 
        item_cats = F('categories__product__productitem__product__category')
 
        reserved_category_products = (
 
            Q(categories=F('categories__product__productitem__product__category')) &
 
            Q(categories=item_cats) &
 
            Q(categories__product__productitem__cart__in=reserved_carts)
 
        )
 

	
 
        # Calculate product lines
 
        reserved_products = (
 
            Q(products=F('products__productitem__product')) &
 
            Q(products__productitem__cart__in=reserved_carts)
 
        )
 

	
 
        category_quantity_in_reserved_carts = When(
 
            reserved_category_products,
 
            then="categories__product__productitem__quantity",
 
        )
 

	
 
        product_quantity_in_reserved_carts = When(
 
            reserved_products,
 
            then="products__productitem__quantity",
 
        )
 

	
 
        quantity_or_zero = Case(
 
            category_quantity_in_reserved_carts,
 
            product_quantity_in_reserved_carts,
 
            default=Value(0),
 
        )
registrasion/controllers/discount.py
Show inline comments
...
 
@@ -25,151 +25,162 @@ class DiscountAndQuantity(object):
 
            A clause describing which product or category this discount item
 
            applies to. This casts to ``str()`` to produce a human-readable
 
            version of the clause.
 

	
 
        quantity (int): The number of times this discount item can be applied
 
            for the given user.
 

	
 
    '''
 

	
 
    def __init__(self, discount, clause, quantity):
 
        self.discount = discount
 
        self.clause = clause
 
        self.quantity = quantity
 

	
 
    def __repr__(self):
 
        return "(discount=%s, clause=%s, quantity=%d)" % (
 
            self.discount, self.clause, self.quantity,
 
        )
 

	
 

	
 
class DiscountController(object):
 

	
 
    @classmethod
 
    def available_discounts(cls, user, categories, products):
 
        ''' Returns all discounts available to this user for the given categories
 
        and products. The discounts also list the available quantity for this user,
 
        not including products that are pending purchase. '''
 
        ''' Returns all discounts available to this user for the given
 
        categories and products. The discounts also list the available quantity
 
        for this user, not including products that are pending purchase. '''
 

	
 

	
 
        filtered_clauses = cls._filtered_discounts(user, categories, products)
 

	
 
        discounts = []
 

	
 
        # Markers so that we don't need to evaluate given conditions more than once
 
        # Markers so that we don't need to evaluate given conditions
 
        # more than once
 
        accepted_discounts = set()
 
        failed_discounts = set()
 

	
 
        for clause in filtered_clauses:
 
            discount = clause.discount
 
            cond = ConditionController.for_condition(discount)
 

	
 
            past_use_count = discount.past_use_count
 

	
 

	
 
            if past_use_count >= clause.quantity:
 
                # This clause has exceeded its use count
 
                pass
 
            elif discount not in failed_discounts:
 
                # This clause is still available
 
                if discount in accepted_discounts or cond.is_met(user, filtered=True):
 
                is_accepted = discount in accepted_discounts
 
                if is_accepted or cond.is_met(user, filtered=True):
 
                    # This clause is valid for this user
 
                    discounts.append(DiscountAndQuantity(
 
                        discount=discount,
 
                        clause=clause,
 
                        quantity=clause.quantity - past_use_count,
 
                    ))
 
                    accepted_discounts.add(discount)
 
                else:
 
                    # This clause is not valid for this user
 
                    failed_discounts.add(discount)
 
        return discounts
 

	
 
    @classmethod
 
    def _filtered_discounts(cls, user, categories, products):
 
        '''
 

	
 
        Returns:
 
            Sequence[discountbase]: All discounts that passed the filter function.
 
            Sequence[discountbase]: All discounts that passed the filter
 
            function.
 

	
 
        '''
 

	
 
        types = list(ConditionController._controllers())
 
        discounttypes = [i for i in types if issubclass(i, conditions.DiscountBase)]
 
        discounttypes = [
 
            i for i in types if issubclass(i, conditions.DiscountBase)
 
        ]
 

	
 
        # discounts that match provided categories
 
        category_discounts = conditions.DiscountForCategory.objects.filter(
 
            category__in=categories
 
        )
 
        # discounts that match provided products
 
        product_discounts = conditions.DiscountForProduct.objects.filter(
 
            product__in=products
 
        )
 
        # discounts that match categories for provided products
 
        product_category_discounts = conditions.DiscountForCategory.objects.filter(
 
        product_category_discounts = conditions.DiscountForCategory.objects
 
        product_category_discounts = product_category_discounts.filter(
 
            category__in=(product.category for product in products)
 
        )
 
        # (Not relevant: discounts that match products in provided categories)
 

	
 
        product_discounts = product_discounts.select_related(
 
            "product",
 
            "product__category",
 
        )
 

	
 
        all_category_discounts = category_discounts | product_category_discounts
 
        all_category_discounts = (
 
            category_discounts | product_category_discounts
 
        )
 
        all_category_discounts = all_category_discounts.select_related(
 
            "category",
 
        )
 

	
 
        valid_discounts = conditions.DiscountBase.objects.filter(
 
            Q(discountforproduct__in=product_discounts) |
 
            Q(discountforcategory__in=all_category_discounts)
 
        )
 

	
 
        all_subsets = []
 

	
 
        for discounttype in discounttypes:
 
            discounts = discounttype.objects.filter(id__in=valid_discounts)
 
            ctrl = ConditionController.for_type(discounttype)
 
            discounts = ctrl.pre_filter(discounts, user)
 
            discounts = cls._annotate_with_past_uses(discounts, user)
 
            all_subsets.append(discounts)
 

	
 
        filtered_discounts = list(itertools.chain(*all_subsets))
 

	
 
        # Map from discount key to itself (contains annotations added by filter)
 
        # Map from discount key to itself
 
        # (contains annotations needed in the future)
 
        from_filter = dict((i.id, i) for i in filtered_discounts)
 

	
 
        # The set of all potential discounts
 
        discount_clauses = set(itertools.chain(
 
            product_discounts.filter(discount__in=filtered_discounts),
 
            all_category_discounts.filter(discount__in=filtered_discounts),
 
        ))
 

	
 
        # Replace discounts with the filtered ones
 
        # These are the correct subclasses (saves query later on), and have
 
        # correct annotations from filters if necessary.
 
        for clause in discount_clauses:
 
            clause.discount = from_filter[clause.discount.id]
 

	
 
        return discount_clauses
 

	
 
    @classmethod
 
    def _annotate_with_past_uses(cls, queryset, user):
 
        ''' Annotates the queryset with a usage count for that discount by the
 
        given user. '''
 

	
 
        past_use_quantity = When(
 
            (
 
                Q(discountitem__cart__user=user) &
 
                Q(discountitem__cart__status=commerce.Cart.STATUS_PAID)
 
            ),
 
            then="discountitem__quantity",
 
        )
 

	
 
        past_use_quantity_or_zero = Case(
 
            past_use_quantity,
 
            default=Value(0),
 
        )
 

	
 
        queryset = queryset.annotate(past_use_count=Sum(past_use_quantity_or_zero))
 
        queryset = queryset.annotate(
 
            past_use_count=Sum(past_use_quantity_or_zero)
 
        )
 
        return queryset
registrasion/controllers/flag.py
Show inline comments
...
 
@@ -207,51 +207,58 @@ _FlagCounter = namedtuple(
 
    "_FlagCounter",
 
    (
 
        "products",
 
        "categories",
 
    ),
 
)
 

	
 

	
 
_ConditionsCount = namedtuple(
 
    "ConditionsCount",
 
    (
 
        "dif",
 
        "eit",
 
    ),
 
)
 

	
 

	
 
class FlagCounter(_FlagCounter):
 

	
 
    @classmethod
 
    def count(cls):
 
        # Get the count of how many conditions should exist per product
 
        flagbases = conditions.FlagBase.objects
 

	
 
        types = (conditions.FlagBase.ENABLE_IF_TRUE, conditions.FlagBase.DISABLE_IF_FALSE)
 
        types = (
 
            conditions.FlagBase.ENABLE_IF_TRUE,
 
            conditions.FlagBase.DISABLE_IF_FALSE,
 
        )
 
        keys = ("eit", "dif")
 
        flags = [
 
            flagbases.filter(condition=condition_type
 
            ).values('products', 'categories'
 
            ).annotate(count=Count('id'))
 
            flagbases.filter(
 
                condition=condition_type
 
            ).values(
 
                'products', 'categories'
 
            ).annotate(
 
                count=Count('id')
 
            )
 
            for condition_type in types
 
        ]
 

	
 
        cats = defaultdict(lambda: defaultdict(int))
 
        prod = defaultdict(lambda: defaultdict(int))
 

	
 
        for key, flagcounts in zip(keys, flags):
 
            for row in flagcounts:
 
                if row["products"] is not None:
 
                    prod[row["products"]][key] = row["count"]
 
                if row["categories"] is not None:
 
                    cats[row["categories"]][key] = row["count"]
 

	
 
        return cls(products=prod, categories=cats)
 

	
 
    def get(self, product):
 
        p = self.products[product.id]
 
        c = self.categories[product.category.id]
 
        eit = p["eit"] + c["eit"]
 
        dif = p["dif"] + c["dif"]
 
        return _ConditionsCount(dif=dif, eit=eit)
registrasion/controllers/product.py
Show inline comments
 
import itertools
 

	
 
from django.db.models import Sum
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 

	
 
from .category import CategoryController
 
from .conditions import ConditionController
 
from .flag import FlagController
 

	
 

	
 
class ProductController(object):
 

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

	
 
    @classmethod
 
    def available_products(cls, user, category=None, products=None):
 
        ''' Returns a list of all of the products that are available per
 
        flag conditions from the given categories.
 
        TODO: refactor so that all conditions are tested here and
 
        can_add_with_flags calls this method. '''
 
        if category is None and products is None:
 
            raise ValueError("You must provide products or a category")
 

	
 
        if category is not None:
 
            all_products = inventory.Product.objects.filter(category=category)
 
            all_products = all_products.select_related("category")
 
        else:
 
            all_products = []
 

	
 
        if products is not None:
registrasion/tests/test_cart.py
Show inline comments
...
 
@@ -5,49 +5,49 @@ from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.core.management import call_command
 
from django.test import TestCase
 

	
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 
from registrasion.models import people
 
from registrasion.controllers.product import ProductController
 

	
 
from controller_helpers import TestingCartController
 
from patch_datetime import SetTimeMixin
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

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

	
 
    def tearDown(self):
 
        if False:
 
        if True:
 
            # If you're seeing segfaults in tests, enable this.
 
            call_command(
 
                'flush',
 
                verbosity=0,
 
                interactive=False,
 
                reset_sequences=False,
 
                allow_cascade=False,
 
                inhibit_post_migrate=False
 
            )
 

	
 
        super(RegistrationCartTestCase, self).tearDown()
 

	
 
    @classmethod
 
    def setUpTestData(cls):
 

	
 
        super(RegistrationCartTestCase, cls).setUpTestData()
 

	
 
        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',
registrasion/tests/test_ceilings.py
Show inline comments
...
 
@@ -128,49 +128,53 @@ class CeilingsTestCases(RegistrationCartTestCase):
 
        # Unpaid cart within reservation window
 
        second_cart.add_to_cart(self.PROD_1, 1)
 
        with self.assertRaises(ValidationError):
 
            first_cart.validate_cart()
 

	
 
        # Paid cart outside the reservation window
 

	
 
        second_cart.next_cart()
 
        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 
        with self.assertRaises(ValidationError):
 
            first_cart.validate_cart()
 

	
 
    def test_discount_ceiling_aggregates_products(self):
 
        # Create two carts, add 1xprod_1 to each. Ceiling should disappear
 
        # after second.
 
        self.make_discount_ceiling(
 
            "Multi-product limit discount ceiling",
 
            limit=2,
 
        )
 
        for i in xrange(2):
 
            cart = TestingCartController.for_user(self.USER_1)
 
            cart.add_to_cart(self.PROD_1, 1)
 
            cart.next_cart()
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [], [self.PROD_1])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_1],
 
        )
 

	
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_flag_ceiling_aggregates_products(self):
 
        # Create two carts, add 1xprod_1 to each. Ceiling should disappear
 
        # after second.
 
        self.make_ceiling("Multi-product limit ceiling", limit=2)
 

	
 
        for i in xrange(2):
 
            cart = TestingCartController.for_user(self.USER_1)
 
            cart.add_to_cart(self.PROD_1, 1)
 
            cart.next_cart()
 

	
 
        products = ProductController.available_products(
 
            self.USER_1,
 
            products=[self.PROD_1],
 
        )
 

	
 
        self.assertEqual(0, len(products))
 

	
 
    def test_items_released_from_ceiling_by_refund(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 

	
 
        first_cart = TestingCartController.for_user(self.USER_1)
registrasion/tests/test_discount.py
Show inline comments
...
 
@@ -224,160 +224,188 @@ class DiscountTestCase(RegistrationCartTestCase):
 
        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)
 

	
 
    def test_discount_quantity_is_per_user(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        # Both users should be able to apply the same discount
 
        # in the same way
 
        for user in (self.USER_1, self.USER_2):
 
            cart = TestingCartController.for_user(user)
 
            cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
            cart.add_to_cart(self.PROD_3, 1)
 

	
 
            discount_items = list(cart.cart.discountitem_set.all())
 
            # The discount is applied.
 
            self.assertEqual(1, len(discount_items))
 

	
 
    # Tests for the DiscountController.available_discounts enumerator
 
    def test_enumerate_no_discounts_for_no_input(self):
 
        discounts = DiscountController.available_discounts(self.USER_1, [], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_enumerate_no_discounts_if_condition_not_met(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_2], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_category_discount_appears_once_if_met_twice(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_with_category(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_2], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_with_product(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_once_with_two_valid_product(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3, self.PROD_4]
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_product_discount_appears_with_product(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_product_discount_does_not_appear_with_category(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_1], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_1],
 
            [],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_discount_quantity_is_correct_before_first_purchase(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=2)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_2], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [],
 
        )
 
        self.assertEqual(2, discounts[0].quantity)
 

	
 
        cart.next_cart()
 

	
 
    def test_discount_quantity_is_correct_after_first_purchase(self):
 
        self.test_discount_quantity_is_correct_before_first_purchase()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity
 

	
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_2], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [],
 
        )
 
        self.assertEqual(1, discounts[0].quantity)
 

	
 
        cart.next_cart()
 

	
 
    def test_discount_is_gone_after_quantity_exhausted(self):
 
        self.test_discount_quantity_is_correct_after_first_purchase()
 
        discounts = DiscountController.available_discounts(self.USER_1, [self.CAT_2], [])
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_product_discount_enabled_twice_appears_twice(self):
 
        self.add_discount_prod_1_includes_prod_3_and_prod_4(quantity=2)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3, self.PROD_4],
 
        )
 
        self.assertEqual(2, len(discounts))
 

	
 
    def test_discounts_are_released_by_refunds(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=2)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        discounts = DiscountController.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
registrasion/views.py
Show inline comments
...
 
@@ -406,49 +406,53 @@ def _handle_products(request, category, products, prefix):
 
        request.POST or None,
 
        product_quantities=quantities,
 
        prefix=prefix,
 
    )
 

	
 
    if request.method == "POST" and products_form.is_valid():
 
        if products_form.has_changed():
 
            _set_quantities_from_products_form(products_form, current_cart)
 

	
 
        # If category is required, the user must have at least one
 
        # in an active+valid cart
 
        if category.required:
 
            carts = commerce.Cart.objects.filter(user=request.user)
 
            items = commerce.ProductItem.objects.filter(
 
                product__category=category,
 
                cart=carts,
 
            )
 
            if len(items) == 0:
 
                products_form.add_error(
 
                    None,
 
                    "You must have at least one item from this category",
 
                )
 
    handled = False if products_form.errors else True
 

	
 
    discounts = DiscountController.available_discounts(request.user, [], products)
 
    discounts = DiscountController.available_discounts(
 
        request.user,
 
        [],
 
        products,
 
    )
 

	
 
    return products_form, discounts, handled
 

	
 

	
 
def _set_quantities_from_products_form(products_form, current_cart):
 

	
 
    quantities = list(products_form.product_quantities())
 

	
 
    pks = [i[0] for i in quantities]
 
    products = inventory.Product.objects.filter(
 
        id__in=pks,
 
    ).select_related("category")
 

	
 
    product_quantities = [
 
        (products.get(pk=i[0]), i[1]) for i in quantities
 
    ]
 
    field_names = dict(
 
        (i[0][0], i[1][2]) for i in zip(product_quantities, quantities)
 
    )
 

	
 
    try:
 
        current_cart.set_quantities(product_quantities)
 
    except CartValidationError as ve:
 
        for ve_field in ve.error_list:
0 comments (0 inline, 0 general)