Changeset - 539dbd35252f
registrasion/admin.py
Show inline comments
...
 
@@ -4,7 +4,8 @@ from django.utils.translation import ugettext_lazy as _
 

	
 
import nested_admin
 

	
 
from registrasion import models as rego
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 

	
 

	
 
class EffectsDisplayMixin(object):
...
 
@@ -15,12 +16,12 @@ class EffectsDisplayMixin(object):
 

	
 

	
 
class ProductInline(admin.TabularInline):
 
    model = rego.Product
 
    model = inventory.Product
 

	
 

	
 
@admin.register(rego.Category)
 
@admin.register(inventory.Category)
 
class CategoryAdmin(admin.ModelAdmin):
 
    model = rego.Category
 
    model = inventory.Category
 
    fields = ("name", "description", "required", "render_type",
 
              "limit_per_user", "order",)
 
    list_display = ("name", "description")
...
 
@@ -29,9 +30,9 @@ class CategoryAdmin(admin.ModelAdmin):
 
    ]
 

	
 

	
 
@admin.register(rego.Product)
 
@admin.register(inventory.Product)
 
class ProductAdmin(admin.ModelAdmin):
 
    model = rego.Product
 
    model = inventory.Product
 
    list_display = ("name", "category", "description")
 
    list_filter = ("category", )
 

	
...
 
@@ -39,18 +40,18 @@ class ProductAdmin(admin.ModelAdmin):
 
# Discounts
 

	
 
class DiscountForProductInline(admin.TabularInline):
 
    model = rego.DiscountForProduct
 
    model = conditions.DiscountForProduct
 
    verbose_name = _("Product included in discount")
 
    verbose_name_plural = _("Products included in discount")
 

	
 

	
 
class DiscountForCategoryInline(admin.TabularInline):
 
    model = rego.DiscountForCategory
 
    model = conditions.DiscountForCategory
 
    verbose_name = _("Category included in discount")
 
    verbose_name_plural = _("Categories included in discount")
 

	
 

	
 
@admin.register(rego.TimeOrStockLimitDiscount)
 
@admin.register(conditions.TimeOrStockLimitDiscount)
 
class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 
    list_display = (
 
        "description",
...
 
@@ -67,7 +68,7 @@ class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 
    ]
 

	
 

	
 
@admin.register(rego.IncludedProductDiscount)
 
@admin.register(conditions.IncludedProductDiscount)
 
class IncludedProductDiscountAdmin(admin.ModelAdmin):
 

	
 
    def enablers(self, obj):
...
 
@@ -87,7 +88,7 @@ class IncludedProductDiscountAdmin(admin.ModelAdmin):
 
# Vouchers
 

	
 
class VoucherDiscountInline(nested_admin.NestedStackedInline):
 
    model = rego.VoucherDiscount
 
    model = conditions.VoucherDiscount
 
    verbose_name = _("Discount")
 

	
 
    # TODO work out why we're allowed to add more than one?
...
 
@@ -100,7 +101,7 @@ class VoucherDiscountInline(nested_admin.NestedStackedInline):
 

	
 

	
 
class VoucherFlagInline(nested_admin.NestedStackedInline):
 
    model = rego.VoucherFlag
 
    model = conditions.VoucherFlag
 
    verbose_name = _("Product and category enabled by voucher")
 
    verbose_name_plural = _("Products and categories enabled by voucher")
 

	
...
 
@@ -109,7 +110,7 @@ class VoucherFlagInline(nested_admin.NestedStackedInline):
 
    extra = 1
 

	
 

	
 
@admin.register(rego.Voucher)
 
@admin.register(inventory.Voucher)
 
class VoucherAdmin(nested_admin.NestedAdmin):
 

	
 
    def effects(self, obj):
...
 
