diff --git a/registrasion/controllers/cart.py b/registrasion/controllers/cart.py index 0a10e5f8..89e660d6 100644 --- a/registrasion/controllers/cart.py +++ b/registrasion/controllers/cart.py @@ -30,14 +30,16 @@ class CartController(object): if there isn't one ready yet. ''' try: - existing = commerce.Cart.objects.get(user=user, active=True) + existing = commerce.Cart.objects.get( + user=user, + status=commerce.Cart.STATUS_ACTIVE, + ) except ObjectDoesNotExist: existing = commerce.Cart.objects.create( user=user, time_last_updated=timezone.now(), reservation_duration=datetime.timedelta(), - ) - existing.save() + ) return cls(existing) def extend_reservation(self): @@ -367,11 +369,10 @@ class CartController(object): allowed = candidate.quantity if ours > allowed: discount_item.quantity = allowed + discount_item.save() # Update the remaining quantity. quantity = ours - allowed else: quantity = 0 candidate.quantity -= discount_item.quantity - - discount_item.save() diff --git a/registrasion/controllers/category.py b/registrasion/controllers/category.py index 94040b5f..a986eea7 100644 --- a/registrasion/controllers/category.py +++ b/registrasion/controllers/category.py @@ -46,8 +46,7 @@ class CategoryController(object): carts = commerce.Cart.objects.filter( user=user, - active=False, - released=False, + status=commerce.Cart.STATUS_PAID, ) items = commerce.ProductItem.objects.filter( diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index e96b6590..db40d0c2 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -201,7 +201,8 @@ class CategoryConditionController(ConditionController): ''' returns True if the user has a product from a category that invokes this condition in one of their carts ''' - carts = commerce.Cart.objects.filter(user=user, released=False) + carts = commerce.Cart.objects.filter(user=user) + carts = carts.exclude(status=commerce.Cart.STATUS_RELEASED) enabling_products = inventory.Product.objects.filter( category=self.condition.enabling_category, ) @@ -223,7 +224,8 @@ class ProductConditionController(ConditionController): ''' returns True if the user has a product that invokes this condition in one of their carts ''' - carts = commerce.Cart.objects.filter(user=user, released=False) + carts = commerce.Cart.objects.filter(user=user) + carts = carts.exclude(status=commerce.Cart.STATUS_RELEASED) products_count = commerce.ProductItem.objects.filter( cart__in=carts, product__in=self.condition.enabling_products.all(), @@ -272,7 +274,7 @@ class TimeOrStockLimitConditionController(ConditionController): reserved_carts = commerce.Cart.reserved_carts() reserved_carts = reserved_carts.exclude( user=user, - active=True, + status=commerce.Cart.STATUS_ACTIVE, ) items = self._items() diff --git a/registrasion/controllers/discount.py b/registrasion/controllers/discount.py index 8b11c36a..6b9b7582 100644 --- a/registrasion/controllers/discount.py +++ b/registrasion/controllers/discount.py @@ -71,8 +71,7 @@ def available_discounts(user, categories, products): # is not available any more. past_uses = commerce.DiscountItem.objects.filter( cart__user=user, - cart__active=False, # Only past carts count - cart__released=False, # You can reuse refunded discounts + cart__status=commerce.Cart.STATUS_PAID, # Only past carts count discount=real_discount, ) agg = past_uses.aggregate(Sum("quantity")) diff --git a/registrasion/controllers/invoice.py b/registrasion/controllers/invoice.py index ef4ee320..62bab0b6 100644 --- a/registrasion/controllers/invoice.py +++ b/registrasion/controllers/invoice.py @@ -227,7 +227,7 @@ class InvoiceController(ForId, object): necessary. ''' cart = self.invoice.cart if cart: - cart.active = False + cart.status = commerce.Cart.STATUS_PAID cart.save() self.invoice.status = commerce.Invoice.STATUS_PAID self.invoice.save() @@ -237,8 +237,7 @@ class InvoiceController(ForId, object): necessary. ''' cart = self.invoice.cart if cart: - cart.active = False - cart.released = True + cart.status = commerce.Cart.STATUS_RELEASED cart.save() self.invoice.status = commerce.Invoice.STATUS_REFUNDED self.invoice.save() diff --git a/registrasion/controllers/product.py b/registrasion/controllers/product.py index c6f8370c..99618ded 100644 --- a/registrasion/controllers/product.py +++ b/registrasion/controllers/product.py @@ -68,8 +68,7 @@ class ProductController(object): carts = commerce.Cart.objects.filter( user=user, - active=False, - released=False, + status=commerce.Cart.STATUS_PAID, ) items = commerce.ProductItem.objects.filter( diff --git a/registrasion/migrations/0023_auto_20160425_0409.py b/registrasion/migrations/0023_auto_20160425_0409.py new file mode 100644 index 00000000..234e9cda --- /dev/null +++ b/registrasion/migrations/0023_auto_20160425_0409.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-25 04:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0022_merge'), + ] + + operations = [ + migrations.AlterIndexTogether( + name='cart', + index_together=set([]), + ), + ] diff --git a/registrasion/migrations/0024_auto_20160425_0410.py b/registrasion/migrations/0024_auto_20160425_0410.py new file mode 100644 index 00000000..5cf7cdbc --- /dev/null +++ b/registrasion/migrations/0024_auto_20160425_0410.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-25 04:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0023_auto_20160425_0409'), + ] + + operations = [ + migrations.AddField( + model_name='cart', + name='status', + field=models.IntegerField(choices=[(1, 'Active'), (2, 'Paid'), (3, 'Released')], db_index=True, default=1), + preserve_default=False, + ), + migrations.RemoveField( + model_name='cart', + name='active', + ), + migrations.RemoveField( + model_name='cart', + name='released', + ), + migrations.AlterIndexTogether( + name='cart', + index_together=set([('status', 'user'), ('status', 'time_last_updated')]), + ), + ] diff --git a/registrasion/migrations/0025_auto_20160425_0411.py b/registrasion/migrations/0025_auto_20160425_0411.py new file mode 100644 index 00000000..aa084246 --- /dev/null +++ b/registrasion/migrations/0025_auto_20160425_0411.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-25 04:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0024_auto_20160425_0410'), + ] + + operations = [ + migrations.AlterField( + model_name='cart', + name='status', + field=models.IntegerField(choices=[(1, 'Active'), (2, 'Paid'), (3, 'Released')], db_index=True, default=1), + ), + ] diff --git a/registrasion/models/commerce.py b/registrasion/models/commerce.py index 27a228e0..cea8cc11 100644 --- a/registrasion/models/commerce.py +++ b/registrasion/models/commerce.py @@ -21,15 +21,23 @@ class Cart(models.Model): class Meta: app_label = "registrasion" index_together = [ - ("active", "time_last_updated"), - ("active", "released"), - ("active", "user"), - ("released", "user"), + ("status", "time_last_updated"), + ("status", "user"), ] def __str__(self): return "%d rev #%d" % (self.id, self.revision) + STATUS_ACTIVE = 1 + STATUS_PAID = 2 + STATUS_RELEASED = 3 + + STATUS_TYPES = [ + (STATUS_ACTIVE, _("Active")), + (STATUS_PAID, _("Paid")), + (STATUS_RELEASED, _("Released")), + ] + user = models.ForeignKey(User) # ProductItems (foreign key) vouchers = models.ManyToManyField(inventory.Voucher, blank=True) @@ -38,24 +46,21 @@ class Cart(models.Model): ) reservation_duration = models.DurationField() revision = models.PositiveIntegerField(default=1) - active = models.BooleanField( - default=True, + status = models.IntegerField( + choices=STATUS_TYPES, db_index=True, + default=STATUS_ACTIVE, ) - released = models.BooleanField( - default=False, - db_index=True - ) # Refunds etc @classmethod def reserved_carts(cls): ''' Gets all carts that are 'reserved' ''' return Cart.objects.filter( - (Q(active=True) & + (Q(status=Cart.STATUS_ACTIVE) & Q(time_last_updated__gt=( timezone.now()-F('reservation_duration') ))) | - (Q(active=False) & Q(released=False)) + Q(status=Cart.STATUS_PAID) ) diff --git a/registrasion/templatetags/registrasion_tags.py b/registrasion/templatetags/registrasion_tags.py index bb721708..bd31d1f4 100644 --- a/registrasion/templatetags/registrasion_tags.py +++ b/registrasion/templatetags/registrasion_tags.py @@ -73,7 +73,7 @@ def items_pending(context): all_items = commerce.ProductItem.objects.filter( cart__user=context.request.user, - cart__active=True, + cart__status=commerce.Cart.STATUS_ACTIVE, ).select_related( "product", "product__category", @@ -100,8 +100,7 @@ def items_purchased(context, category=None): all_items = commerce.ProductItem.objects.filter( cart__user=context.request.user, - cart__active=False, - cart__released=False, + cart__status=commerce.Cart.STATUS_PAID, ).select_related("product", "product__category") if category: diff --git a/registrasion/tests/controller_helpers.py b/registrasion/tests/controller_helpers.py index 3ede49c2..1022ffe8 100644 --- a/registrasion/tests/controller_helpers.py +++ b/registrasion/tests/controller_helpers.py @@ -28,8 +28,9 @@ class TestingCartController(CartController): self.set_quantity(product, old_quantity + quantity) def next_cart(self): - self.cart.active = False - self.cart.save() + if self.cart.status == commerce.Cart.STATUS_ACTIVE: + self.cart.status = commerce.Cart.STATUS_PAID + self.cart.save() class TestingInvoiceController(InvoiceController): diff --git a/registrasion/tests/test_cart.py b/registrasion/tests/test_cart.py index 0f5565e5..bf781bb1 100644 --- a/registrasion/tests/test_cart.py +++ b/registrasion/tests/test_cart.py @@ -5,6 +5,7 @@ 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 @@ -24,6 +25,16 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): def setUp(self): super(RegistrationCartTestCase, self).setUp() + def tearDown(self): + if False: + # 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): @@ -40,17 +51,13 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): password='top_secret') attendee1 = people.Attendee.get_instance(cls.USER_1) - attendee1.save() profile1 = people.AttendeeProfileBase.objects.create( attendee=attendee1, ) - profile1.save() attendee2 = people.Attendee.get_instance(cls.USER_2) - attendee2.save() profile2 = people.AttendeeProfileBase.objects.create( attendee=attendee2, ) - profile2.save() cls.RESERVATION = datetime.timedelta(hours=1) @@ -63,7 +70,6 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): render_type=inventory.Category.RENDER_TYPE_RADIO, required=False, ) - cat.save() cls.categories.append(cat) cls.CAT_1 = cls.categories[0] @@ -80,7 +86,6 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): limit_per_user=10, order=1, ) - prod.save() cls.products.append(prod) cls.PROD_1 = cls.products[0] @@ -91,7 +96,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): cls.PROD_4.price = Decimal("5.00") cls.PROD_4.save() - # Burn through some carts -- this made some past EC tests fail + # Burn through some carts -- this made some past flag tests fail current_cart = TestingCartController.for_user(cls.USER_1) current_cart.next_cart() @@ -109,9 +114,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): start_time=start_time, end_time=end_time ) - limit_ceiling.save() limit_ceiling.products.add(cls.PROD_1, cls.PROD_2) - limit_ceiling.save() @classmethod def make_category_ceiling( @@ -123,9 +126,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): start_time=start_time, end_time=end_time ) - limit_ceiling.save() limit_ceiling.categories.add(cls.CAT_1) - limit_ceiling.save() @classmethod def make_discount_ceiling( @@ -137,13 +138,12 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): end_time=end_time, limit=limit, ) - limit_ceiling.save() conditions.DiscountForProduct.objects.create( discount=limit_ceiling, product=cls.PROD_1, percentage=percentage, quantity=10, - ).save() + ) @classmethod def new_voucher(self, code="VOUCHER", limit=1): @@ -152,7 +152,6 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): code=code, limit=limit, ) - voucher.save() return voucher @classmethod diff --git a/registrasion/tests/test_ceilings.py b/registrasion/tests/test_ceilings.py index d3841ca8..877556d0 100644 --- a/registrasion/tests/test_ceilings.py +++ b/registrasion/tests/test_ceilings.py @@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError from controller_helpers import TestingCartController from test_cart import RegistrationCartTestCase +from registrasion.models import commerce from registrasion.models import conditions UTC = pytz.timezone('UTC') @@ -146,8 +147,8 @@ class CeilingsTestCases(RegistrationCartTestCase): with self.assertRaises(ValidationError): second_cart.add_to_cart(self.PROD_1, 1) - first_cart.cart.released = True - first_cart.next_cart() + first_cart.cart.status = commerce.Cart.STATUS_RELEASED + first_cart.cart.save() second_cart.add_to_cart(self.PROD_1, 1) @@ -159,13 +160,12 @@ class CeilingsTestCases(RegistrationCartTestCase): description="VOUCHER RECIPIENT", voucher=voucher, ) - discount.save() conditions.DiscountForProduct.objects.create( discount=discount, product=self.PROD_1, percentage=100, quantity=1 - ).save() + ) # Buy two of PROD_1, in separate carts: cart = TestingCartController.for_user(self.USER_1) @@ -173,7 +173,7 @@ class CeilingsTestCases(RegistrationCartTestCase): # and not the ceiling discount. cart.apply_voucher("VOUCHER") cart.add_to_cart(self.PROD_1, 1) - self.assertEqual(1, len(cart.cart.discountitem_set.all())) + self.assertEqual(1, cart.cart.discountitem_set.count()) cart.next_cart() @@ -181,4 +181,4 @@ class CeilingsTestCases(RegistrationCartTestCase): # ceiling discount cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_1, 1) - self.assertEqual(1, len(cart.cart.discountitem_set.all())) + self.assertEqual(1, cart.cart.discountitem_set.count()) diff --git a/registrasion/tests/test_discount.py b/registrasion/tests/test_discount.py index 3229a381..71d09d97 100644 --- a/registrasion/tests/test_discount.py +++ b/registrasion/tests/test_discount.py @@ -2,6 +2,7 @@ import pytz from decimal import Decimal +from registrasion.models import commerce from registrasion.models import conditions from registrasion.controllers import discount from controller_helpers import TestingCartController @@ -22,15 +23,13 @@ class DiscountTestCase(RegistrationCartTestCase): discount = conditions.IncludedProductDiscount.objects.create( description="PROD_1 includes PROD_2 " + str(amount) + "%", ) - discount.save() discount.enabling_products.add(cls.PROD_1) - discount.save() conditions.DiscountForProduct.objects.create( discount=discount, product=cls.PROD_2, percentage=amount, quantity=quantity, - ).save() + ) return discount @classmethod @@ -42,15 +41,13 @@ class DiscountTestCase(RegistrationCartTestCase): discount = conditions.IncludedProductDiscount.objects.create( description="PROD_1 includes CAT_2 " + str(amount) + "%", ) - discount.save() discount.enabling_products.add(cls.PROD_1) - discount.save() conditions.DiscountForCategory.objects.create( discount=discount, category=cls.CAT_2, percentage=amount, quantity=quantity, - ).save() + ) return discount @classmethod @@ -63,21 +60,19 @@ class DiscountTestCase(RegistrationCartTestCase): description="PROD_1 includes PROD_3 and PROD_4 " + str(amount) + "%", ) - discount.save() discount.enabling_products.add(cls.PROD_1) - discount.save() conditions.DiscountForProduct.objects.create( discount=discount, product=cls.PROD_3, percentage=amount, quantity=quantity, - ).save() + ) conditions.DiscountForProduct.objects.create( discount=discount, product=cls.PROD_4, percentage=amount, quantity=quantity, - ).save() + ) return discount def test_discount_is_applied(self): @@ -386,7 +381,6 @@ class DiscountTestCase(RegistrationCartTestCase): ) self.assertEqual(1, len(discounts)) - cart.cart.active = False # Keep discount enabled cart.next_cart() cart = TestingCartController.for_user(self.USER_1) @@ -401,8 +395,8 @@ class DiscountTestCase(RegistrationCartTestCase): ) self.assertEqual(0, len(discounts)) - cart.cart.released = True - cart.next_cart() + cart.cart.status = commerce.Cart.STATUS_RELEASED + cart.cart.save() discounts = discount.available_discounts( self.USER_1, diff --git a/registrasion/tests/test_flag.py b/registrasion/tests/test_flag.py index 1c03c6c8..094ac1a4 100644 --- a/registrasion/tests/test_flag.py +++ b/registrasion/tests/test_flag.py @@ -23,10 +23,8 @@ class FlagTestCases(RegistrationCartTestCase): description="Product condition", condition=condition, ) - flag.save() flag.products.add(cls.PROD_1) flag.enabling_products.add(cls.PROD_2) - flag.save() @classmethod def add_product_flag_on_category( @@ -39,10 +37,8 @@ class FlagTestCases(RegistrationCartTestCase): description="Product condition", condition=condition, ) - flag.save() flag.categories.add(cls.CAT_1) flag.enabling_products.add(cls.PROD_3) - flag.save() def add_category_flag(cls, condition=conditions.FlagBase.ENABLE_IF_TRUE): ''' Adds a category flag condition: adding PROD_1 to a cart is @@ -52,9 +48,7 @@ class FlagTestCases(RegistrationCartTestCase): condition=condition, enabling_category=cls.CAT_2, ) - flag.save() flag.products.add(cls.PROD_1) - flag.save() def test_product_flag_enables_product(self): self.add_product_flag() @@ -265,8 +259,8 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.add_to_cart(self.PROD_1, 1) cart_2.set_quantity(self.PROD_1, 0) - cart.cart.released = True - cart.next_cart() + cart.cart.status = commerce.Cart.STATUS_RELEASED + cart.cart.save() with self.assertRaises(ValidationError): cart_2.set_quantity(self.PROD_1, 1) @@ -283,8 +277,8 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.add_to_cart(self.PROD_1, 1) cart_2.set_quantity(self.PROD_1, 0) - cart.cart.released = True - cart.next_cart() + cart.cart.status = commerce.Cart.STATUS_RELEASED + cart.cart.save() with self.assertRaises(ValidationError): cart_2.set_quantity(self.PROD_1, 1) diff --git a/registrasion/tests/test_invoice.py b/registrasion/tests/test_invoice.py index a7db2849..1b687c0c 100644 --- a/registrasion/tests/test_invoice.py +++ b/registrasion/tests/test_invoice.py @@ -97,7 +97,10 @@ class InvoiceTestCase(RegistrationCartTestCase): self.assertTrue(invoice.invoice.is_paid) # Cart should not be active - self.assertFalse(invoice.invoice.cart.active) + self.assertNotEqual( + commerce.Cart.STATUS_ACTIVE, + invoice.invoice.cart.status, + ) # Asking for a cart should generate a new one new_cart = TestingCartController.for_user(self.USER_1) @@ -482,7 +485,7 @@ class InvoiceTestCase(RegistrationCartTestCase): notes = commerce.CreditNote.objects.filter(invoice=invoice.invoice) notes = sorted(notes, key = lambda note: note.value) - + self.assertEqual(cnval, notes[0].value) self.assertEqual(val, notes[1].value) diff --git a/registrasion/tests/test_refund.py b/registrasion/tests/test_refund.py index fbed1f32..dde1fa30 100644 --- a/registrasion/tests/test_refund.py +++ b/registrasion/tests/test_refund.py @@ -5,6 +5,8 @@ from controller_helpers import TestingInvoiceController from test_cart import RegistrationCartTestCase +from registrasion.models import commerce + UTC = pytz.timezone('UTC') @@ -21,10 +23,16 @@ class RefundTestCase(RegistrationCartTestCase): self.assertFalse(invoice.invoice.is_void) self.assertTrue(invoice.invoice.is_paid) self.assertFalse(invoice.invoice.is_refunded) - self.assertFalse(invoice.invoice.cart.released) + self.assertNotEqual( + commerce.Cart.STATUS_RELEASED, + invoice.invoice.cart.status, + ) invoice.refund() self.assertFalse(invoice.invoice.is_void) self.assertFalse(invoice.invoice.is_paid) self.assertTrue(invoice.invoice.is_refunded) - self.assertTrue(invoice.invoice.cart.released) + self.assertEqual( + commerce.Cart.STATUS_RELEASED, + invoice.invoice.cart.status, + ) diff --git a/registrasion/tests/test_voucher.py b/registrasion/tests/test_voucher.py index 46b2270a..f837a480 100644 --- a/registrasion/tests/test_voucher.py +++ b/registrasion/tests/test_voucher.py @@ -4,6 +4,7 @@ import pytz from decimal import Decimal from django.core.exceptions import ValidationError from django.db import IntegrityError +from django.db import transaction from registrasion.models import conditions from registrasion.models import inventory @@ -64,9 +65,7 @@ class VoucherTestCases(RegistrationCartTestCase): voucher=voucher, condition=conditions.FlagBase.ENABLE_IF_TRUE, ) - flag.save() flag.products.add(self.PROD_1) - flag.save() # Adding the product without a voucher will not work current_cart = TestingCartController.for_user(self.USER_1) @@ -84,13 +83,12 @@ class VoucherTestCases(RegistrationCartTestCase): description="VOUCHER RECIPIENT", voucher=voucher, ) - discount.save() conditions.DiscountForProduct.objects.create( discount=discount, product=self.PROD_1, percentage=Decimal(100), quantity=1 - ).save() + ) # Having PROD_1 in place should add a discount current_cart = TestingCartController.for_user(self.USER_1) @@ -98,6 +96,7 @@ class VoucherTestCases(RegistrationCartTestCase): current_cart.add_to_cart(self.PROD_1, 1) self.assertEqual(1, len(current_cart.cart.discountitem_set.all())) + @transaction.atomic def test_voucher_codes_unique(self): self.new_voucher(code="VOUCHER") with self.assertRaises(IntegrityError):