@@ -133,7 +134,7 @@ class VoucherAdmin(nested_admin.NestedAdmin):
 

	
 
        return "\n".join(out)
 

	
 
    model = rego.Voucher
 
    model = inventory.Voucher
 
    list_display = ("recipient", "code", "effects")
 
    inlines = [
 
        VoucherDiscountInline,
...
 
@@ -142,7 +143,7 @@ class VoucherAdmin(nested_admin.NestedAdmin):
 

	
 

	
 
# Enabling conditions
 
@admin.register(rego.ProductFlag)
 
@admin.register(conditions.ProductFlag)
 
class ProductFlagAdmin(
 
        nested_admin.NestedAdmin,
 
        EffectsDisplayMixin):
...
 
@@ -150,7 +151,7 @@ class ProductFlagAdmin(
 
    def enablers(self, obj):
 
        return list(obj.enabling_products.all())
 

	
 
    model = rego.ProductFlag
 
    model = conditions.ProductFlag
 
    fields = ("description", "enabling_products", "condition", "products",
 
              "categories"),
 

	
...
 
@@ -158,12 +159,12 @@ class ProductFlagAdmin(
 

	
 

	
 
# Enabling conditions
 
@admin.register(rego.CategoryFlag)
 
@admin.register(conditions.CategoryFlag)
 
class CategoryFlagAdmin(
 
        nested_admin.NestedAdmin,
 
        EffectsDisplayMixin):
 

	
 
    model = rego.CategoryFlag
 
    model = conditions.CategoryFlag
 
    fields = ("description", "enabling_category", "condition", "products",
 
              "categories"),
 

	
...
 
@@ -172,11 +173,11 @@ class CategoryFlagAdmin(
 

	
 

	
 
# Enabling conditions
 
@admin.register(rego.TimeOrStockLimitFlag)
 
@admin.register(conditions.TimeOrStockLimitFlag)
 
class TimeOrStockLimitFlagAdmin(
 
        nested_admin.NestedAdmin,
 
        EffectsDisplayMixin):
 
    model = rego.TimeOrStockLimitFlag
 
    model = conditions.TimeOrStockLimitFlag
 

	
 
    list_display = (
 
        "description",
registrasion/controllers/cart.py
Show inline comments
...
 
@@ -9,8 +9,10 @@ from django.db import transaction
 
from django.db.models import Max
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
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
...
 
@@ -28,9 +30,9 @@ class CartController(object):
 
        if there isn't one ready yet. '''
 

	
 
        try:
 
            existing = rego.Cart.objects.get(user=user, active=True)
 
            existing = commerce.Cart.objects.get(user=user, active=True)
 
        except ObjectDoesNotExist:
 
            existing = rego.Cart.objects.create(
 
            existing = commerce.Cart.objects.create(
 
                user=user,
 
                time_last_updated=timezone.now(),
 
                reservation_duration=datetime.timedelta(),
...
 
@@ -47,10 +49,10 @@ class CartController(object):
 

	
 
        # 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)
 
            reservations.append(inventory.Voucher.RESERVATION_DURATION)
 

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

	
...
 
@@ -79,7 +81,7 @@ class CartController(object):
 
        is violated. `product_quantities` is an iterable of (product, quantity)
 
        pairs. '''
 

	
 
        items_in_cart = rego.ProductItem.objects.filter(cart=self.cart)
 
        items_in_cart = commerce.ProductItem.objects.filter(cart=self.cart)
 
        items_in_cart = items_in_cart.select_related(
 
            "product",
 
            "product__category",
...
 
@@ -99,14 +101,14 @@ class CartController(object):
 

	
 
        for product, quantity in product_quantities:
 
            try:
 
                product_item = rego.ProductItem.objects.get(
 
                product_item = commerce.ProductItem.objects.get(
 
                    cart=self.cart,
 
                    product=product,
 
                )
 
                product_item.quantity = quantity
 
                product_item.save()
 
            except ObjectDoesNotExist:
 
                rego.ProductItem.objects.create(
 
                commerce.ProductItem.objects.create(
 
                    cart=self.cart,
 
                    product=product,
 
                    quantity=quantity,
...
 
@@ -176,7 +178,7 @@ class CartController(object):
 
        ''' Applies the voucher with the given code to this cart. '''
 

	
 
        # Try and find the voucher
 
        voucher = rego.Voucher.objects.get(code=voucher_code.upper())
 
        voucher = inventory.Voucher.objects.get(code=voucher_code.upper())
 

	
 
        # Re-applying vouchers should be idempotent
 
        if voucher in self.cart.vouchers.all():
...
 
@@ -193,7 +195,7 @@ class CartController(object):
 
        Raises ValidationError if not. '''
 

	
 
        # Is voucher exhausted?
 
        active_carts = rego.Cart.reserved_carts()
 
        active_carts = commerce.Cart.reserved_carts()
 

	
 
        # It's invalid for a user to enter a voucher that's exhausted
 
        carts_with_voucher = active_carts.filter(vouchers=voucher)
...
 
@@ -238,7 +240,7 @@ class CartController(object):
 
        except ValidationError as ve:
 
            errors.append(ve)
 

	
 
        items = rego.ProductItem.objects.filter(cart=cart)
 
        items = commerce.ProductItem.objects.filter(cart=cart)
 

	
 
        product_quantities = list((i.product, i.quantity) for i in items)
 
        try:
...
 
@@ -248,7 +250,7 @@ class CartController(object):
 
                errors.append(error.message[1])
 

	
 
        # Validate the discounts
 
        discount_items = rego.DiscountItem.objects.filter(cart=cart)
 
        discount_items = commerce.DiscountItem.objects.filter(cart=cart)
 
        seen_discounts = set()
 

	
 
        for discount_item in discount_items:
...
 
@@ -256,7 +258,7 @@ class CartController(object):
 
            if discount in seen_discounts:
 
                continue
 
            seen_discounts.add(discount)
 
            real_discount = rego.DiscountBase.objects.get_subclass(
 
            real_discount = conditions.DiscountBase.objects.get_subclass(
 
                pk=discount.pk)
 
            cond = ConditionController.for_condition(real_discount)
 

	
...
 
@@ -287,7 +289,7 @@ class CartController(object):
 
            self.cart.vouchers.remove(voucher)
 

	
 
        # Fix products and discounts
 
        items = rego.ProductItem.objects.filter(cart=self.cart)
 
        items = commerce.ProductItem.objects.filter(cart=self.cart)
 
        items = items.select_related("product")
 
        products = set(i.product for i in items)
 
        available = set(ProductController.available_products(
...
 
@@ -306,7 +308,7 @@ class CartController(object):
 
        '''
 

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

	
 
        product_items = self.cart.productitem_set.all().select_related(
 
            "product", "product__category",
...
 
@@ -331,7 +333,7 @@ class CartController(object):
 
        def matches(discount):
 
            ''' Returns True if and only if the given discount apples to
 
            our product. '''
 
            if isinstance(discount.clause, rego.DiscountForCategory):
 
            if isinstance(discount.clause, conditions.DiscountForCategory):
 
                return discount.clause.category == product.category
 
            else:
 
                return discount.clause.product == product
...
 
@@ -356,7 +358,7 @@ class CartController(object):
 

	
 
            # 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(
 
            discount_item = commerce.DiscountItem.objects.create(
 
                product=product,
 
                cart=self.cart,
 
                discount=candidate.discount,
registrasion/controllers/category.py
Show inline comments
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 

	
 
from django.db.models import Sum
 

	
...
 
@@ -22,7 +23,9 @@ class CategoryController(object):
 
        from product import ProductController
 

	
 
        if products is AllProducts:
 
            products = rego.Product.objects.all().select_related("category")
 
            products = inventory.Product.objects.all().select_related(
 
                "category",
 
            )
 

	
 
        available = ProductController.available_products(
 
            user,
...
 
@@ -41,13 +44,13 @@ class CategoryController(object):
 
            # We don't need to waste the following queries
 
            return 99999999
 

	
 
        carts = rego.Cart.objects.filter(
 
        carts = commerce.Cart.objects.filter(
 
            user=user,
 
            active=False,
 
            released=False,
 
        )
 

	
 
        items = rego.ProductItem.objects.filter(
 
        items = commerce.ProductItem.objects.filter(
 
            cart__in=carts,
 
            product__category=self.category,
 
        )
registrasion/controllers/conditions.py
Show inline comments
...
 
@@ -7,7 +7,9 @@ from collections import namedtuple
 
from django.db.models import Sum
 
from django.utils import timezone
 

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

	
 

	
 
ConditionAndRemainder = namedtuple(
...
 
@@ -29,15 +31,15 @@ class ConditionController(object):
 
    @staticmethod
 
    def for_condition(condition):
 
        CONTROLLERS = {
 
            rego.CategoryFlag: CategoryConditionController,
 
            rego.IncludedProductDiscount: ProductConditionController,
 
            rego.ProductFlag: ProductConditionController,
 
            rego.TimeOrStockLimitDiscount:
 
            conditions.CategoryFlag: CategoryConditionController,
 
            conditions.IncludedProductDiscount: ProductConditionController,
 
            conditions.ProductFlag: ProductConditionController,
 
            conditions.TimeOrStockLimitDiscount:
 
                TimeOrStockLimitDiscountController,
 
            rego.TimeOrStockLimitFlag:
 
            conditions.TimeOrStockLimitFlag:
 
                TimeOrStockLimitFlagController,
 
            rego.VoucherDiscount: VoucherConditionController,
 
            rego.VoucherFlag: VoucherConditionController,
 
            conditions.VoucherDiscount: VoucherConditionController,
 
            conditions.VoucherFlag: VoucherConditionController,
 
        }
 

	
 
        try:
...
 
@@ -121,7 +123,7 @@ class ConditionController(object):
 
            # Get all products covered by this condition, and the products
 
            # from the categories covered by this condition
 
            cond_products = condition.products.all()
 
            from_category = rego.Product.objects.filter(
 
            from_category = inventory.Product.objects.filter(
 
                category__in=condition.categories.all(),
 
            ).all()
 
            all_products = cond_products | from_category
...
 
@@ -199,11 +201,11 @@ class CategoryConditionController(ConditionController):
 
        ''' returns True if the user has a product from a category that invokes
 
        this condition in one of their carts '''
 

	
 
        carts = rego.Cart.objects.filter(user=user, released=False)
 
        enabling_products = rego.Product.objects.filter(
 
        carts = commerce.Cart.objects.filter(user=user, released=False)
 
        enabling_products = inventory.Product.objects.filter(
 
            category=self.condition.enabling_category,
 
        )
 
        products_count = rego.ProductItem.objects.filter(
 
        products_count = commerce.ProductItem.objects.filter(
 
            cart__in=carts,
 
            product__in=enabling_products,
 
        ).count()
...
 
@@ -221,8 +223,8 @@ class ProductConditionController(ConditionController):
 
        ''' returns True if the user has a product that invokes this
 
        condition in one of their carts '''
 

	
 
        carts = rego.Cart.objects.filter(user=user, released=False)
 
        products_count = rego.ProductItem.objects.filter(
 
        carts = commerce.Cart.objects.filter(user=user, released=False)
 
        products_count = commerce.ProductItem.objects.filter(
 
            cart__in=carts,
 
            product__in=self.condition.enabling_products.all(),
 
        ).count()
...
 
@@ -267,7 +269,7 @@ class TimeOrStockLimitConditionController(ConditionController):
 
            return 99999999
 

	
 
        # We care about all reserved carts, but not the user's current cart
 
        reserved_carts = rego.Cart.reserved_carts()
 
        reserved_carts = commerce.Cart.reserved_carts()
 
        reserved_carts = reserved_carts.exclude(
 
            user=user,
 
            active=True,
...
 
@@ -284,12 +286,12 @@ class TimeOrStockLimitFlagController(
 
        TimeOrStockLimitConditionController):
 

	
 
    def _items(self):
 
        category_products = rego.Product.objects.filter(
 
        category_products = inventory.Product.objects.filter(
 
            category__in=self.ceiling.categories.all(),
 
        )
 
        products = self.ceiling.products.all() | category_products
 

	
 
        product_items = rego.ProductItem.objects.filter(
 
        product_items = commerce.ProductItem.objects.filter(
 
            product__in=products.all(),
 
        )
 
        return product_items
...
 
@@ -298,7 +300,7 @@ class TimeOrStockLimitFlagController(
 
class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController):
 

	
 
    def _items(self):
 
        discount_items = rego.DiscountItem.objects.filter(
 
        discount_items = commerce.DiscountItem.objects.filter(
 
            discount=self.ceiling,
 
        )
 
        return discount_items
...
 
@@ -312,7 +314,7 @@ class VoucherConditionController(ConditionController):
 

	
 
    def is_met(self, user):
 
        ''' returns True if the user has the given voucher attached. '''
 
        carts_count = rego.Cart.objects.filter(
 
        carts_count = commerce.Cart.objects.filter(
 
            user=user,
 
            vouchers=self.condition.voucher,
 
        ).count()
registrasion/controllers/credit_note.py
Show inline comments
 
from django.db import transaction
 

	
 
from registrasion import models as rego
 
from registrasion.models import commerce
 

	
 

	
 
class CreditNoteController(object):
...
 
@@ -14,7 +14,7 @@ class CreditNoteController(object):
 
        the given invoice. You need to call InvoiceController.update_status()
 
        to set the status correctly, if appropriate. '''
 

	
 
        credit_note = rego.CreditNote.objects.create(
 
        credit_note = commerce.CreditNote.objects.create(
 
            invoice=invoice,
 
            amount=0-value,  # Credit notes start off as a payment against inv.
 
            reference="ONE MOMENT",
...
 
@@ -39,7 +39,7 @@ class CreditNoteController(object):
 
        inv.validate_allowed_to_pay()
 

	
 
        # Apply payment to invoice
 
        rego.CreditNoteApplication.objects.create(
 
        commerce.CreditNoteApplication.objects.create(
 
            parent=self.credit_note,
 
            invoice=invoice,
 
            amount=self.credit_note.value,
registrasion/controllers/discount.py
Show inline comments
 
import itertools
 

	
 
from conditions import ConditionController
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 

	
 
from django.db.models import Sum
 

	
...
 
@@ -24,15 +25,15 @@ def available_discounts(user, categories, products):
 
    not including products that are pending purchase. '''
 

	
 
    # discounts that match provided categories
 
    category_discounts = rego.DiscountForCategory.objects.filter(
 
    category_discounts = conditions.DiscountForCategory.objects.filter(
 
        category__in=categories
 
    )
 
    # discounts that match provided products
 
    product_discounts = rego.DiscountForProduct.objects.filter(
 
    product_discounts = conditions.DiscountForProduct.objects.filter(
 
        product__in=products
 
    )
 
    # discounts that match categories for provided products
 
    product_category_discounts = rego.DiscountForCategory.objects.filter(
 
    product_category_discounts = conditions.DiscountForCategory.objects.filter(
 
        category__in=(product.category for product in products)
 
    )
 
    # (Not relevant: discounts that match products in provided categories)
...
 
@@ -60,7 +61,7 @@ def available_discounts(user, categories, products):
 
    failed_discounts = set()
 

	
 
    for discount in potential_discounts:
 
        real_discount = rego.DiscountBase.objects.get_subclass(
 
        real_discount = conditions.DiscountBase.objects.get_subclass(
 
            pk=discount.discount.pk,
 
        )
 
        cond = ConditionController.for_condition(real_discount)
...
 
@@ -68,7 +69,7 @@ def available_discounts(user, categories, products):
 
        # Count the past uses of the given discount item.
 
        # If this user has exceeded the limit for the clause, this clause
 
        # is not available any more.
 
        past_uses = rego.DiscountItem.objects.filter(
 
        past_uses = commerce.DiscountItem.objects.filter(
 
            cart__user=user,
 
            cart__active=False,  # Only past carts count
 
            cart__released=False,  # You can reuse refunded discounts
registrasion/controllers/invoice.py
Show inline comments
...
 
@@ -5,7 +5,9 @@ from django.db import transaction
 
from django.db.models import Sum
 
from django.utils import timezone
 

	
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.models import people
 

	
 
from cart import CartController
 
from credit_note import CreditNoteController
...
 
@@ -25,8 +27,8 @@ class InvoiceController(object):
 
        an invoice is generated.'''
 

	
 
        try:
 
            invoice = rego.Invoice.objects.exclude(
 
                status=rego.Invoice.STATUS_VOID,
 
            invoice = commerce.Invoice.objects.exclude(
 
                status=commerce.Invoice.STATUS_VOID,
 
            ).get(
 
                cart=cart,
 
                cart_revision=cart.revision,
...
 
@@ -42,19 +44,19 @@ class InvoiceController(object):
 

	
 
    @classmethod
 
    def void_all_invoices(cls, cart):
 
        invoices = rego.Invoice.objects.filter(cart=cart).all()
 
        invoices = commerce.Invoice.objects.filter(cart=cart).all()
 
        for invoice in invoices:
 
            cls(invoice).void()
 

	
 
    @classmethod
 
    def resolve_discount_value(cls, item):
 
        try:
 
            condition = rego.DiscountForProduct.objects.get(
 
            condition = conditions.DiscountForProduct.objects.get(
 
                discount=item.discount,
 
                product=item.product
 
            )
 
        except ObjectDoesNotExist:
 
            condition = rego.DiscountForCategory.objects.get(
 
            condition = conditions.DiscountForCategory.objects.get(
 
                discount=item.discount,
 
                category=item.product.category
 
            )
...
 
@@ -75,22 +77,22 @@ class InvoiceController(object):
 
        due = max(issued, reservation_limit)
 

	
 
        # Get the invoice recipient
 
        profile = rego.AttendeeProfileBase.objects.get_subclass(
 
        profile = people.AttendeeProfileBase.objects.get_subclass(
 
            id=cart.user.attendee.attendeeprofilebase.id,
 
        )
 
        recipient = profile.invoice_recipient()
 
        invoice = rego.Invoice.objects.create(
 
        invoice = commerce.Invoice.objects.create(
 
            user=cart.user,
 
            cart=cart,
 
            cart_revision=cart.revision,
 
            status=rego.Invoice.STATUS_UNPAID,
 
            status=commerce.Invoice.STATUS_UNPAID,
 
            value=Decimal(),
 
            issue_time=issued,
 
            due_time=due,
 
            recipient=recipient,
 
        )
 

	
 
        product_items = rego.ProductItem.objects.filter(cart=cart)
 
        product_items = commerce.ProductItem.objects.filter(cart=cart)
 

	
 
        if len(product_items) == 0:
 
            raise ValidationError("Your cart is empty.")
...
 
@@ -98,11 +100,11 @@ class InvoiceController(object):
 
        product_items = product_items.order_by(
 
            "product__category__order", "product__order"
 
        )
 
        discount_items = rego.DiscountItem.objects.filter(cart=cart)
 
        discount_items = commerce.DiscountItem.objects.filter(cart=cart)
 
        invoice_value = Decimal()
 
        for item in product_items:
 
            product = item.product
 
            line_item = rego.LineItem.objects.create(
 
            line_item = commerce.LineItem.objects.create(
 
                invoice=invoice,
 
                description="%s - %s" % (product.category.name, product.name),
 
                quantity=item.quantity,
...
 
@@ -112,7 +114,7 @@ class InvoiceController(object):
 
            invoice_value += line_item.quantity * line_item.price
 

	
 
        for item in discount_items:
 
            line_item = rego.LineItem.objects.create(
 
            line_item = commerce.LineItem.objects.create(
 
                invoice=invoice,
 
                description=item.discount.description,
 
                quantity=item.quantity,
...
 
@@ -170,7 +172,7 @@ class InvoiceController(object):
 
    def total_payments(self):
 
        ''' Returns the total amount paid towards this invoice. '''
 

	
 
        payments = rego.PaymentBase.objects.filter(invoice=self.invoice)
 
        payments = commerce.PaymentBase.objects.filter(invoice=self.invoice)
 
        total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0
 
        return total_paid
 

	
...
 
@@ -180,12 +182,12 @@ class InvoiceController(object):
 

	
 
        old_status = self.invoice.status
 
        total_paid = self.total_payments()
 
        num_payments = rego.PaymentBase.objects.filter(
 
        num_payments = commerce.PaymentBase.objects.filter(
 
            invoice=self.invoice,
 
        ).count()
 
        remainder = self.invoice.value - total_paid
 

	
 
        if old_status == rego.Invoice.STATUS_UNPAID:
 
        if old_status == commerce.Invoice.STATUS_UNPAID:
 
            # Invoice had an amount owing
 
            if remainder <= 0:
 
                # Invoice no longer has amount owing
...
 
@@ -199,15 +201,15 @@ class InvoiceController(object):
 
            elif total_paid == 0 and num_payments > 0:
 
                # Invoice has multiple payments totalling zero
 
                self._mark_void()
 
        elif old_status == rego.Invoice.STATUS_PAID:
 
        elif old_status == commerce.Invoice.STATUS_PAID:
 
            if remainder > 0:
 
                # Invoice went from having a remainder of zero or less
 
                # to having a positive remainder -- must be a refund
 
                self._mark_refunded()
 
        elif old_status == rego.Invoice.STATUS_REFUNDED:
 
        elif old_status == commerce.Invoice.STATUS_REFUNDED:
 
            # Should not ever change from here
 
            pass
 
        elif old_status == rego.Invoice.STATUS_VOID:
 
        elif old_status == commerce.Invoice.STATUS_VOID:
 
            # Should not ever change from here
 
            pass
 

	
...
 
@@ -218,7 +220,7 @@ class InvoiceController(object):
 
        if cart:
 
            cart.active = False
 
            cart.save()
 
        self.invoice.status = rego.Invoice.STATUS_PAID
 
        self.invoice.status = commerce.Invoice.STATUS_PAID
 
        self.invoice.save()
 

	
 
    def _mark_refunded(self):
...
 
@@ -229,13 +231,13 @@ class InvoiceController(object):
 
            cart.active = False
 
            cart.released = True
 
            cart.save()
 
        self.invoice.status = rego.Invoice.STATUS_REFUNDED
 
        self.invoice.status = commerce.Invoice.STATUS_REFUNDED
 
        self.invoice.save()
 

	
 
    def _mark_void(self):
 
        ''' Marks the invoice as refunded, and updates the attached cart if
 
        necessary. '''
 
        self.invoice.status = rego.Invoice.STATUS_VOID
 
        self.invoice.status = commerce.Invoice.STATUS_VOID
 
        self.invoice.save()
 

	
 
    def _invoice_matches_cart(self):
registrasion/controllers/product.py
Show inline comments
 
import itertools
 

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

	
 
from category import CategoryController
 
from conditions import ConditionController
...
 
@@ -22,7 +23,7 @@ class ProductController(object):
 
            raise ValueError("You must provide products or a category")
 

	
 
        if category is not None:
 
            all_products = rego.Product.objects.filter(category=category)
 
            all_products = inventory.Product.objects.filter(category=category)
 
            all_products = all_products.select_related("category")
 
        else:
 
            all_products = []
...
 
@@ -65,13 +66,13 @@ class ProductController(object):
 
            # Don't need to run the remaining queries
 
            return 999999  # We can do better
 

	
 
        carts = rego.Cart.objects.filter(
 
        carts = commerce.Cart.objects.filter(
 
            user=user,
 
            active=False,
 
            released=False,
 
        )
 

	
 
        items = rego.ProductItem.objects.filter(
 
        items = commerce.ProductItem.objects.filter(
 
            cart__in=carts,
 
            product=self.product,
 
        )
registrasion/forms.py
Show inline comments
 
import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 

	
 
from django import forms
 

	
...
 
@@ -14,8 +15,8 @@ class ApplyCreditNoteForm(forms.Form):
 
        self.fields["invoice"].choices = self._unpaid_invoices_for_user
 

	
 
    def _unpaid_invoices_for_user(self):
 
        invoices = rego.Invoice.objects.filter(
 
            status=rego.Invoice.STATUS_UNPAID,
 
        invoices = commerce.Invoice.objects.filter(
 
            status=commerce.Invoice.STATUS_UNPAID,
 
            user=self.user,
 
        )
 

	
...
 
@@ -25,7 +26,6 @@ class ApplyCreditNoteForm(forms.Form):
 
        ]
 

	
 
    invoice = forms.ChoiceField(
 
        #choices=_unpaid_invoices_for_user,
 
        required=True,
 
    )
 

	
...
 
@@ -33,14 +33,14 @@ class ApplyCreditNoteForm(forms.Form):
 
class ManualCreditNoteRefundForm(forms.ModelForm):
 

	
 
    class Meta:
 
        model = rego.ManualCreditNoteRefund
 
        model = commerce.ManualCreditNoteRefund
 
        fields = ["reference"]
 

	
 

	
 
class ManualPaymentForm(forms.ModelForm):
 

	
 
    class Meta:
 
        model = rego.ManualPayment
 
        model = commerce.ManualPayment
 
        fields = ["reference", "amount"]
 

	
 

	
...
 
@@ -168,8 +168,8 @@ def ProductsForm(category, products):
 

	
 
    # Each Category.RENDER_TYPE value has a subclass here.
 
    RENDER_TYPES = {
 
        rego.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
 
        rego.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
 
        inventory.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
 
        inventory.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
 
    }
 

	
 
    # Produce a subclass of _ProductsForm which we can alter the base_fields on
registrasion/models.py
Show inline comments
 
deleted file
registrasion/models/__init__.py
Show inline comments
 
new file 100644
 
from commerce import *  # NOQA
 
from conditions import *  # NOQA
 
from inventory import *  # NOQA
 
from people import *  # NOQA
registrasion/models/commerce.py
Show inline comments
 
new file 100644
 
from . import conditions
 
from . import inventory
 

	
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ValidationError
 
from django.db import models
 
from django.db.models import F, Q
 
from django.utils import timezone
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from model_utils.managers import InheritanceManager
 

	
 

	
 
# Commerce Models
 

	
 
@python_2_unicode_compatible
 
class Cart(models.Model):
 
    ''' Represents a set of product items that have been purchased, or are
 
    pending purchase. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        index_together = [
 
            ("active", "time_last_updated"),
 
            ("active", "released"),
 
            ("active", "user"),
 
            ("released", "user"),
 
        ]
 

	
 
    def __str__(self):
 
        return "%d rev #%d" % (self.id, self.revision)
 

	
 
    user = models.ForeignKey(User)
 
    # ProductItems (foreign key)
 
    vouchers = models.ManyToManyField(inventory.Voucher, blank=True)
 
    time_last_updated = models.DateTimeField(
 
        db_index=True,
 
    )
 
    reservation_duration = models.DurationField()
 
    revision = models.PositiveIntegerField(default=1)
 
    active = models.BooleanField(
 
        default=True,
 
        db_index=True,
 
    )
 
    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(time_last_updated__gt=(
 
                    timezone.now()-F('reservation_duration')
 
                                        ))) |
 
            (Q(active=False) & Q(released=False))
 
        )
 

	
 

	
 
@python_2_unicode_compatible
 
class ProductItem(models.Model):
 
    ''' Represents a product-quantity pair in a Cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("product", )
 

	
 
    def __str__(self):
 
        return "product: %s * %d in Cart: %s" % (
 
            self.product, self.quantity, self.cart)
 

	
 
    cart = models.ForeignKey(Cart)
 
    product = models.ForeignKey(inventory.Product)
 
    quantity = models.PositiveIntegerField(db_index=True)
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountItem(models.Model):
 
    ''' Represents a discount-product-quantity relation in a Cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("product", )
 

	
 
    def __str__(self):
 
        return "%s: %s * %d in Cart: %s" % (
 
            self.discount, self.product, self.quantity, self.cart)
 

	
 
    cart = models.ForeignKey(Cart)
 
    product = models.ForeignKey(inventory.Product)
 
    discount = models.ForeignKey(conditions.DiscountBase)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
@python_2_unicode_compatible
 
class Invoice(models.Model):
 
    ''' An invoice. Invoices can be automatically generated when checking out
 
    a Cart, in which case, it is attached to a given revision of a Cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    STATUS_UNPAID = 1
 
    STATUS_PAID = 2
 
    STATUS_REFUNDED = 3
 
    STATUS_VOID = 4
 

	
 
    STATUS_TYPES = [
 
        (STATUS_UNPAID, _("Unpaid")),
 
        (STATUS_PAID, _("Paid")),
 
        (STATUS_REFUNDED, _("Refunded")),
 
        (STATUS_VOID, _("VOID")),
 
    ]
 

	
 
    def __str__(self):
 
        return "Invoice #%d" % self.id
 

	
 
    def clean(self):
 
        if self.cart is not None and self.cart_revision is None:
 
            raise ValidationError(
 
                "If this is a cart invoice, it must have a revision")
 

	
 
    @property
 
    def is_unpaid(self):
 
        return self.status == self.STATUS_UNPAID
 

	
 
    @property
 
    def is_void(self):
 
        return self.status == self.STATUS_VOID
 

	
 
    @property
 
    def is_paid(self):
 
        return self.status == self.STATUS_PAID
 

	
 
    @property
 
    def is_refunded(self):
 
        return self.status == self.STATUS_REFUNDED
 

	
 
    # Invoice Number
 
    user = models.ForeignKey(User)
 
    cart = models.ForeignKey(Cart, null=True)
 
    cart_revision = models.IntegerField(
 
        null=True,
 
        db_index=True,
 
    )
 
    # Line Items (foreign key)
 
    status = models.IntegerField(
 
        choices=STATUS_TYPES,
 
        db_index=True,
 
    )
 
    recipient = models.CharField(max_length=1024)
 
    issue_time = models.DateTimeField()
 
    due_time = models.DateTimeField()
 
    value = models.DecimalField(max_digits=8, decimal_places=2)
 

	
 

	
 
@python_2_unicode_compatible
 
class LineItem(models.Model):
 
    ''' Line items for an invoice. These are denormalised from the ProductItems
 
    and DiscountItems that belong to a cart (for consistency), but also allow
 
    for arbitrary line items when required. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("id", )
 

	
 
    def __str__(self):
 
        return "Line: %s * %d @ %s" % (
 
            self.description, self.quantity, self.price)
 

	
 
    invoice = models.ForeignKey(Invoice)
 
    description = models.CharField(max_length=255)
 
    quantity = models.PositiveIntegerField()
 
    price = models.DecimalField(max_digits=8, decimal_places=2)
 
    product = models.ForeignKey(inventory.Product, null=True, blank=True)
 

	
 

	
 
@python_2_unicode_compatible
 
class PaymentBase(models.Model):
 
    ''' The base payment type for invoices. Payment apps should subclass this
 
    class to handle implementation-specific issues. '''
 

	
 
    class Meta:
 
        ordering = ("time", )
 

	
 
    objects = InheritanceManager()
 

	
 
    def __str__(self):
 
        return "Payment: ref=%s amount=%s" % (self.reference, self.amount)
 

	
 
    invoice = models.ForeignKey(Invoice)
 
    time = models.DateTimeField(default=timezone.now)
 
    reference = models.CharField(max_length=255)
 
    amount = models.DecimalField(max_digits=8, decimal_places=2)
 

	
 

	
 
class ManualPayment(PaymentBase):
 
    ''' Payments that are manually entered by staff. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 

	
 
class CreditNote(PaymentBase):
 
    ''' Credit notes represent money accounted for in the system that do not
 
    belong to specific invoices. They may be paid into other invoices, or
 
    cashed out as refunds.
 

	
 
    Each CreditNote may either be used to pay towards another Invoice in the
 
    system (by attaching a CreditNoteApplication), or may be marked as
 
    refunded (by attaching a CreditNoteRefund).'''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    @classmethod
 
    def unclaimed(cls):
 
        return cls.objects.filter(
 
            creditnoteapplication=None,
 
            creditnoterefund=None,
 
        )
 

	
 
    @property
 
    def status(self):
 
        if self.is_unclaimed:
 
            return "Unclaimed"
 

	
 
        if hasattr(self, 'creditnoteapplication'):
 
            destination = self.creditnoteapplication.invoice.id
 
            return "Applied to invoice %d" % destination
 

	
 
        elif hasattr(self, 'creditnoterefund'):
 
            reference = self.creditnoterefund.reference
 
            print reference
 
            return "Refunded with reference: %s" % reference
 

	
 
        raise ValueError("This should never happen.")
 

	
 
    @property
 
    def is_unclaimed(self):
 
        return not (
 
            hasattr(self, 'creditnoterefund') or
 
            hasattr(self, 'creditnoteapplication')
 
        )
 

	
 
    @property
 
    def value(self):
 
        ''' Returns the value of the credit note. Because CreditNotes are
 
        implemented as PaymentBase objects internally, the amount is a
 
        negative payment against an invoice. '''
 
        return -self.amount
 

	
 

	
 
class CleanOnSave(object):
 

	
 
    def save(self, *a, **k):
 
        self.full_clean()
 
        super(CleanOnSave, self).save(*a, **k)
 

	
 

	
 
class CreditNoteApplication(CleanOnSave, PaymentBase):
 
    ''' Represents an application of a credit note to an Invoice. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def clean(self):
 
        if not hasattr(self, "parent"):
 
            return
 
        if hasattr(self.parent, 'creditnoterefund'):
 
            raise ValidationError(
 
                "Cannot apply a refunded credit note to an invoice"
 
            )
 

	
 
    parent = models.OneToOneField(CreditNote)
 

	
 

	
 
class CreditNoteRefund(CleanOnSave, models.Model):
 
    ''' Represents a refund of a credit note to an external payment.
 
    Credit notes may only be refunded in full. How those refunds are handled
 
    is left as an exercise to the payment app. '''
 

	
 
    def clean(self):
 
        if not hasattr(self, "parent"):
 
            return
 
        if hasattr(self.parent, 'creditnoteapplication'):
 
            raise ValidationError(
 
                "Cannot refund a credit note that has been paid to an invoice"
 
            )
 

	
 
    parent = models.OneToOneField(CreditNote)
 
    time = models.DateTimeField(default=timezone.now)
 
    reference = models.CharField(max_length=255)
 

	
 

	
 
class ManualCreditNoteRefund(CreditNoteRefund):
 
    ''' Credit notes that are entered by a staff member. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    entered_by = models.ForeignKey(User)
registrasion/models/conditions.py
Show inline comments
 
new file 100644
 
import itertools
 

	
 
from . import inventory
 

	
 
from django.core.exceptions import ValidationError
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from model_utils.managers import InheritanceManager
 

	
 

	
 
# Product Modifiers
 

	
 
@python_2_unicode_compatible
 
class DiscountBase(models.Model):
 
    ''' Base class for discounts. Each subclass has controller code that
 
    determines whether or not the given discount is available to be added to
 
    the current cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    objects = InheritanceManager()
 

	
 
    def __str__(self):
 
        return "Discount: " + self.description
 

	
 
    def effects(self):
 
        ''' Returns all of the effects of this discount. '''
 
        products = self.discountforproduct_set.all()
 
        categories = self.discountforcategory_set.all()
 
        return itertools.chain(products, categories)
 

	
 
    description = models.CharField(
 
        max_length=255,
 
        verbose_name=_("Description"),
 
        help_text=_("A description of this discount. This will be included on "
 
                    "invoices where this discount is applied."),
 
        )
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountForProduct(models.Model):
 
    ''' Represents a discount on an individual product. Each Discount can
 
    contain multiple products and categories. Discounts can either be a
 
    percentage or a fixed amount, but not both. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        if self.percentage:
 
            return "%s%% off %s" % (self.percentage, self.product)
 
        elif self.price:
 
            return "$%s off %s" % (self.price, self.product)
 

	
 
    def clean(self):
 
        if self.percentage is None and self.price is None:
 
            raise ValidationError(
 
                _("Discount must have a percentage or a price."))
 
        elif self.percentage is not None and self.price is not None:
 
            raise ValidationError(
 
                _("Discount may only have a percentage or only a price."))
 

	
 
        prods = DiscountForProduct.objects.filter(
 
            discount=self.discount,
 
            product=self.product)
 
        cats = DiscountForCategory.objects.filter(
 
            discount=self.discount,
 
            category=self.product.category)
 
        if len(prods) > 1:
 
            raise ValidationError(
 
                _("You may only have one discount line per product"))
 
        if len(cats) != 0:
 
            raise ValidationError(
 
                _("You may only have one discount for "
 
                    "a product or its category"))
 

	
 
    discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE)
 
    product = models.ForeignKey(inventory.Product, on_delete=models.CASCADE)
 
    percentage = models.DecimalField(
 
        max_digits=4, decimal_places=1, null=True, blank=True)
 
    price = models.DecimalField(
 
        max_digits=8, decimal_places=2, null=True, blank=True)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountForCategory(models.Model):
 
    ''' Represents a discount for a category of products. Each discount can
 
    contain multiple products. Category discounts can only be a percentage. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        return "%s%% off %s" % (self.percentage, self.category)
 

	
 
    def clean(self):
 
        prods = DiscountForProduct.objects.filter(
 
            discount=self.discount,
 
            product__category=self.category)
 
        cats = DiscountForCategory.objects.filter(
 
            discount=self.discount,
 
            category=self.category)
 
        if len(prods) != 0:
 
            raise ValidationError(
 
                _("You may only have one discount for "
 
                    "a product or its category"))
 
        if len(cats) > 1:
 
            raise ValidationError(
 
                _("You may only have one discount line per category"))
 

	
 
    discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE)
 
    category = models.ForeignKey(inventory.Category, on_delete=models.CASCADE)
 
    percentage = models.DecimalField(
 
        max_digits=4,
 
        decimal_places=1)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
class TimeOrStockLimitDiscount(DiscountBase):
 
    ''' Discounts that are generally available, but are limited by timespan or
 
    usage count. This is for e.g. Early Bird discounts. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (time/stock limit)")
 
        verbose_name_plural = _("discounts (time/stock limit)")
 

	
 
    start_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Start time"),
 
        help_text=_("This discount will only be available after this time."),
 
    )
 
    end_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("End time"),
 
        help_text=_("This discount will only be available before this time."),
 
    )
 
    limit = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Limit"),
 
        help_text=_("This discount may only be applied this many times."),
 
    )
 

	
 

	
 
class VoucherDiscount(DiscountBase):
 
    ''' Discounts that are enabled when a voucher code is in the current
 
    cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (enabled by voucher)")
 
        verbose_name_plural = _("discounts (enabled by voucher)")
 

	
 
    voucher = models.OneToOneField(
 
        inventory.Voucher,
 
        on_delete=models.CASCADE,
 
        verbose_name=_("Voucher"),
 
        db_index=True,
 
    )
 

	
 

	
 
class IncludedProductDiscount(DiscountBase):
 
    ''' Discounts that are enabled because another product has been purchased.
 
    e.g. A conference ticket includes a free t-shirt. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (product inclusions)")
 
        verbose_name_plural = _("discounts (product inclusions)")
 

	
 
    enabling_products = models.ManyToManyField(
 
        inventory.Product,
 
        verbose_name=_("Including product"),
 
        help_text=_("If one of these products are purchased, the discounts "
 
                    "below will be enabled."),
 
    )
 

	
 

	
 
class RoleDiscount(object):
 
    ''' Discounts that are enabled because the active user has a specific
 
    role. This is for e.g. volunteers who can get a discount ticket. '''
 
    # TODO: implement RoleDiscount
 
    pass
 

	
 

	
 
@python_2_unicode_compatible
 
class FlagBase(models.Model):
 
    ''' This defines a condition which allows products or categories to
 
    be made visible, or be prevented from being visible.
 

	
 
    The various subclasses of this can define the conditions that enable
 
    or disable products, by the following rules:
 

	
 
    If there is at least one 'disable if false' flag defined on a product or
 
    category, all such flag conditions must be met. If there is at least one
 
    'enable if true' flag, at least one such condition must be met.
 

	
 
    If both types of conditions exist on a product, both of these rules apply.
 
    '''
 

	
 
    class Meta:
 
        # TODO: make concrete once https://code.djangoproject.com/ticket/26488
 
        # is solved.
 
        abstract = True
 

	
 
    DISABLE_IF_FALSE = 1
 
    ENABLE_IF_TRUE = 2
 

	
 
    def __str__(self):
 
        return self.description
 

	
 
    def effects(self):
 
        ''' Returns all of the items affected by this condition. '''
 
        return itertools.chain(self.products.all(), self.categories.all())
 

	
 
    @property
 
    def is_disable_if_false(self):
 
        return self.condition == FlagBase.DISABLE_IF_FALSE
 

	
 
    @property
 
    def is_enable_if_true(self):
 
        return self.condition == FlagBase.ENABLE_IF_TRUE
 

	
 
    description = models.CharField(max_length=255)
 
    condition = models.IntegerField(
 
        default=ENABLE_IF_TRUE,
 
        choices=(
 
            (DISABLE_IF_FALSE, _("Disable if false")),
 
            (ENABLE_IF_TRUE, _("Enable if true")),
 
        ),
 
        help_text=_("If there is at least one 'disable if false' flag "
 
                    "defined on a product or category, all such flag "
 
                    " conditions must be met. If there is at least one "
 
                    "'enable if true' flag, at least one such condition must "
 
                    "be met. If both types of conditions exist on a product, "
 
                    "both of these rules apply."
 
                    ),
 
    )
 
    products = models.ManyToManyField(
 
        inventory.Product,
 
        blank=True,
 
        help_text=_("Products affected by this flag's condition."),
 
        related_name="flagbase_set",
 
    )
 
    categories = models.ManyToManyField(
 
        inventory.Category,
 
        blank=True,
 
        help_text=_("Categories whose products are affected by this flag's "
 
                    "condition."
 
                    ),
 
        related_name="flagbase_set",
 
    )
 

	
 

	
 
class EnablingConditionBase(FlagBase):
 
    ''' Reifies the abstract FlagBase. This is necessary because django
 
    prevents renaming base classes in migrations. '''
 
    # TODO: remove this, and make subclasses subclass FlagBase once
 
    # https://code.djangoproject.com/ticket/26488 is solved.
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    objects = InheritanceManager()
 

	
 

	
 
class TimeOrStockLimitFlag(EnablingConditionBase):
 
    ''' Registration product ceilings '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (time/stock limit)")
 
        verbose_name_plural = _("flags (time/stock limit)")
 

	
 
    start_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        help_text=_("Products included in this condition will only be "
 
                    "available after this time."),
 
    )
 
    end_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        help_text=_("Products included in this condition will only be "
 
                    "available before this time."),
 
    )
 
    limit = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        help_text=_("The number of items under this grouping that can be "
 
                    "purchased."),
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class ProductFlag(EnablingConditionBase):
 
    ''' The condition is met because a specific product is purchased. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on product)")
 
        verbose_name_plural = _("flags (dependency on product)")
 

	
 
    def __str__(self):
 
        return "Enabled by products: " + str(self.enabling_products.all())
 

	
 
    enabling_products = models.ManyToManyField(
 
        inventory.Product,
 
        help_text=_("If one of these products are purchased, this condition "
 
                    "is met."),
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class CategoryFlag(EnablingConditionBase):
 
    ''' The condition is met because a product in a particular product is
 
    purchased. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on product from category)")
 
        verbose_name_plural = _("flags (dependency on product from category)")
 

	
 
    def __str__(self):
 
        return "Enabled by product in category: " + str(self.enabling_category)
 

	
 
    enabling_category = models.ForeignKey(
 
        inventory.Category,
 
        help_text=_("If a product from this category is purchased, this "
 
                    "condition is met."),
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class VoucherFlag(EnablingConditionBase):
 
    ''' The condition is met because a Voucher is present. This is for e.g.
 
    enabling sponsor tickets. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on voucher)")
 
        verbose_name_plural = _("flags (dependency on voucher)")
 

	
 
    def __str__(self):
 
        return "Enabled by voucher: %s" % self.voucher
 

	
 
    voucher = models.OneToOneField(inventory.Voucher)
 

	
 

	
 
# @python_2_unicode_compatible
 
class RoleFlag(object):
 
    ''' The condition is met because the active user has a particular Role.
 
    This is for e.g. enabling Team tickets. '''
 
    # TODO: implement RoleFlag
 
    pass
registrasion/models/inventory.py
Show inline comments
 
new file 100644
 
import datetime
 

	
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 

	
 

	
 
# Inventory Models
 

	
 
@python_2_unicode_compatible
 
class Category(models.Model):
 
    ''' Registration product categories, used as logical groupings for Products
 
    in registration forms.
 

	
 
    Attributes:
 
        name (str): The display name for the category.
 

	
 
        description (str): Some explanatory text for the category. This is
 
            displayed alongside the forms where your attendees choose their
 
            items.
 

	
 
        required (bool): Requires a user to select an item from this category
 
            during initial registration. You can use this, e.g., for making
 
            sure that the user has a ticket before they select whether they
 
            want a t-shirt.
 

	
 
        render_type (int): This is used to determine what sort of form the
 
            attendee will be presented with when choosing Products from this
 
            category. These may be either of the following:
 

	
 
            ``RENDER_TYPE_RADIO`` presents the Products in the Category as a
 
            list of radio buttons. At most one item can be chosen at a time.
 
            This works well when setting limit_per_user to 1.
 

	
 
            ``RENDER_TYPE_QUANTITY`` shows each Product next to an input field,
 
            where the user can specify a quantity of each Product type. This is
 
            useful for additional extras, like Dinner Tickets.
 

	
 
        limit_per_user (Optional[int]): This restricts the number of items
 
            from this Category that each attendee may claim. This extends
 
            across multiple Invoices.
 

	
 
        display_order (int): An ascending order for displaying the Categories
 
            available. By convention, your Category for ticket types should
 
            have the lowest display order.
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("inventory - category")
 
        verbose_name_plural = _("inventory - categories")
 
        ordering = ("order", )
 

	
 
    def __str__(self):
 
        return self.name
 

	
 
    RENDER_TYPE_RADIO = 1
 
    RENDER_TYPE_QUANTITY = 2
 

	
 
    CATEGORY_RENDER_TYPES = [
 
        (RENDER_TYPE_RADIO, _("Radio button")),
 
        (RENDER_TYPE_QUANTITY, _("Quantity boxes")),
 
    ]
 

	
 
    name = models.CharField(
 
        max_length=65,
 
        verbose_name=_("Name"),
 
    )
 
    description = models.CharField(
 
        max_length=255,
 
        verbose_name=_("Description"),
 
    )
 
    limit_per_user = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Limit per user"),
 
        help_text=_("The total number of items from this category one "
 
                    "attendee may purchase."),
 
    )
 
    required = models.BooleanField(
 
        blank=True,
 
        help_text=_("If enabled, a user must select an "
 
                    "item from this category."),
 
    )
 
    order = models.PositiveIntegerField(
 
        verbose_name=("Display order"),
 
        db_index=True,
 
    )
 
    render_type = models.IntegerField(
 
        choices=CATEGORY_RENDER_TYPES,
 
        verbose_name=_("Render type"),
 
        help_text=_("The registration form will render this category in this "
 
                    "style."),
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class Product(models.Model):
 
    ''' Registration products '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("inventory - product")
 
        ordering = ("category__order", "order")
 

	
 
    def __str__(self):
 
        return "%s - %s" % (self.category.name, self.name)
 

	
 
    name = models.CharField(
 
        max_length=65,
 
        verbose_name=_("Name"),
 
    )
 
    description = models.CharField(
 
        max_length=255,
 
        verbose_name=_("Description"),
 
        null=True,
 
        blank=True,
 
    )
 
    category = models.ForeignKey(
 
        Category,
 
        verbose_name=_("Product category")
 
    )
 
    price = models.DecimalField(
 
        max_digits=8,
 
        decimal_places=2,
 
        verbose_name=_("Price"),
 
    )
 
    limit_per_user = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Limit per user"),
 
    )
 
    reservation_duration = models.DurationField(
 
        default=datetime.timedelta(hours=1),
 
        verbose_name=_("Reservation duration"),
 
        help_text=_("The length of time this product will be reserved before "
 
                    "it is released for someone else to purchase."),
 
    )
 
    order = models.PositiveIntegerField(
 
        verbose_name=("Display order"),
 
        db_index=True,
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class Voucher(models.Model):
 
    ''' Registration vouchers '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    # Vouchers reserve a cart for a fixed amount of time, so that
 
    # items may be added without the voucher being swiped by someone else
 
    RESERVATION_DURATION = datetime.timedelta(hours=1)
 

	
 
    def __str__(self):
 
        return "Voucher for %s" % self.recipient
 

	
 
    @classmethod
 
    def normalise_code(cls, code):
 
        return code.upper()
 

	
 
    def save(self, *a, **k):
 
        ''' Normalise the voucher code to be uppercase '''
 
        self.code = self.normalise_code(self.code)
 
        super(Voucher, self).save(*a, **k)
 

	
 
    recipient = models.CharField(max_length=64, verbose_name=_("Recipient"))
 
    code = models.CharField(max_length=16,
 
                            unique=True,
 
                            verbose_name=_("Voucher code"))
 
    limit = models.PositiveIntegerField(verbose_name=_("Voucher use limit"))
registrasion/models/people.py
Show inline comments
 
new file 100644
 
from registrasion import util
 

	
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from model_utils.managers import InheritanceManager
 

	
 

	
 
# User models
 

	
 
@python_2_unicode_compatible
 
class Attendee(models.Model):
 
    ''' Miscellaneous user-related data. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        return "%s" % self.user
 

	
 
    @staticmethod
 
    def get_instance(user):
 
        ''' Returns the instance of attendee for the given user, or creates
 
        a new one. '''
 
        try:
 
            return Attendee.objects.get(user=user)
 
        except ObjectDoesNotExist:
 
            return Attendee.objects.create(user=user)
 

	
 
    def save(self, *a, **k):
 
        while not self.access_code:
 
            access_code = util.generate_access_code()
 
            if Attendee.objects.filter(access_code=access_code).count() == 0:
 
                self.access_code = access_code
 
        return super(Attendee, self).save(*a, **k)
 

	
 
    user = models.OneToOneField(User, on_delete=models.CASCADE)
 
    # Badge/profile is linked
 
    access_code = models.CharField(
 
        max_length=6,
 
        unique=True,
 
        db_index=True,
 
    )
 
    completed_registration = models.BooleanField(default=False)
 
    guided_categories_complete = models.ManyToManyField("category")
 

	
 

	
 
class AttendeeProfileBase(models.Model):
 
    ''' Information for an attendee's badge and related preferences.
 
    Subclass this in your Django site to ask for attendee information in your
 
    registration progess.
 
     '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    objects = InheritanceManager()
 

	
 
    @classmethod
 
    def name_field(cls):
 
        ''' This is used to pre-fill the attendee's name from the
 
        speaker profile. If it's None, that functionality is disabled. '''
 
        return None
 

	
 
    def invoice_recipient(self):
 
        ''' Returns a representation of this attendee profile for the purpose
 
        of rendering to an invoice. Override in subclasses. '''
 

	
 
        # Manual dispatch to subclass. Fleh.
 
        slf = AttendeeProfileBase.objects.get_subclass(id=self.id)
 
        # Actually compare the functions.
 
        if type(slf).invoice_recipient != type(self).invoice_recipient:
 
            return type(slf).invoice_recipient(slf)
 

	
 
        # Return a default
 
        return slf.attendee.user.username
 

	
 
    attendee = models.OneToOneField(Attendee, on_delete=models.CASCADE)
registrasion/templatetags/registrasion_tags.py
Show inline comments
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 
from registrasion.controllers.category import CategoryController
 

	
 
from collections import namedtuple
...
 
@@ -19,7 +20,7 @@ def available_categories(context):
 
@register.assignment_tag(takes_context=True)
 
def available_credit(context):
 
    ''' Returns the amount of unclaimed credit available for this user. '''
 
    notes = rego.CreditNote.unclaimed().filter(
 
    notes = commerce.CreditNote.unclaimed().filter(
 
        invoice__user=context.request.user,
 
    )
 
    ret = notes.values("amount").aggregate(Sum("amount"))["amount__sum"] or 0
...
 
@@ -29,7 +30,7 @@ def available_credit(context):
 
@register.assignment_tag(takes_context=True)
 
def invoices(context):
 
    ''' Returns all of the invoices that this user has. '''
 
    return rego.Invoice.objects.filter(cart__user=context.request.user)
 
    return commerce.Invoice.objects.filter(cart__user=context.request.user)
 

	
 

	
 
@register.assignment_tag(takes_context=True)
...
 
@@ -37,7 +38,7 @@ def items_pending(context):
 
    ''' Returns all of the items that this user has in their current cart,
 
    and is awaiting payment. '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
    all_items = commerce.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=True,
 
    ).select_related(
...
 
@@ -55,7 +56,7 @@ def items_purchased(context, category=None):
 
    ''' Returns all of the items that this user has purchased, optionally
 
    from the given category. '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
    all_items = commerce.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=False,
 
        cart__released=False,
...
 
@@ -65,7 +66,7 @@ def items_purchased(context, category=None):
 
        all_items = all_items.filter(product__category=category)
 

	
 
    pq = all_items.values("product").annotate(quantity=Sum("quantity")).all()
 
    products = rego.Product.objects.all()
 
    products = inventory.Product.objects.all()
 
    out = []
 
    for item in pq:
 
        prod = products.get(pk=item["product"])
registrasion/tests/controller_helpers.py
Show inline comments
 
from registrasion.controllers.cart import CartController
 
from registrasion.controllers.credit_note import CreditNoteController
 
from registrasion.controllers.invoice import InvoiceController
 
from registrasion import models as rego
 
from registrasion.models import commerce
 

	
 
from django.core.exceptions import ObjectDoesNotExist
 

	
...
 
@@ -19,7 +19,7 @@ class TestingCartController(CartController):
 
        ValidationError if constraints are violated.'''
 

	
 
        try:
 
            product_item = rego.ProductItem.objects.get(
 
            product_item = commerce.ProductItem.objects.get(
 
                cart=self.cart,
 
                product=product)
 
            old_quantity = product_item.quantity
...
 
@@ -41,7 +41,7 @@ class TestingInvoiceController(InvoiceController):
 
        self.validate_allowed_to_pay()
 

	
 
        ''' Adds a payment '''
 
        rego.ManualPayment.objects.create(
 
        commerce.ManualPayment.objects.create(
 
            invoice=self.invoice,
 
            reference=reference,
 
            amount=amount,
...
 
@@ -53,7 +53,7 @@ class TestingInvoiceController(InvoiceController):
 
class TestingCreditNoteController(CreditNoteController):
 

	
 
    def refund(self):
 
        rego.CreditNoteRefund.objects.create(
 
        commerce.CreditNoteRefund.objects.create(
 
            parent=self.credit_note,
 
            reference="Whoops."
 
        )
registrasion/tests/test_cart.py
Show inline comments
...
 
@@ -7,7 +7,10 @@ from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 

	
 
from registrasion import models as rego
 
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
...
 
@@ -36,24 +39,28 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 
            email='test2@example.com',
 
            password='top_secret')
 

	
 
        attendee1 = rego.Attendee.get_instance(cls.USER_1)
 
        attendee1 = people.Attendee.get_instance(cls.USER_1)
 
        attendee1.save()
 
        profile1 = rego.AttendeeProfileBase.objects.create(attendee=attendee1)
 
        profile1 = people.AttendeeProfileBase.objects.create(
 
            attendee=attendee1,
 
        )
 
        profile1.save()
 
        attendee2 = rego.Attendee.get_instance(cls.USER_2)
 
        attendee2 = people.Attendee.get_instance(cls.USER_2)
 
        attendee2.save()
 
        profile2 = rego.AttendeeProfileBase.objects.create(attendee=attendee2)
 
        profile2 = people.AttendeeProfileBase.objects.create(
 
            attendee=attendee2,
 
        )
 
        profile2.save()
 

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

	
 
        cls.categories = []
 
        for i in xrange(2):
 
            cat = rego.Category.objects.create(
 
            cat = inventory.Category.objects.create(
 
                name="Category " + str(i + 1),
 
                description="This is a test category",
 
                order=i,
 
                render_type=rego.Category.RENDER_TYPE_RADIO,
 
                render_type=inventory.Category.RENDER_TYPE_RADIO,
 
                required=False,
 
            )
 
            cat.save()
...
 
@@ -64,7 +71,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

	
 
        cls.products = []
 
        for i in xrange(4):
 
            prod = rego.Product.objects.create(
 
            prod = inventory.Product.objects.create(
 
                name="Product " + str(i + 1),
 
                description="This is a test product.",
 
                category=cls.categories[i / 2],  # 2 products per category
...
 
@@ -95,9 +102,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

	
 
    @classmethod
 
    def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
 
        limit_ceiling = rego.TimeOrStockLimitFlag.objects.create(
 
        limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create(
 
            description=name,
 
            condition=rego.FlagBase.DISABLE_IF_FALSE,
 
            condition=conditions.FlagBase.DISABLE_IF_FALSE,
 
            limit=limit,
 
            start_time=start_time,
 
            end_time=end_time
...
 
@@ -109,9 +116,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 
    @classmethod
 
    def make_category_ceiling(
 
            cls, name, limit=None, start_time=None, end_time=None):
 
        limit_ceiling = rego.TimeOrStockLimitFlag.objects.create(
 
        limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create(
 
            description=name,
 
            condition=rego.FlagBase.DISABLE_IF_FALSE,
 
            condition=conditions.FlagBase.DISABLE_IF_FALSE,
 
            limit=limit,
 
            start_time=start_time,
 
            end_time=end_time
...
 
@@ -124,14 +131,14 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 
    def make_discount_ceiling(
 
            cls, name, limit=None, start_time=None, end_time=None,
 
            percentage=100):
 
        limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create(
 
        limit_ceiling = conditions.TimeOrStockLimitDiscount.objects.create(
 
            description=name,
 
            start_time=start_time,
 
            end_time=end_time,
 
            limit=limit,
 
        )
 
        limit_ceiling.save()
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=limit_ceiling,
 
            product=cls.PROD_1,
 
            percentage=percentage,
...
 
@@ -140,7 +147,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

	
 
    @classmethod
 
    def new_voucher(self, code="VOUCHER", limit=1):
 
        voucher = rego.Voucher.objects.create(
 
        voucher = inventory.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code=code,
 
            limit=limit,
...
 
@@ -176,7 +183,7 @@ class BasicCartTests(RegistrationCartTestCase):
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Count of products for a given user should be collapsed.
 
        items = rego.ProductItem.objects.filter(
 
        items = commerce.ProductItem.objects.filter(
 
            cart=current_cart.cart,
 
            product=self.PROD_1)
 
        self.assertEqual(1, len(items))
...
 
@@ -187,7 +194,7 @@ class BasicCartTests(RegistrationCartTestCase):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        def get_item():
 
            return rego.ProductItem.objects.get(
 
            return commerce.ProductItem.objects.get(
 
                cart=current_cart.cart,
 
                product=self.PROD_1)
 

	
registrasion/tests/test_ceilings.py
Show inline comments
...
 
@@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
 
from controller_helpers import TestingCartController
 
from test_cart import RegistrationCartTestCase
 

	
 
from registrasion import models as rego
 
from registrasion.models import conditions
 

	
 
UTC = pytz.timezone('UTC')
 

	
...
 
@@ -155,12 +155,12 @@ class CeilingsTestCases(RegistrationCartTestCase):
 
        self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50)
 
        voucher = self.new_voucher(code="VOUCHER")
 

	
 
        discount = rego.VoucherDiscount.objects.create(
 
        discount = conditions.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=100,
registrasion/tests/test_discount.py
Show inline comments
...
 
@@ -2,7 +2,7 @@ import pytz
 

	
 
from decimal import Decimal
 

	
 
from registrasion import models as rego
 
from registrasion.models import conditions
 
from registrasion.controllers import discount
 
from controller_helpers import TestingCartController
 

	
...
 
@@ -19,13 +19,13 @@ class DiscountTestCase(RegistrationCartTestCase):
 
            amount=Decimal(100),
 
            quantity=2,
 
            ):
 
        discount = rego.IncludedProductDiscount.objects.create(
 
        discount = conditions.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(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=cls.PROD_2,
 
            percentage=amount,
...
 
@@ -39,13 +39,13 @@ class DiscountTestCase(RegistrationCartTestCase):
 
            amount=Decimal(100),
 
            quantity=2,
 
            ):
 
        discount = rego.IncludedProductDiscount.objects.create(
 
        discount = conditions.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(
 
        conditions.DiscountForCategory.objects.create(
 
            discount=discount,
 
            category=cls.CAT_2,
 
            percentage=amount,
...
 
@@ -59,20 +59,20 @@ class DiscountTestCase(RegistrationCartTestCase):
 
            amount=Decimal(100),
 
            quantity=2,
 
            ):
 
        discount = rego.IncludedProductDiscount.objects.create(
 
        discount = conditions.IncludedProductDiscount.objects.create(
 
            description="PROD_1 includes PROD_3 and PROD_4 " +
 
                        str(amount) + "%",
 
        )
 
        discount.save()
 
        discount.enabling_products.add(cls.PROD_1)
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=cls.PROD_3,
 
            percentage=amount,
 
            quantity=quantity,
 
        ).save()
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=cls.PROD_4,
 
            percentage=amount,
registrasion/tests/test_flag.py
Show inline comments
...
 
@@ -2,7 +2,8 @@ import pytz
 

	
 
from django.core.exceptions import ValidationError
 

	
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.controllers.category import CategoryController
 
from controller_helpers import TestingCartController
 
from registrasion.controllers.product import ProductController
...
 
@@ -15,10 +16,10 @@ UTC = pytz.timezone('UTC')
 
class FlagTestCases(RegistrationCartTestCase):
 

	
 
    @classmethod
 
    def add_product_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE):
 
    def add_product_flag(cls, condition=conditions.FlagBase.ENABLE_IF_TRUE):
 
        ''' Adds a product flag condition: adding PROD_1 to a cart is
 
        predicated on adding PROD_2 beforehand. '''
 
        flag = rego.ProductFlag.objects.create(
 
        flag = conditions.ProductFlag.objects.create(
 
            description="Product condition",
 
            condition=condition,
 
        )
...
 
@@ -28,10 +29,13 @@ class FlagTestCases(RegistrationCartTestCase):
 
        flag.save()
 

	
 
    @classmethod
 
    def add_product_flag_on_category(cls, condition=rego.FlagBase.ENABLE_IF_TRUE):
 
    def add_product_flag_on_category(
 
            cls,
 
            condition=conditions.FlagBase.ENABLE_IF_TRUE,
 
            ):
 
        ''' Adds a product flag condition that operates on a category:
 
        adding an item from CAT_1 is predicated on adding PROD_3 beforehand '''
 
        flag = rego.ProductFlag.objects.create(
 
        flag = conditions.ProductFlag.objects.create(
 
            description="Product condition",
 
            condition=condition,
 
        )
...
 
@@ -40,10 +44,10 @@ class FlagTestCases(RegistrationCartTestCase):
 
        flag.enabling_products.add(cls.PROD_3)
 
        flag.save()
 

	
 
    def add_category_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE):
 
    def add_category_flag(cls, condition=conditions.FlagBase.ENABLE_IF_TRUE):
 
        ''' Adds a category flag condition: adding PROD_1 to a cart is
 
        predicated on adding an item from CAT_2 beforehand.'''
 
        flag = rego.CategoryFlag.objects.create(
 
        flag = conditions.CategoryFlag.objects.create(
 
            description="Category condition",
 
            condition=condition,
 
            enabling_category=cls.CAT_2,
...
 
@@ -131,8 +135,8 @@ class FlagTestCases(RegistrationCartTestCase):
 
        cart_2.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_multiple_dif_conditions(self):
 
        self.add_product_flag(condition=rego.FlagBase.DISABLE_IF_FALSE)
 
        self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE)
 
        self.add_product_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
 
        self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        # Cannot add PROD_1 until both conditions are met
...
 
@@ -145,8 +149,8 @@ class FlagTestCases(RegistrationCartTestCase):
 
        cart_1.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_eit_and_dif_conditions_work_together(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 
        self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        # Cannot add PROD_1 until both conditions are met
...
 
@@ -200,7 +204,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.PROD_4 in prods)
 

	
 
    def test_available_products_on_category_works_when_condition_not_met(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
...
 
@@ -211,7 +215,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_category_works_when_condition_is_met(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        cart_1.add_to_cart(self.PROD_2, 1)
...
 
@@ -225,7 +229,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_products_works_when_condition_not_met(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
...
 
@@ -236,7 +240,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_products_works_when_condition_is_met(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        cart_1.add_to_cart(self.PROD_2, 1)
...
 
@@ -250,7 +254,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_category_flag_fails_if_cart_refunded(self):
 
        self.add_category_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_category_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_3, 1)
...
 
@@ -268,7 +272,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
            cart_2.set_quantity(self.PROD_1, 1)
 

	
 
    def test_product_flag_fails_if_cart_refunded(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
...
 
@@ -286,7 +290,9 @@ class FlagTestCases(RegistrationCartTestCase):
 
            cart_2.set_quantity(self.PROD_1, 1)
 

	
 
    def test_available_categories(self):
 
        self.add_product_flag_on_category(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag_on_category(
 
            condition=conditions.FlagBase.ENABLE_IF_TRUE,
 
        )
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 

	
...
 
@@ -307,7 +313,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        self.assertTrue(self.CAT_2 in cats)
 

	
 
    def test_validate_cart_when_flags_become_unmet(self):
 
        self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE)
 
        self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
...
 
@@ -332,7 +338,7 @@ class FlagTestCases(RegistrationCartTestCase):
 
        cart.validate_cart()
 

	
 
        # Should keep PROD_2 in the cart
 
        items = rego.ProductItem.objects.filter(cart=cart.cart)
 
        items = commerce.ProductItem.objects.filter(cart=cart.cart)
 
        self.assertFalse([i for i in items if i.product == self.PROD_1])
 

	
 
    def test_fix_simple_errors_does_not_remove_limited_items(self):
...
 
@@ -348,5 +354,5 @@ class FlagTestCases(RegistrationCartTestCase):
 

	
 
        # Should keep PROD_2 in the cart
 
        # and also PROD_1, which is now exhausted for user.
 
        items = rego.ProductItem.objects.filter(cart=cart.cart)
 
        items = commerce.ProductItem.objects.filter(cart=cart.cart)
 
        self.assertTrue([i for i in items if i.product == self.PROD_1])
registrasion/tests/test_invoice.py
Show inline comments
...
 
@@ -4,7 +4,9 @@ import pytz
 
from decimal import Decimal
 
from django.core.exceptions import ValidationError
 

	
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 
from controller_helpers import TestingCartController
 
from controller_helpers import TestingCreditNoteController
 
from controller_helpers import TestingInvoiceController
...
 
@@ -23,7 +25,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = TestingInvoiceController.for_cart(current_cart.cart)
 
        # That invoice should have a single line item
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice)
 
        line_items = commerce.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)
...
 
@@ -34,13 +38,15 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 

	
 
        # The old invoice should automatically be voided
 
        invoice_1_new = rego.Invoice.objects.get(pk=invoice_1.invoice.id)
 
        invoice_2_new = rego.Invoice.objects.get(pk=invoice_2.invoice.id)
 
        invoice_1_new = commerce.Invoice.objects.get(pk=invoice_1.invoice.id)
 
        invoice_2_new = commerce.Invoice.objects.get(pk=invoice_2.invoice.id)
 
        self.assertTrue(invoice_1_new.is_void)
 
        self.assertFalse(invoice_2_new.is_void)
 

	
 
        # Invoice should have two line items
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice)
 
        line_items = commerce.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(
...
 
@@ -79,16 +85,16 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertNotEqual(current_cart.cart, new_cart.cart)
 

	
 
    def test_invoice_includes_discounts(self):
 
        voucher = rego.Voucher.objects.create(
 
        voucher = inventory.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code="VOUCHER",
 
            limit=1
 
        )
 
        discount = rego.VoucherDiscount.objects.create(
 
        discount = conditions.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(50),
...
 
@@ -103,7 +109,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        invoice_1 = TestingInvoiceController.for_cart(current_cart.cart)
 

	
 
        # That invoice should have two line items
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice)
 
        line_items = commerce.LineItem.objects.filter(
 
            invoice=invoice_1.invoice,
 
        )
 
        self.assertEqual(2, len(line_items))
 
        # That invoice should have a value equal to 50% of the cost of PROD_1
 
        self.assertEqual(
...
 
@@ -111,16 +119,16 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
            invoice_1.invoice.value)
 

	
 
    def test_zero_value_invoice_is_automatically_paid(self):
 
        voucher = rego.Voucher.objects.create(
 
        voucher = inventory.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code="VOUCHER",
 
            limit=1
 
        )
 
        discount = rego.VoucherDiscount.objects.create(
 
        discount = conditions.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(100),
...
 
@@ -239,7 +247,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertTrue(invoice.invoice.is_paid)
 

	
 
        # There should be a credit note generated out of the invoice.
 
        credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice)
 
        credit_notes = commerce.CreditNote.objects.filter(
 
            invoice=invoice.invoice,
 
        )
 
        self.assertEqual(1, credit_notes.count())
 
        self.assertEqual(to_pay - invoice.invoice.value, credit_notes[0].value)
 

	
...
 
@@ -257,7 +267,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertTrue(invoice.invoice.is_paid)
 

	
 
        # There should be no credit notes
 
        credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice)
 
        credit_notes = commerce.CreditNote.objects.filter(
 
            invoice=invoice.invoice,
 
        )
 
        self.assertEqual(0, credit_notes.count())
 

	
 
    def test_refund_partially_paid_invoice_generates_correct_credit_note(self):
...
 
@@ -276,7 +288,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertTrue(invoice.invoice.is_void)
 

	
 
        # There should be a credit note generated out of the invoice.
 
        credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice)
 
        credit_notes = commerce.CreditNote.objects.filter(
 
            invoice=invoice.invoice,
 
        )
 
        self.assertEqual(1, credit_notes.count())
 
        self.assertEqual(to_pay, credit_notes[0].value)
 

	
...
 
@@ -297,7 +311,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertTrue(invoice.invoice.is_refunded)
 

	
 
        # There should be a credit note generated out of the invoice.
 
        credit_notes = rego.CreditNote.objects.filter(invoice=invoice.invoice)
 
        credit_notes = commerce.CreditNote.objects.filter(
 
            invoice=invoice.invoice,
 
        )
 
        self.assertEqual(1, credit_notes.count())
 
        self.assertEqual(to_pay, credit_notes[0].value)
 

	
...
 
@@ -314,11 +330,11 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        invoice.refund()
 

	
 
        # There should be one credit note generated out of the invoice.
 
        credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice)
 
        credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice)
 
        cn = TestingCreditNoteController(credit_note)
 

	
 
        # That credit note should be in the unclaimed pile
 
        self.assertEquals(1, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(1, commerce.CreditNote.unclaimed().count())
 

	
 
        # Create a new (identical) cart with invoice
 
        cart = TestingCartController.for_user(self.USER_1)
...
 
@@ -330,7 +346,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        self.assertTrue(invoice2.invoice.is_paid)
 

	
 
        # That invoice should not show up as unclaimed any more
 
        self.assertEquals(0, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(0, commerce.CreditNote.unclaimed().count())
 

	
 
    def test_apply_credit_note_generates_new_credit_note_if_overpaying(self):
 
        cart = TestingCartController.for_user(self.USER_1)
...
 
@@ -345,10 +361,10 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        invoice.refund()
 

	
 
        # There should be one credit note generated out of the invoice.
 
        credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice)
 
        credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice)
 
        cn = TestingCreditNoteController(credit_note)
 

	
 
        self.assertEquals(1, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(1, commerce.CreditNote.unclaimed().count())
 

	
 
        # Create a new cart (of half value of inv 1) and get invoice
 
        cart = TestingCartController.for_user(self.USER_1)
...
 
@@ -361,9 +377,11 @@ class InvoiceTestCase(RegistrationCartTestCase):
 

	
 
        # We generated a new credit note, and spent the old one,
 
        # unclaimed should still be 1.
 
        self.assertEquals(1, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(1, commerce.CreditNote.unclaimed().count())
 

	
 
        credit_note2 = rego.CreditNote.objects.get(invoice=invoice2.invoice)
 
        credit_note2 = commerce.CreditNote.objects.get(
 
            invoice=invoice2.invoice,
 
        )
 

	
 
        # The new credit note should be the residual of the cost of cart 1
 
        # minus the cost of cart 2.
...
 
@@ -385,7 +403,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        invoice.refund()
 

	
 
        # There should be one credit note generated out of the invoice.
 
        credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice)
 
        credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice)
 
        cn = TestingCreditNoteController(credit_note)
 

	
 
        # Create a new cart with invoice, pay it
...
 
@@ -426,15 +444,15 @@ class InvoiceTestCase(RegistrationCartTestCase):
 

	
 
        invoice.refund()
 

	
 
        self.assertEquals(1, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(1, commerce.CreditNote.unclaimed().count())
 

	
 
        credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice)
 
        credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice)
 

	
 
        cn = TestingCreditNoteController(credit_note)
 
        cn.refund()
 

	
 
        # Refunding a credit note should mark it as claimed
 
        self.assertEquals(0, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(0, commerce.CreditNote.unclaimed().count())
 

	
 
        # Create a new cart with invoice
 
        cart = TestingCartController.for_user(self.USER_1)
...
 
@@ -458,9 +476,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
 

	
 
        invoice.refund()
 

	
 
        self.assertEquals(1, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(1, commerce.CreditNote.unclaimed().count())
 

	
 
        credit_note = rego.CreditNote.objects.get(invoice=invoice.invoice)
 
        credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice)
 

	
 
        cn = TestingCreditNoteController(credit_note)
 

	
...
 
@@ -471,7 +489,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
 
        invoice_2 = TestingInvoiceController.for_cart(self.reget(cart.cart))
 
        cn.apply_to_invoice(invoice_2.invoice)
 

	
 
        self.assertEquals(0, rego.CreditNote.unclaimed().count())
 
        self.assertEquals(0, commerce.CreditNote.unclaimed().count())
 

	
 
        # Cannot refund this credit note as it is already applied.
 
        with self.assertRaises(ValidationError):
registrasion/tests/test_voucher.py
Show inline comments
...
 
@@ -5,7 +5,8 @@ from decimal import Decimal
 
from django.core.exceptions import ValidationError
 
from django.db import IntegrityError
 

	
 
from registrasion import models as rego
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 
from controller_helpers import TestingCartController
 
from controller_helpers import TestingInvoiceController
 

	
...
 
@@ -32,14 +33,14 @@ class VoucherTestCases(RegistrationCartTestCase):
 

	
 
        # After the reservation duration
 
        # user 2 should be able to apply voucher
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        self.add_timedelta(inventory.Voucher.RESERVATION_DURATION * 2)
 
        cart_2.apply_voucher(voucher.code)
 

	
 
        cart_2.next_cart()
 

	
 
        # After the reservation duration, even though the voucher has applied,
 
        # it exceeds the number of vouchers available.
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        self.add_timedelta(inventory.Voucher.RESERVATION_DURATION * 2)
 
        with self.assertRaises(ValidationError):
 
            cart_1.validate_cart()
 

	
...
 
@@ -58,10 +59,10 @@ class VoucherTestCases(RegistrationCartTestCase):
 
    def test_voucher_enables_item(self):
 
        voucher = self.new_voucher()
 

	
 
        flag = rego.VoucherFlag.objects.create(
 
        flag = conditions.VoucherFlag.objects.create(
 
            description="Voucher condition",
 
            voucher=voucher,
 
            condition=rego.FlagBase.ENABLE_IF_TRUE,
 
            condition=conditions.FlagBase.ENABLE_IF_TRUE,
 
        )
 
        flag.save()
 
        flag.products.add(self.PROD_1)
...
 
@@ -79,12 +80,12 @@ class VoucherTestCases(RegistrationCartTestCase):
 
    def test_voucher_enables_discount(self):
 
        voucher = self.new_voucher()
 

	
 
        discount = rego.VoucherDiscount.objects.create(
 
        discount = conditions.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
        conditions.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(100),
registrasion/views.py
Show inline comments
 
import sys
 

	
 
from registrasion import forms
 
from registrasion import models as rego
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 
from registrasion.models import people
 
from registrasion.controllers import discount
 
from registrasion.controllers.cart import CartController
 
from registrasion.controllers.credit_note import CreditNoteController
...
 
@@ -60,7 +62,7 @@ def guided_registration(request, page_id=0):
 

	
 
    sections = []
 

	
 
    attendee = rego.Attendee.get_instance(request.user)
 
    attendee = people.Attendee.get_instance(request.user)
 

	
 
    if attendee.completed_registration:
 
        return render(
...
 
@@ -111,7 +113,7 @@ def guided_registration(request, page_id=0):
 
        starting = attendee.guided_categories_complete.count() == 0
 

	
 
        # Get the next category
 
        cats = rego.Category.objects
 
        cats = inventory.Category.objects
 
        if SESSION_KEY in request.session:
 
            _cats = request.session[SESSION_KEY]
 
            cats = cats.filter(id__in=_cats)
...
 
@@ -134,7 +136,7 @@ def guided_registration(request, page_id=0):
 
            current_step = 3
 
            title = "Additional items"
 

	
 
        all_products = rego.Product.objects.filter(
 
        all_products = inventory.Product.objects.filter(
 
            category__in=cats,
 
        ).select_related("category")
 

	
...
 
@@ -217,11 +219,13 @@ def edit_profile(request):
 
def handle_profile(request, prefix):
 
    ''' Returns a profile form instance, and a boolean which is true if the
 
    form was handled. '''
 
    attendee = rego.Attendee.get_instance(request.user)
 
    attendee = people.Attendee.get_instance(request.user)
 

	
 
    try:
 
        profile = attendee.attendeeprofilebase
 
        profile = rego.AttendeeProfileBase.objects.get_subclass(pk=profile.id)
 
        profile = people.AttendeeProfileBase.objects.get_subclass(
 
            pk=profile.id,
 
        )
 
    except ObjectDoesNotExist:
 
        profile = None
 

	
...
 
@@ -270,7 +274,7 @@ def product_category(request, category_id):
 
    voucher_form, voucher_handled = v
 

	
 
    category_id = int(category_id)  # Routing is [0-9]+
 
    category = rego.Category.objects.get(pk=category_id)
 
    category = inventory.Category.objects.get(pk=category_id)
 

	
 
    products = ProductController.available_products(
 
        request.user,
...
 
@@ -316,7 +320,7 @@ def handle_products(request, category, products, prefix):
 
    ProductsForm = forms.ProductsForm(category, products)
 

	
 
    # Create initial data for each of products in category
 
    items = rego.ProductItem.objects.filter(
 
    items = commerce.ProductItem.objects.filter(
 
        product__in=products,
 
        cart=current_cart.cart,
 
    ).select_related("product")
...
 
@@ -344,8 +348,8 @@ def handle_products(request, category, products, prefix):
 
        # If category is required, the user must have at least one
 
        # in an active+valid cart
 
        if category.required:
 
            carts = rego.Cart.objects.filter(user=request.user)
 
            items = rego.ProductItem.objects.filter(
 
            carts = commerce.Cart.objects.filter(user=request.user)
 
            items = commerce.ProductItem.objects.filter(
 
                product__category=category,
 
                cart=carts,
 
            )
...
 
@@ -366,7 +370,7 @@ def set_quantities_from_products_form(products_form, current_cart):
 
    quantities = list(products_form.product_quantities())
 

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

	
...
 
@@ -384,7 +388,7 @@ def set_quantities_from_products_form(products_form, current_cart):
 
            product, message = ve_field.message
 
            if product in field_names:
 
                field = field_names[product]
 
            elif isinstance(product, rego.Product):
 
            elif isinstance(product, inventory.Product):
 
                continue
 
            else:
 
                field = None
...
 
@@ -402,7 +406,7 @@ def handle_voucher(request, prefix):
 
            voucher_form.cleaned_data["voucher"].strip()):
 

	
 
        voucher = voucher_form.cleaned_data["voucher"]
 
        voucher = rego.Voucher.normalise_code(voucher)
 
        voucher = inventory.Voucher.normalise_code(voucher)
 

	
 
        if len(current_cart.cart.vouchers.filter(code=voucher)) > 0:
 
            # This voucher has already been applied to this cart.
...
 
@@ -457,9 +461,9 @@ def invoice_access(request, access_code):
 
    ''' Redirects to the first unpaid invoice for the attendee that matches
 
    the given access code, if any. '''
 

	
 
    invoices = rego.Invoice.objects.filter(
 
    invoices = commerce.Invoice.objects.filter(
 
        user__attendee__access_code=access_code,
 
        status=rego.Invoice.STATUS_UNPAID,
 
        status=commerce.Invoice.STATUS_UNPAID,
 
    ).order_by("issue_time")
 

	
 
    if not invoices:
...
 
@@ -478,7 +482,7 @@ def invoice(request, invoice_id, access_code=None):
 
    '''
 

	
 
    invoice_id = int(invoice_id)
 
    inv = rego.Invoice.objects.get(pk=invoice_id)
 
    inv = commerce.Invoice.objects.get(pk=invoice_id)
 

	
 
    current_invoice = InvoiceController(inv)
 

	
...
 
@@ -505,7 +509,7 @@ def manual_payment(request, invoice_id):
 
        raise Http404()
 

	
 
    invoice_id = int(invoice_id)
 
    inv = get_object_or_404(rego.Invoice, pk=invoice_id)
 
    inv = get_object_or_404(commerce.Invoice, pk=invoice_id)
 
    current_invoice = InvoiceController(inv)
 

	
 
    form = forms.ManualPaymentForm(
...
 
@@ -536,7 +540,7 @@ def refund(request, invoice_id):
 
        raise Http404()
 

	
 
    invoice_id = int(invoice_id)
 
    inv = get_object_or_404(rego.Invoice, pk=invoice_id)
 
    inv = get_object_or_404(commerce.Invoice, pk=invoice_id)
 
    current_invoice = InvoiceController(inv)
 

	
 
    try:
...
 
@@ -557,7 +561,7 @@ def credit_note(request, note_id, access_code=None):
 
        raise Http404()
 

	
 
    note_id = int(note_id)
 
    note = rego.CreditNote.objects.get(pk=note_id)
 
    note = commerce.CreditNote.objects.get(pk=note_id)
 

	
 
    current_note = CreditNoteController(note)
 

	
...
 
@@ -574,10 +578,11 @@ def credit_note(request, note_id, access_code=None):
 

	
 
    if request.POST and apply_form.is_valid():
 
        inv_id = apply_form.cleaned_data["invoice"]
 
        invoice = rego.Invoice.objects.get(pk=inv_id)
 
        invoice = commerce.Invoice.objects.get(pk=inv_id)
 
        current_note.apply_to_invoice(invoice)
 
        messages.success(request,
 
            "Applied credit note %d to invoice." % note_id
 
        messages.success(
 
            request,
 
            "Applied credit note %d to invoice." % note_id,
 
        )
 
        return redirect("invoice", invoice.id)
 

	
...
 
@@ -585,7 +590,8 @@ def credit_note(request, note_id, access_code=None):
 
        refund_form.instance.entered_by = request.user
 
        refund_form.instance.parent = note
 
        refund_form.save()
 
        messages.success(request,
 
        messages.success(
 
            request,
 
            "Applied manual refund to credit note."
 
        )
 
        return redirect("invoice", invoice.id)
0 comments (0 inline, 0 general)