Consolidates models.py into a directory module.

This commit is contained in:
Christopher Neugebauer 2016-04-22 15:06:24 +10:00
parent 278ca23c29
commit 875f736d67
24 changed files with 1193 additions and 1040 deletions

View file

@ -4,7 +4,8 @@ from django.utils.translation import ugettext_lazy as _
import nested_admin import nested_admin
from registrasion import models as rego from registrasion.models import conditions
from registrasion.models import inventory
class EffectsDisplayMixin(object): class EffectsDisplayMixin(object):
@ -15,12 +16,12 @@ class EffectsDisplayMixin(object):
class ProductInline(admin.TabularInline): class ProductInline(admin.TabularInline):
model = rego.Product model = inventory.Product
@admin.register(rego.Category) @admin.register(inventory.Category)
class CategoryAdmin(admin.ModelAdmin): class CategoryAdmin(admin.ModelAdmin):
model = rego.Category model = inventory.Category
fields = ("name", "description", "required", "render_type", fields = ("name", "description", "required", "render_type",
"limit_per_user", "order",) "limit_per_user", "order",)
list_display = ("name", "description") list_display = ("name", "description")
@ -29,9 +30,9 @@ class CategoryAdmin(admin.ModelAdmin):
] ]
@admin.register(rego.Product) @admin.register(inventory.Product)
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
model = rego.Product model = inventory.Product
list_display = ("name", "category", "description") list_display = ("name", "category", "description")
list_filter = ("category", ) list_filter = ("category", )
@ -39,18 +40,18 @@ class ProductAdmin(admin.ModelAdmin):
# Discounts # Discounts
class DiscountForProductInline(admin.TabularInline): class DiscountForProductInline(admin.TabularInline):
model = rego.DiscountForProduct model = conditions.DiscountForProduct
verbose_name = _("Product included in discount") verbose_name = _("Product included in discount")
verbose_name_plural = _("Products included in discount") verbose_name_plural = _("Products included in discount")
class DiscountForCategoryInline(admin.TabularInline): class DiscountForCategoryInline(admin.TabularInline):
model = rego.DiscountForCategory model = conditions.DiscountForCategory
verbose_name = _("Category included in discount") verbose_name = _("Category included in discount")
verbose_name_plural = _("Categories included in discount") verbose_name_plural = _("Categories included in discount")
@admin.register(rego.TimeOrStockLimitDiscount) @admin.register(conditions.TimeOrStockLimitDiscount)
class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
list_display = ( list_display = (
"description", "description",
@ -67,7 +68,7 @@ class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
] ]
@admin.register(rego.IncludedProductDiscount) @admin.register(conditions.IncludedProductDiscount)
class IncludedProductDiscountAdmin(admin.ModelAdmin): class IncludedProductDiscountAdmin(admin.ModelAdmin):
def enablers(self, obj): def enablers(self, obj):
@ -87,7 +88,7 @@ class IncludedProductDiscountAdmin(admin.ModelAdmin):
# Vouchers # Vouchers
class VoucherDiscountInline(nested_admin.NestedStackedInline): class VoucherDiscountInline(nested_admin.NestedStackedInline):
model = rego.VoucherDiscount model = conditions.VoucherDiscount
verbose_name = _("Discount") verbose_name = _("Discount")
# TODO work out why we're allowed to add more than one? # 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): class VoucherFlagInline(nested_admin.NestedStackedInline):
model = rego.VoucherFlag model = conditions.VoucherFlag
verbose_name = _("Product and category enabled by voucher") verbose_name = _("Product and category enabled by voucher")
verbose_name_plural = _("Products and categories enabled by voucher") verbose_name_plural = _("Products and categories enabled by voucher")
@ -109,7 +110,7 @@ class VoucherFlagInline(nested_admin.NestedStackedInline):
extra = 1 extra = 1
@admin.register(rego.Voucher) @admin.register(inventory.Voucher)
class VoucherAdmin(nested_admin.NestedAdmin): class VoucherAdmin(nested_admin.NestedAdmin):
def effects(self, obj): def effects(self, obj):
@ -133,7 +134,7 @@ class VoucherAdmin(nested_admin.NestedAdmin):
return "\n".join(out) return "\n".join(out)
model = rego.Voucher model = inventory.Voucher
list_display = ("recipient", "code", "effects") list_display = ("recipient", "code", "effects")
inlines = [ inlines = [
VoucherDiscountInline, VoucherDiscountInline,
@ -142,7 +143,7 @@ class VoucherAdmin(nested_admin.NestedAdmin):
# Enabling conditions # Enabling conditions
@admin.register(rego.ProductFlag) @admin.register(conditions.ProductFlag)
class ProductFlagAdmin( class ProductFlagAdmin(
nested_admin.NestedAdmin, nested_admin.NestedAdmin,
EffectsDisplayMixin): EffectsDisplayMixin):
@ -150,7 +151,7 @@ class ProductFlagAdmin(
def enablers(self, obj): def enablers(self, obj):
return list(obj.enabling_products.all()) return list(obj.enabling_products.all())
model = rego.ProductFlag model = conditions.ProductFlag
fields = ("description", "enabling_products", "condition", "products", fields = ("description", "enabling_products", "condition", "products",
"categories"), "categories"),
@ -158,12 +159,12 @@ class ProductFlagAdmin(
# Enabling conditions # Enabling conditions
@admin.register(rego.CategoryFlag) @admin.register(conditions.CategoryFlag)
class CategoryFlagAdmin( class CategoryFlagAdmin(
nested_admin.NestedAdmin, nested_admin.NestedAdmin,
EffectsDisplayMixin): EffectsDisplayMixin):
model = rego.CategoryFlag model = conditions.CategoryFlag
fields = ("description", "enabling_category", "condition", "products", fields = ("description", "enabling_category", "condition", "products",
"categories"), "categories"),
@ -172,11 +173,11 @@ class CategoryFlagAdmin(
# Enabling conditions # Enabling conditions
@admin.register(rego.TimeOrStockLimitFlag) @admin.register(conditions.TimeOrStockLimitFlag)
class TimeOrStockLimitFlagAdmin( class TimeOrStockLimitFlagAdmin(
nested_admin.NestedAdmin, nested_admin.NestedAdmin,
EffectsDisplayMixin): EffectsDisplayMixin):
model = rego.TimeOrStockLimitFlag model = conditions.TimeOrStockLimitFlag
list_display = ( list_display = (
"description", "description",

View file

@ -9,8 +9,10 @@ from django.db import transaction
from django.db.models import Max from django.db.models import Max
from django.utils import timezone from django.utils import timezone
from registrasion import models as rego
from registrasion.exceptions import CartValidationError 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 category import CategoryController
from conditions import ConditionController from conditions import ConditionController
@ -28,9 +30,9 @@ class CartController(object):
if there isn't one ready yet. ''' if there isn't one ready yet. '''
try: try:
existing = rego.Cart.objects.get(user=user, active=True) existing = commerce.Cart.objects.get(user=user, active=True)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing = rego.Cart.objects.create( existing = commerce.Cart.objects.create(
user=user, user=user,
time_last_updated=timezone.now(), time_last_updated=timezone.now(),
reservation_duration=datetime.timedelta(), reservation_duration=datetime.timedelta(),
@ -47,10 +49,10 @@ class CartController(object):
# If we have vouchers, we're entitled to an hour at minimum. # If we have vouchers, we're entitled to an hour at minimum.
if len(self.cart.vouchers.all()) >= 1: 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 # 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")) agg = items.aggregate(Max("product__reservation_duration"))
product_max = agg["product__reservation_duration__max"] product_max = agg["product__reservation_duration__max"]
@ -79,7 +81,7 @@ class CartController(object):
is violated. `product_quantities` is an iterable of (product, quantity) is violated. `product_quantities` is an iterable of (product, quantity)
pairs. ''' 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( items_in_cart = items_in_cart.select_related(
"product", "product",
"product__category", "product__category",
@ -99,14 +101,14 @@ class CartController(object):
for product, quantity in product_quantities: for product, quantity in product_quantities:
try: try:
product_item = rego.ProductItem.objects.get( product_item = commerce.ProductItem.objects.get(
cart=self.cart, cart=self.cart,
product=product, product=product,
) )
product_item.quantity = quantity product_item.quantity = quantity
product_item.save() product_item.save()
except ObjectDoesNotExist: except ObjectDoesNotExist:
rego.ProductItem.objects.create( commerce.ProductItem.objects.create(
cart=self.cart, cart=self.cart,
product=product, product=product,
quantity=quantity, quantity=quantity,
@ -176,7 +178,7 @@ class CartController(object):
''' Applies the voucher with the given code to this cart. ''' ''' Applies the voucher with the given code to this cart. '''
# Try and find the voucher # 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 # Re-applying vouchers should be idempotent
if voucher in self.cart.vouchers.all(): if voucher in self.cart.vouchers.all():
@ -193,7 +195,7 @@ class CartController(object):
Raises ValidationError if not. ''' Raises ValidationError if not. '''
# Is voucher exhausted? # 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 # It's invalid for a user to enter a voucher that's exhausted
carts_with_voucher = active_carts.filter(vouchers=voucher) carts_with_voucher = active_carts.filter(vouchers=voucher)
@ -238,7 +240,7 @@ class CartController(object):
except ValidationError as ve: except ValidationError as ve:
errors.append(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) product_quantities = list((i.product, i.quantity) for i in items)
try: try:
@ -248,7 +250,7 @@ class CartController(object):
errors.append(error.message[1]) errors.append(error.message[1])
# Validate the discounts # Validate the discounts
discount_items = rego.DiscountItem.objects.filter(cart=cart) discount_items = commerce.DiscountItem.objects.filter(cart=cart)
seen_discounts = set() seen_discounts = set()
for discount_item in discount_items: for discount_item in discount_items:
@ -256,7 +258,7 @@ class CartController(object):
if discount in seen_discounts: if discount in seen_discounts:
continue continue
seen_discounts.add(discount) seen_discounts.add(discount)
real_discount = rego.DiscountBase.objects.get_subclass( real_discount = conditions.DiscountBase.objects.get_subclass(
pk=discount.pk) pk=discount.pk)
cond = ConditionController.for_condition(real_discount) cond = ConditionController.for_condition(real_discount)
@ -287,7 +289,7 @@ class CartController(object):
self.cart.vouchers.remove(voucher) self.cart.vouchers.remove(voucher)
# Fix products and discounts # 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") items = items.select_related("product")
products = set(i.product for i in items) products = set(i.product for i in items)
available = set(ProductController.available_products( available = set(ProductController.available_products(
@ -306,7 +308,7 @@ class CartController(object):
''' '''
# Delete the existing entries. # 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_items = self.cart.productitem_set.all().select_related(
"product", "product__category", "product", "product__category",
@ -331,7 +333,7 @@ class CartController(object):
def matches(discount): def matches(discount):
''' Returns True if and only if the given discount apples to ''' Returns True if and only if the given discount apples to
our product. ''' our product. '''
if isinstance(discount.clause, rego.DiscountForCategory): if isinstance(discount.clause, conditions.DiscountForCategory):
return discount.clause.category == product.category return discount.clause.category == product.category
else: else:
return discount.clause.product == product return discount.clause.product == product
@ -356,7 +358,7 @@ class CartController(object):
# Get a provisional instance for this DiscountItem # Get a provisional instance for this DiscountItem
# with the quantity set to as much as we have in the cart # 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, product=product,
cart=self.cart, cart=self.cart,
discount=candidate.discount, discount=candidate.discount,

View file

@ -1,4 +1,5 @@
from registrasion import models as rego from registrasion.models import commerce
from registrasion.models import inventory
from django.db.models import Sum from django.db.models import Sum
@ -22,7 +23,9 @@ class CategoryController(object):
from product import ProductController from product import ProductController
if products is AllProducts: if products is AllProducts:
products = rego.Product.objects.all().select_related("category") products = inventory.Product.objects.all().select_related(
"category",
)
available = ProductController.available_products( available = ProductController.available_products(
user, user,
@ -41,13 +44,13 @@ class CategoryController(object):
# We don't need to waste the following queries # We don't need to waste the following queries
return 99999999 return 99999999
carts = rego.Cart.objects.filter( carts = commerce.Cart.objects.filter(
user=user, user=user,
active=False, active=False,
released=False, released=False,
) )
items = rego.ProductItem.objects.filter( items = commerce.ProductItem.objects.filter(
cart__in=carts, cart__in=carts,
product__category=self.category, product__category=self.category,
) )

View file

@ -7,7 +7,9 @@ from collections import namedtuple
from django.db.models import Sum from django.db.models import Sum
from django.utils import timezone 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( ConditionAndRemainder = namedtuple(
@ -29,15 +31,15 @@ class ConditionController(object):
@staticmethod @staticmethod
def for_condition(condition): def for_condition(condition):
CONTROLLERS = { CONTROLLERS = {
rego.CategoryFlag: CategoryConditionController, conditions.CategoryFlag: CategoryConditionController,
rego.IncludedProductDiscount: ProductConditionController, conditions.IncludedProductDiscount: ProductConditionController,
rego.ProductFlag: ProductConditionController, conditions.ProductFlag: ProductConditionController,
rego.TimeOrStockLimitDiscount: conditions.TimeOrStockLimitDiscount:
TimeOrStockLimitDiscountController, TimeOrStockLimitDiscountController,
rego.TimeOrStockLimitFlag: conditions.TimeOrStockLimitFlag:
TimeOrStockLimitFlagController, TimeOrStockLimitFlagController,
rego.VoucherDiscount: VoucherConditionController, conditions.VoucherDiscount: VoucherConditionController,
rego.VoucherFlag: VoucherConditionController, conditions.VoucherFlag: VoucherConditionController,
} }
try: try:
@ -121,7 +123,7 @@ class ConditionController(object):
# Get all products covered by this condition, and the products # Get all products covered by this condition, and the products
# from the categories covered by this condition # from the categories covered by this condition
cond_products = condition.products.all() cond_products = condition.products.all()
from_category = rego.Product.objects.filter( from_category = inventory.Product.objects.filter(
category__in=condition.categories.all(), category__in=condition.categories.all(),
).all() ).all()
all_products = cond_products | from_category 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 ''' returns True if the user has a product from a category that invokes
this condition in one of their carts ''' this condition in one of their carts '''
carts = rego.Cart.objects.filter(user=user, released=False) carts = commerce.Cart.objects.filter(user=user, released=False)
enabling_products = rego.Product.objects.filter( enabling_products = inventory.Product.objects.filter(
category=self.condition.enabling_category, category=self.condition.enabling_category,
) )
products_count = rego.ProductItem.objects.filter( products_count = commerce.ProductItem.objects.filter(
cart__in=carts, cart__in=carts,
product__in=enabling_products, product__in=enabling_products,
).count() ).count()
@ -221,8 +223,8 @@ class ProductConditionController(ConditionController):
''' returns True if the user has a product that invokes this ''' returns True if the user has a product that invokes this
condition in one of their carts ''' condition in one of their carts '''
carts = rego.Cart.objects.filter(user=user, released=False) carts = commerce.Cart.objects.filter(user=user, released=False)
products_count = rego.ProductItem.objects.filter( products_count = commerce.ProductItem.objects.filter(
cart__in=carts, cart__in=carts,
product__in=self.condition.enabling_products.all(), product__in=self.condition.enabling_products.all(),
).count() ).count()
@ -267,7 +269,7 @@ class TimeOrStockLimitConditionController(ConditionController):
return 99999999 return 99999999
# We care about all reserved carts, but not the user's current cart # 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( reserved_carts = reserved_carts.exclude(
user=user, user=user,
active=True, active=True,
@ -284,12 +286,12 @@ class TimeOrStockLimitFlagController(
TimeOrStockLimitConditionController): TimeOrStockLimitConditionController):
def _items(self): def _items(self):
category_products = rego.Product.objects.filter( category_products = inventory.Product.objects.filter(
category__in=self.ceiling.categories.all(), category__in=self.ceiling.categories.all(),
) )
products = self.ceiling.products.all() | category_products products = self.ceiling.products.all() | category_products
product_items = rego.ProductItem.objects.filter( product_items = commerce.ProductItem.objects.filter(
product__in=products.all(), product__in=products.all(),
) )
return product_items return product_items
@ -298,7 +300,7 @@ class TimeOrStockLimitFlagController(
class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController): class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController):
def _items(self): def _items(self):
discount_items = rego.DiscountItem.objects.filter( discount_items = commerce.DiscountItem.objects.filter(
discount=self.ceiling, discount=self.ceiling,
) )
return discount_items return discount_items
@ -312,7 +314,7 @@ class VoucherConditionController(ConditionController):
def is_met(self, user): def is_met(self, user):
''' returns True if the user has the given voucher attached. ''' ''' returns True if the user has the given voucher attached. '''
carts_count = rego.Cart.objects.filter( carts_count = commerce.Cart.objects.filter(
user=user, user=user,
vouchers=self.condition.voucher, vouchers=self.condition.voucher,
).count() ).count()

View file

@ -1,6 +1,6 @@
from django.db import transaction from django.db import transaction
from registrasion import models as rego from registrasion.models import commerce
class CreditNoteController(object): class CreditNoteController(object):
@ -14,7 +14,7 @@ class CreditNoteController(object):
the given invoice. You need to call InvoiceController.update_status() the given invoice. You need to call InvoiceController.update_status()
to set the status correctly, if appropriate. ''' to set the status correctly, if appropriate. '''
credit_note = rego.CreditNote.objects.create( credit_note = commerce.CreditNote.objects.create(
invoice=invoice, invoice=invoice,
amount=0-value, # Credit notes start off as a payment against inv. amount=0-value, # Credit notes start off as a payment against inv.
reference="ONE MOMENT", reference="ONE MOMENT",
@ -39,7 +39,7 @@ class CreditNoteController(object):
inv.validate_allowed_to_pay() inv.validate_allowed_to_pay()
# Apply payment to invoice # Apply payment to invoice
rego.CreditNoteApplication.objects.create( commerce.CreditNoteApplication.objects.create(
parent=self.credit_note, parent=self.credit_note,
invoice=invoice, invoice=invoice,
amount=self.credit_note.value, amount=self.credit_note.value,

View file

@ -1,7 +1,8 @@
import itertools import itertools
from conditions import ConditionController 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 from django.db.models import Sum
@ -24,15 +25,15 @@ def available_discounts(user, categories, products):
not including products that are pending purchase. ''' not including products that are pending purchase. '''
# discounts that match provided categories # discounts that match provided categories
category_discounts = rego.DiscountForCategory.objects.filter( category_discounts = conditions.DiscountForCategory.objects.filter(
category__in=categories category__in=categories
) )
# discounts that match provided products # discounts that match provided products
product_discounts = rego.DiscountForProduct.objects.filter( product_discounts = conditions.DiscountForProduct.objects.filter(
product__in=products product__in=products
) )
# discounts that match categories for provided 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) category__in=(product.category for product in products)
) )
# (Not relevant: discounts that match products in provided categories) # (Not relevant: discounts that match products in provided categories)
@ -60,7 +61,7 @@ def available_discounts(user, categories, products):
failed_discounts = set() failed_discounts = set()
for discount in potential_discounts: for discount in potential_discounts:
real_discount = rego.DiscountBase.objects.get_subclass( real_discount = conditions.DiscountBase.objects.get_subclass(
pk=discount.discount.pk, pk=discount.discount.pk,
) )
cond = ConditionController.for_condition(real_discount) 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. # Count the past uses of the given discount item.
# If this user has exceeded the limit for the clause, this clause # If this user has exceeded the limit for the clause, this clause
# is not available any more. # is not available any more.
past_uses = rego.DiscountItem.objects.filter( past_uses = commerce.DiscountItem.objects.filter(
cart__user=user, cart__user=user,
cart__active=False, # Only past carts count cart__active=False, # Only past carts count
cart__released=False, # You can reuse refunded discounts cart__released=False, # You can reuse refunded discounts

View file

@ -5,7 +5,9 @@ from django.db import transaction
from django.db.models import Sum from django.db.models import Sum
from django.utils import timezone 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 cart import CartController
from credit_note import CreditNoteController from credit_note import CreditNoteController
@ -25,8 +27,8 @@ class InvoiceController(object):
an invoice is generated.''' an invoice is generated.'''
try: try:
invoice = rego.Invoice.objects.exclude( invoice = commerce.Invoice.objects.exclude(
status=rego.Invoice.STATUS_VOID, status=commerce.Invoice.STATUS_VOID,
).get( ).get(
cart=cart, cart=cart,
cart_revision=cart.revision, cart_revision=cart.revision,
@ -42,19 +44,19 @@ class InvoiceController(object):
@classmethod @classmethod
def void_all_invoices(cls, cart): 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: for invoice in invoices:
cls(invoice).void() cls(invoice).void()
@classmethod @classmethod
def resolve_discount_value(cls, item): def resolve_discount_value(cls, item):
try: try:
condition = rego.DiscountForProduct.objects.get( condition = conditions.DiscountForProduct.objects.get(
discount=item.discount, discount=item.discount,
product=item.product product=item.product
) )
except ObjectDoesNotExist: except ObjectDoesNotExist:
condition = rego.DiscountForCategory.objects.get( condition = conditions.DiscountForCategory.objects.get(
discount=item.discount, discount=item.discount,
category=item.product.category category=item.product.category
) )
@ -75,22 +77,22 @@ class InvoiceController(object):
due = max(issued, reservation_limit) due = max(issued, reservation_limit)
# Get the invoice recipient # Get the invoice recipient
profile = rego.AttendeeProfileBase.objects.get_subclass( profile = people.AttendeeProfileBase.objects.get_subclass(
id=cart.user.attendee.attendeeprofilebase.id, id=cart.user.attendee.attendeeprofilebase.id,
) )
recipient = profile.invoice_recipient() recipient = profile.invoice_recipient()
invoice = rego.Invoice.objects.create( invoice = commerce.Invoice.objects.create(
user=cart.user, user=cart.user,
cart=cart, cart=cart,
cart_revision=cart.revision, cart_revision=cart.revision,
status=rego.Invoice.STATUS_UNPAID, status=commerce.Invoice.STATUS_UNPAID,
value=Decimal(), value=Decimal(),
issue_time=issued, issue_time=issued,
due_time=due, due_time=due,
recipient=recipient, recipient=recipient,
) )
product_items = rego.ProductItem.objects.filter(cart=cart) product_items = commerce.ProductItem.objects.filter(cart=cart)
if len(product_items) == 0: if len(product_items) == 0:
raise ValidationError("Your cart is empty.") raise ValidationError("Your cart is empty.")
@ -98,11 +100,11 @@ class InvoiceController(object):
product_items = product_items.order_by( product_items = product_items.order_by(
"product__category__order", "product__order" "product__category__order", "product__order"
) )
discount_items = rego.DiscountItem.objects.filter(cart=cart) discount_items = commerce.DiscountItem.objects.filter(cart=cart)
invoice_value = Decimal() invoice_value = Decimal()
for item in product_items: for item in product_items:
product = item.product product = item.product
line_item = rego.LineItem.objects.create( line_item = commerce.LineItem.objects.create(
invoice=invoice, invoice=invoice,
description="%s - %s" % (product.category.name, product.name), description="%s - %s" % (product.category.name, product.name),
quantity=item.quantity, quantity=item.quantity,
@ -112,7 +114,7 @@ class InvoiceController(object):
invoice_value += line_item.quantity * line_item.price invoice_value += line_item.quantity * line_item.price
for item in discount_items: for item in discount_items:
line_item = rego.LineItem.objects.create( line_item = commerce.LineItem.objects.create(
invoice=invoice, invoice=invoice,
description=item.discount.description, description=item.discount.description,
quantity=item.quantity, quantity=item.quantity,
@ -170,7 +172,7 @@ class InvoiceController(object):
def total_payments(self): def total_payments(self):
''' Returns the total amount paid towards this invoice. ''' ''' 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 total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0
return total_paid return total_paid
@ -180,12 +182,12 @@ class InvoiceController(object):
old_status = self.invoice.status old_status = self.invoice.status
total_paid = self.total_payments() total_paid = self.total_payments()
num_payments = rego.PaymentBase.objects.filter( num_payments = commerce.PaymentBase.objects.filter(
invoice=self.invoice, invoice=self.invoice,
).count() ).count()
remainder = self.invoice.value - total_paid 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 # Invoice had an amount owing
if remainder <= 0: if remainder <= 0:
# Invoice no longer has amount owing # Invoice no longer has amount owing
@ -199,15 +201,15 @@ class InvoiceController(object):
elif total_paid == 0 and num_payments > 0: elif total_paid == 0 and num_payments > 0:
# Invoice has multiple payments totalling zero # Invoice has multiple payments totalling zero
self._mark_void() self._mark_void()
elif old_status == rego.Invoice.STATUS_PAID: elif old_status == commerce.Invoice.STATUS_PAID:
if remainder > 0: if remainder > 0:
# Invoice went from having a remainder of zero or less # Invoice went from having a remainder of zero or less
# to having a positive remainder -- must be a refund # to having a positive remainder -- must be a refund
self._mark_refunded() self._mark_refunded()
elif old_status == rego.Invoice.STATUS_REFUNDED: elif old_status == commerce.Invoice.STATUS_REFUNDED:
# Should not ever change from here # Should not ever change from here
pass pass
elif old_status == rego.Invoice.STATUS_VOID: elif old_status == commerce.Invoice.STATUS_VOID:
# Should not ever change from here # Should not ever change from here
pass pass
@ -218,7 +220,7 @@ class InvoiceController(object):
if cart: if cart:
cart.active = False cart.active = False
cart.save() cart.save()
self.invoice.status = rego.Invoice.STATUS_PAID self.invoice.status = commerce.Invoice.STATUS_PAID
self.invoice.save() self.invoice.save()
def _mark_refunded(self): def _mark_refunded(self):
@ -229,13 +231,13 @@ class InvoiceController(object):
cart.active = False cart.active = False
cart.released = True cart.released = True
cart.save() cart.save()
self.invoice.status = rego.Invoice.STATUS_REFUNDED self.invoice.status = commerce.Invoice.STATUS_REFUNDED
self.invoice.save() self.invoice.save()
def _mark_void(self): def _mark_void(self):
''' Marks the invoice as refunded, and updates the attached cart if ''' Marks the invoice as refunded, and updates the attached cart if
necessary. ''' necessary. '''
self.invoice.status = rego.Invoice.STATUS_VOID self.invoice.status = commerce.Invoice.STATUS_VOID
self.invoice.save() self.invoice.save()
def _invoice_matches_cart(self): def _invoice_matches_cart(self):

View file

@ -1,7 +1,8 @@
import itertools import itertools
from django.db.models import Sum 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 category import CategoryController
from conditions import ConditionController from conditions import ConditionController
@ -22,7 +23,7 @@ class ProductController(object):
raise ValueError("You must provide products or a category") raise ValueError("You must provide products or a category")
if category is not None: 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") all_products = all_products.select_related("category")
else: else:
all_products = [] all_products = []
@ -65,13 +66,13 @@ class ProductController(object):
# Don't need to run the remaining queries # Don't need to run the remaining queries
return 999999 # We can do better return 999999 # We can do better
carts = rego.Cart.objects.filter( carts = commerce.Cart.objects.filter(
user=user, user=user,
active=False, active=False,
released=False, released=False,
) )
items = rego.ProductItem.objects.filter( items = commerce.ProductItem.objects.filter(
cart__in=carts, cart__in=carts,
product=self.product, product=self.product,
) )

View file

@ -1,4 +1,5 @@
import models as rego from registrasion.models import commerce
from registrasion.models import inventory
from django import forms from django import forms
@ -14,8 +15,8 @@ class ApplyCreditNoteForm(forms.Form):
self.fields["invoice"].choices = self._unpaid_invoices_for_user self.fields["invoice"].choices = self._unpaid_invoices_for_user
def _unpaid_invoices_for_user(self): def _unpaid_invoices_for_user(self):
invoices = rego.Invoice.objects.filter( invoices = commerce.Invoice.objects.filter(
status=rego.Invoice.STATUS_UNPAID, status=commerce.Invoice.STATUS_UNPAID,
user=self.user, user=self.user,
) )
@ -25,7 +26,6 @@ class ApplyCreditNoteForm(forms.Form):
] ]
invoice = forms.ChoiceField( invoice = forms.ChoiceField(
#choices=_unpaid_invoices_for_user,
required=True, required=True,
) )
@ -33,14 +33,14 @@ class ApplyCreditNoteForm(forms.Form):
class ManualCreditNoteRefundForm(forms.ModelForm): class ManualCreditNoteRefundForm(forms.ModelForm):
class Meta: class Meta:
model = rego.ManualCreditNoteRefund model = commerce.ManualCreditNoteRefund
fields = ["reference"] fields = ["reference"]
class ManualPaymentForm(forms.ModelForm): class ManualPaymentForm(forms.ModelForm):
class Meta: class Meta:
model = rego.ManualPayment model = commerce.ManualPayment
fields = ["reference", "amount"] fields = ["reference", "amount"]
@ -168,8 +168,8 @@ def ProductsForm(category, products):
# Each Category.RENDER_TYPE value has a subclass here. # Each Category.RENDER_TYPE value has a subclass here.
RENDER_TYPES = { RENDER_TYPES = {
rego.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm, inventory.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
rego.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm, inventory.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
} }
# Produce a subclass of _ProductsForm which we can alter the base_fields on # Produce a subclass of _ProductsForm which we can alter the base_fields on

View file

@ -1,818 +0,0 @@
from __future__ import unicode_literals
import util
import datetime
import itertools
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
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
# User models
@python_2_unicode_compatible
class Attendee(models.Model):
''' Miscellaneous user-related data. '''
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.
'''
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)
# Inventory Models
@python_2_unicode_compatible
class Category(models.Model):
''' Registration product categories '''
class Meta:
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:
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 '''
# 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"))
# 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. '''
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. '''
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(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. '''
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(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:
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:
verbose_name = _("discount (enabled by voucher)")
verbose_name_plural = _("discounts (enabled by voucher)")
voucher = models.OneToOneField(
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:
verbose_name = _("discount (product inclusions)")
verbose_name_plural = _("discounts (product inclusions)")
enabling_products = models.ManyToManyField(
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(
Product,
blank=True,
help_text=_("Products affected by this flag's condition."),
related_name="flagbase_set",
)
categories = models.ManyToManyField(
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.
objects = InheritanceManager()
class TimeOrStockLimitFlag(EnablingConditionBase):
''' Registration product ceilings '''
class Meta:
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:
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(
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:
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(
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:
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(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
# 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:
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(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:
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(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:
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(Product)
discount = models.ForeignKey(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. '''
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:
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(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. '''
pass
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).'''
@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. '''
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. '''
entered_by = models.ForeignKey(User)

View file

@ -0,0 +1,4 @@
from commerce import * # NOQA
from conditions import * # NOQA
from inventory import * # NOQA
from people import * # NOQA

View file

@ -0,0 +1,304 @@
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)

View file

@ -0,0 +1,361 @@
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

View file

@ -0,0 +1,172 @@
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"))

View file

@ -0,0 +1,79 @@
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)

View file

@ -1,4 +1,5 @@
from registrasion import models as rego from registrasion.models import commerce
from registrasion.models import inventory
from registrasion.controllers.category import CategoryController from registrasion.controllers.category import CategoryController
from collections import namedtuple from collections import namedtuple
@ -19,7 +20,7 @@ def available_categories(context):
@register.assignment_tag(takes_context=True) @register.assignment_tag(takes_context=True)
def available_credit(context): def available_credit(context):
''' Returns the amount of unclaimed credit available for this user. ''' ''' 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, invoice__user=context.request.user,
) )
ret = notes.values("amount").aggregate(Sum("amount"))["amount__sum"] or 0 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) @register.assignment_tag(takes_context=True)
def invoices(context): def invoices(context):
''' Returns all of the invoices that this user has. ''' ''' 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) @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, ''' Returns all of the items that this user has in their current cart,
and is awaiting payment. ''' and is awaiting payment. '''
all_items = rego.ProductItem.objects.filter( all_items = commerce.ProductItem.objects.filter(
cart__user=context.request.user, cart__user=context.request.user,
cart__active=True, cart__active=True,
).select_related( ).select_related(
@ -55,7 +56,7 @@ def items_purchased(context, category=None):
''' Returns all of the items that this user has purchased, optionally ''' Returns all of the items that this user has purchased, optionally
from the given category. ''' from the given category. '''
all_items = rego.ProductItem.objects.filter( all_items = commerce.ProductItem.objects.filter(
cart__user=context.request.user, cart__user=context.request.user,
cart__active=False, cart__active=False,
cart__released=False, cart__released=False,
@ -65,7 +66,7 @@ def items_purchased(context, category=None):
all_items = all_items.filter(product__category=category) all_items = all_items.filter(product__category=category)
pq = all_items.values("product").annotate(quantity=Sum("quantity")).all() pq = all_items.values("product").annotate(quantity=Sum("quantity")).all()
products = rego.Product.objects.all() products = inventory.Product.objects.all()
out = [] out = []
for item in pq: for item in pq:
prod = products.get(pk=item["product"]) prod = products.get(pk=item["product"])

View file

@ -1,7 +1,7 @@
from registrasion.controllers.cart import CartController from registrasion.controllers.cart import CartController
from registrasion.controllers.credit_note import CreditNoteController from registrasion.controllers.credit_note import CreditNoteController
from registrasion.controllers.invoice import InvoiceController from registrasion.controllers.invoice import InvoiceController
from registrasion import models as rego from registrasion.models import commerce
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -19,7 +19,7 @@ class TestingCartController(CartController):
ValidationError if constraints are violated.''' ValidationError if constraints are violated.'''
try: try:
product_item = rego.ProductItem.objects.get( product_item = commerce.ProductItem.objects.get(
cart=self.cart, cart=self.cart,
product=product) product=product)
old_quantity = product_item.quantity old_quantity = product_item.quantity
@ -41,7 +41,7 @@ class TestingInvoiceController(InvoiceController):
self.validate_allowed_to_pay() self.validate_allowed_to_pay()
''' Adds a payment ''' ''' Adds a payment '''
rego.ManualPayment.objects.create( commerce.ManualPayment.objects.create(
invoice=self.invoice, invoice=self.invoice,
reference=reference, reference=reference,
amount=amount, amount=amount,
@ -53,7 +53,7 @@ class TestingInvoiceController(InvoiceController):
class TestingCreditNoteController(CreditNoteController): class TestingCreditNoteController(CreditNoteController):
def refund(self): def refund(self):
rego.CreditNoteRefund.objects.create( commerce.CreditNoteRefund.objects.create(
parent=self.credit_note, parent=self.credit_note,
reference="Whoops." reference="Whoops."
) )

View file

@ -7,7 +7,10 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
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 registrasion.controllers.product import ProductController
from controller_helpers import TestingCartController from controller_helpers import TestingCartController
@ -36,24 +39,28 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
email='test2@example.com', email='test2@example.com',
password='top_secret') password='top_secret')
attendee1 = rego.Attendee.get_instance(cls.USER_1) attendee1 = people.Attendee.get_instance(cls.USER_1)
attendee1.save() attendee1.save()
profile1 = rego.AttendeeProfileBase.objects.create(attendee=attendee1) profile1 = people.AttendeeProfileBase.objects.create(
attendee=attendee1,
)
profile1.save() profile1.save()
attendee2 = rego.Attendee.get_instance(cls.USER_2) attendee2 = people.Attendee.get_instance(cls.USER_2)
attendee2.save() attendee2.save()
profile2 = rego.AttendeeProfileBase.objects.create(attendee=attendee2) profile2 = people.AttendeeProfileBase.objects.create(
attendee=attendee2,
)
profile2.save() profile2.save()
cls.RESERVATION = datetime.timedelta(hours=1) cls.RESERVATION = datetime.timedelta(hours=1)
cls.categories = [] cls.categories = []
for i in xrange(2): for i in xrange(2):
cat = rego.Category.objects.create( cat = inventory.Category.objects.create(
name="Category " + str(i + 1), name="Category " + str(i + 1),
description="This is a test category", description="This is a test category",
order=i, order=i,
render_type=rego.Category.RENDER_TYPE_RADIO, render_type=inventory.Category.RENDER_TYPE_RADIO,
required=False, required=False,
) )
cat.save() cat.save()
@ -64,7 +71,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
cls.products = [] cls.products = []
for i in xrange(4): for i in xrange(4):
prod = rego.Product.objects.create( prod = inventory.Product.objects.create(
name="Product " + str(i + 1), name="Product " + str(i + 1),
description="This is a test product.", description="This is a test product.",
category=cls.categories[i / 2], # 2 products per category category=cls.categories[i / 2], # 2 products per category
@ -95,9 +102,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
@classmethod @classmethod
def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create(
description=name, description=name,
condition=rego.FlagBase.DISABLE_IF_FALSE, condition=conditions.FlagBase.DISABLE_IF_FALSE,
limit=limit, limit=limit,
start_time=start_time, start_time=start_time,
end_time=end_time end_time=end_time
@ -109,9 +116,9 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
@classmethod @classmethod
def make_category_ceiling( def make_category_ceiling(
cls, name, limit=None, start_time=None, end_time=None): cls, name, limit=None, start_time=None, end_time=None):
limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( limit_ceiling = conditions.TimeOrStockLimitFlag.objects.create(
description=name, description=name,
condition=rego.FlagBase.DISABLE_IF_FALSE, condition=conditions.FlagBase.DISABLE_IF_FALSE,
limit=limit, limit=limit,
start_time=start_time, start_time=start_time,
end_time=end_time end_time=end_time
@ -124,14 +131,14 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
def make_discount_ceiling( def make_discount_ceiling(
cls, name, limit=None, start_time=None, end_time=None, cls, name, limit=None, start_time=None, end_time=None,
percentage=100): percentage=100):
limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create( limit_ceiling = conditions.TimeOrStockLimitDiscount.objects.create(
description=name, description=name,
start_time=start_time, start_time=start_time,
end_time=end_time, end_time=end_time,
limit=limit, limit=limit,
) )
limit_ceiling.save() limit_ceiling.save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=limit_ceiling, discount=limit_ceiling,
product=cls.PROD_1, product=cls.PROD_1,
percentage=percentage, percentage=percentage,
@ -140,7 +147,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
@classmethod @classmethod
def new_voucher(self, code="VOUCHER", limit=1): def new_voucher(self, code="VOUCHER", limit=1):
voucher = rego.Voucher.objects.create( voucher = inventory.Voucher.objects.create(
recipient="Voucher recipient", recipient="Voucher recipient",
code=code, code=code,
limit=limit, limit=limit,
@ -176,7 +183,7 @@ class BasicCartTests(RegistrationCartTestCase):
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)
# Count of products for a given user should be collapsed. # Count of products for a given user should be collapsed.
items = rego.ProductItem.objects.filter( items = commerce.ProductItem.objects.filter(
cart=current_cart.cart, cart=current_cart.cart,
product=self.PROD_1) product=self.PROD_1)
self.assertEqual(1, len(items)) self.assertEqual(1, len(items))
@ -187,7 +194,7 @@ class BasicCartTests(RegistrationCartTestCase):
current_cart = TestingCartController.for_user(self.USER_1) current_cart = TestingCartController.for_user(self.USER_1)
def get_item(): def get_item():
return rego.ProductItem.objects.get( return commerce.ProductItem.objects.get(
cart=current_cart.cart, cart=current_cart.cart,
product=self.PROD_1) product=self.PROD_1)

View file

@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
from controller_helpers import TestingCartController from controller_helpers import TestingCartController
from test_cart import RegistrationCartTestCase from test_cart import RegistrationCartTestCase
from registrasion import models as rego from registrasion.models import conditions
UTC = pytz.timezone('UTC') UTC = pytz.timezone('UTC')
@ -155,12 +155,12 @@ class CeilingsTestCases(RegistrationCartTestCase):
self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50) self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50)
voucher = self.new_voucher(code="VOUCHER") voucher = self.new_voucher(code="VOUCHER")
discount = rego.VoucherDiscount.objects.create( discount = conditions.VoucherDiscount.objects.create(
description="VOUCHER RECIPIENT", description="VOUCHER RECIPIENT",
voucher=voucher, voucher=voucher,
) )
discount.save() discount.save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=self.PROD_1, product=self.PROD_1,
percentage=100, percentage=100,

View file

@ -2,7 +2,7 @@ import pytz
from decimal import Decimal from decimal import Decimal
from registrasion import models as rego from registrasion.models import conditions
from registrasion.controllers import discount from registrasion.controllers import discount
from controller_helpers import TestingCartController from controller_helpers import TestingCartController
@ -19,13 +19,13 @@ class DiscountTestCase(RegistrationCartTestCase):
amount=Decimal(100), amount=Decimal(100),
quantity=2, quantity=2,
): ):
discount = rego.IncludedProductDiscount.objects.create( discount = conditions.IncludedProductDiscount.objects.create(
description="PROD_1 includes PROD_2 " + str(amount) + "%", description="PROD_1 includes PROD_2 " + str(amount) + "%",
) )
discount.save() discount.save()
discount.enabling_products.add(cls.PROD_1) discount.enabling_products.add(cls.PROD_1)
discount.save() discount.save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=cls.PROD_2, product=cls.PROD_2,
percentage=amount, percentage=amount,
@ -39,13 +39,13 @@ class DiscountTestCase(RegistrationCartTestCase):
amount=Decimal(100), amount=Decimal(100),
quantity=2, quantity=2,
): ):
discount = rego.IncludedProductDiscount.objects.create( discount = conditions.IncludedProductDiscount.objects.create(
description="PROD_1 includes CAT_2 " + str(amount) + "%", description="PROD_1 includes CAT_2 " + str(amount) + "%",
) )
discount.save() discount.save()
discount.enabling_products.add(cls.PROD_1) discount.enabling_products.add(cls.PROD_1)
discount.save() discount.save()
rego.DiscountForCategory.objects.create( conditions.DiscountForCategory.objects.create(
discount=discount, discount=discount,
category=cls.CAT_2, category=cls.CAT_2,
percentage=amount, percentage=amount,
@ -59,20 +59,20 @@ class DiscountTestCase(RegistrationCartTestCase):
amount=Decimal(100), amount=Decimal(100),
quantity=2, quantity=2,
): ):
discount = rego.IncludedProductDiscount.objects.create( discount = conditions.IncludedProductDiscount.objects.create(
description="PROD_1 includes PROD_3 and PROD_4 " + description="PROD_1 includes PROD_3 and PROD_4 " +
str(amount) + "%", str(amount) + "%",
) )
discount.save() discount.save()
discount.enabling_products.add(cls.PROD_1) discount.enabling_products.add(cls.PROD_1)
discount.save() discount.save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=cls.PROD_3, product=cls.PROD_3,
percentage=amount, percentage=amount,
quantity=quantity, quantity=quantity,
).save() ).save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=cls.PROD_4, product=cls.PROD_4,
percentage=amount, percentage=amount,

View file

@ -2,7 +2,8 @@ import pytz
from django.core.exceptions import ValidationError 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 registrasion.controllers.category import CategoryController
from controller_helpers import TestingCartController from controller_helpers import TestingCartController
from registrasion.controllers.product import ProductController from registrasion.controllers.product import ProductController
@ -15,10 +16,10 @@ UTC = pytz.timezone('UTC')
class FlagTestCases(RegistrationCartTestCase): class FlagTestCases(RegistrationCartTestCase):
@classmethod @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 ''' Adds a product flag condition: adding PROD_1 to a cart is
predicated on adding PROD_2 beforehand. ''' predicated on adding PROD_2 beforehand. '''
flag = rego.ProductFlag.objects.create( flag = conditions.ProductFlag.objects.create(
description="Product condition", description="Product condition",
condition=condition, condition=condition,
) )
@ -28,10 +29,13 @@ class FlagTestCases(RegistrationCartTestCase):
flag.save() flag.save()
@classmethod @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: ''' Adds a product flag condition that operates on a category:
adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' 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", description="Product condition",
condition=condition, condition=condition,
) )
@ -40,10 +44,10 @@ class FlagTestCases(RegistrationCartTestCase):
flag.enabling_products.add(cls.PROD_3) flag.enabling_products.add(cls.PROD_3)
flag.save() 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 ''' Adds a category flag condition: adding PROD_1 to a cart is
predicated on adding an item from CAT_2 beforehand.''' predicated on adding an item from CAT_2 beforehand.'''
flag = rego.CategoryFlag.objects.create( flag = conditions.CategoryFlag.objects.create(
description="Category condition", description="Category condition",
condition=condition, condition=condition,
enabling_category=cls.CAT_2, enabling_category=cls.CAT_2,
@ -131,8 +135,8 @@ class FlagTestCases(RegistrationCartTestCase):
cart_2.add_to_cart(self.PROD_1, 1) cart_2.add_to_cart(self.PROD_1, 1)
def test_multiple_dif_conditions(self): def test_multiple_dif_conditions(self):
self.add_product_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) self.add_product_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
cart_1 = TestingCartController.for_user(self.USER_1) cart_1 = TestingCartController.for_user(self.USER_1)
# Cannot add PROD_1 until both conditions are met # 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) cart_1.add_to_cart(self.PROD_1, 1)
def test_eit_and_dif_conditions_work_together(self): def test_eit_and_dif_conditions_work_together(self):
self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) self.add_product_flag(condition=conditions.FlagBase.ENABLE_IF_TRUE)
self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) self.add_category_flag(condition=conditions.FlagBase.DISABLE_IF_FALSE)
cart_1 = TestingCartController.for_user(self.USER_1) cart_1 = TestingCartController.for_user(self.USER_1)
# Cannot add PROD_1 until both conditions are met # Cannot add PROD_1 until both conditions are met
@ -200,7 +204,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.PROD_4 in prods) self.assertTrue(self.PROD_4 in prods)
def test_available_products_on_category_works_when_condition_not_met(self): 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( prods = ProductController.available_products(
self.USER_1, self.USER_1,
@ -211,7 +215,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.PROD_2 in prods) self.assertTrue(self.PROD_2 in prods)
def test_available_products_on_category_works_when_condition_is_met(self): 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 = TestingCartController.for_user(self.USER_1)
cart_1.add_to_cart(self.PROD_2, 1) cart_1.add_to_cart(self.PROD_2, 1)
@ -225,7 +229,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.PROD_2 in prods) self.assertTrue(self.PROD_2 in prods)
def test_available_products_on_products_works_when_condition_not_met(self): 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( prods = ProductController.available_products(
self.USER_1, self.USER_1,
@ -236,7 +240,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.PROD_2 in prods) self.assertTrue(self.PROD_2 in prods)
def test_available_products_on_products_works_when_condition_is_met(self): 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 = TestingCartController.for_user(self.USER_1)
cart_1.add_to_cart(self.PROD_2, 1) cart_1.add_to_cart(self.PROD_2, 1)
@ -250,7 +254,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.PROD_2 in prods) self.assertTrue(self.PROD_2 in prods)
def test_category_flag_fails_if_cart_refunded(self): 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 = TestingCartController.for_user(self.USER_1)
cart.add_to_cart(self.PROD_3, 1) cart.add_to_cart(self.PROD_3, 1)
@ -268,7 +272,7 @@ class FlagTestCases(RegistrationCartTestCase):
cart_2.set_quantity(self.PROD_1, 1) cart_2.set_quantity(self.PROD_1, 1)
def test_product_flag_fails_if_cart_refunded(self): 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 = TestingCartController.for_user(self.USER_1)
cart.add_to_cart(self.PROD_2, 1) cart.add_to_cart(self.PROD_2, 1)
@ -286,7 +290,9 @@ class FlagTestCases(RegistrationCartTestCase):
cart_2.set_quantity(self.PROD_1, 1) cart_2.set_quantity(self.PROD_1, 1)
def test_available_categories(self): 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) cart_1 = TestingCartController.for_user(self.USER_1)
@ -307,7 +313,7 @@ class FlagTestCases(RegistrationCartTestCase):
self.assertTrue(self.CAT_2 in cats) self.assertTrue(self.CAT_2 in cats)
def test_validate_cart_when_flags_become_unmet(self): 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 = TestingCartController.for_user(self.USER_1)
cart.add_to_cart(self.PROD_2, 1) cart.add_to_cart(self.PROD_2, 1)
@ -332,7 +338,7 @@ class FlagTestCases(RegistrationCartTestCase):
cart.validate_cart() cart.validate_cart()
# Should keep PROD_2 in the 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]) self.assertFalse([i for i in items if i.product == self.PROD_1])
def test_fix_simple_errors_does_not_remove_limited_items(self): 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 # Should keep PROD_2 in the cart
# and also PROD_1, which is now exhausted for user. # 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]) self.assertTrue([i for i in items if i.product == self.PROD_1])

View file

@ -4,7 +4,9 @@ import pytz
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ValidationError 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 TestingCartController
from controller_helpers import TestingCreditNoteController from controller_helpers import TestingCreditNoteController
from controller_helpers import TestingInvoiceController from controller_helpers import TestingInvoiceController
@ -23,7 +25,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
current_cart.add_to_cart(self.PROD_1, 1) current_cart.add_to_cart(self.PROD_1, 1)
invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) invoice_1 = TestingInvoiceController.for_cart(current_cart.cart)
# That invoice should have a single line item # 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)) self.assertEqual(1, len(line_items))
# That invoice should have a value equal to cost of PROD_1 # That invoice should have a value equal to cost of PROD_1
self.assertEqual(self.PROD_1.price, invoice_1.invoice.value) 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) self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
# The old invoice should automatically be voided # The old invoice should automatically be voided
invoice_1_new = rego.Invoice.objects.get(pk=invoice_1.invoice.id) invoice_1_new = commerce.Invoice.objects.get(pk=invoice_1.invoice.id)
invoice_2_new = rego.Invoice.objects.get(pk=invoice_2.invoice.id) invoice_2_new = commerce.Invoice.objects.get(pk=invoice_2.invoice.id)
self.assertTrue(invoice_1_new.is_void) self.assertTrue(invoice_1_new.is_void)
self.assertFalse(invoice_2_new.is_void) self.assertFalse(invoice_2_new.is_void)
# Invoice should have two line items # 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)) self.assertEqual(2, len(line_items))
# Invoice should have a value equal to cost of PROD_1 and PROD_2 # Invoice should have a value equal to cost of PROD_1 and PROD_2
self.assertEqual( self.assertEqual(
@ -79,16 +85,16 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertNotEqual(current_cart.cart, new_cart.cart) self.assertNotEqual(current_cart.cart, new_cart.cart)
def test_invoice_includes_discounts(self): def test_invoice_includes_discounts(self):
voucher = rego.Voucher.objects.create( voucher = inventory.Voucher.objects.create(
recipient="Voucher recipient", recipient="Voucher recipient",
code="VOUCHER", code="VOUCHER",
limit=1 limit=1
) )
discount = rego.VoucherDiscount.objects.create( discount = conditions.VoucherDiscount.objects.create(
description="VOUCHER RECIPIENT", description="VOUCHER RECIPIENT",
voucher=voucher, voucher=voucher,
) )
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=self.PROD_1, product=self.PROD_1,
percentage=Decimal(50), percentage=Decimal(50),
@ -103,7 +109,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice_1 = TestingInvoiceController.for_cart(current_cart.cart) invoice_1 = TestingInvoiceController.for_cart(current_cart.cart)
# That invoice should have two line items # 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)) self.assertEqual(2, len(line_items))
# That invoice should have a value equal to 50% of the cost of PROD_1 # That invoice should have a value equal to 50% of the cost of PROD_1
self.assertEqual( self.assertEqual(
@ -111,16 +119,16 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice_1.invoice.value) invoice_1.invoice.value)
def test_zero_value_invoice_is_automatically_paid(self): def test_zero_value_invoice_is_automatically_paid(self):
voucher = rego.Voucher.objects.create( voucher = inventory.Voucher.objects.create(
recipient="Voucher recipient", recipient="Voucher recipient",
code="VOUCHER", code="VOUCHER",
limit=1 limit=1
) )
discount = rego.VoucherDiscount.objects.create( discount = conditions.VoucherDiscount.objects.create(
description="VOUCHER RECIPIENT", description="VOUCHER RECIPIENT",
voucher=voucher, voucher=voucher,
) )
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=self.PROD_1, product=self.PROD_1,
percentage=Decimal(100), percentage=Decimal(100),
@ -239,7 +247,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertTrue(invoice.invoice.is_paid) self.assertTrue(invoice.invoice.is_paid)
# There should be a credit note generated out of the invoice. # 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(1, credit_notes.count())
self.assertEqual(to_pay - invoice.invoice.value, credit_notes[0].value) self.assertEqual(to_pay - invoice.invoice.value, credit_notes[0].value)
@ -257,7 +267,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertTrue(invoice.invoice.is_paid) self.assertTrue(invoice.invoice.is_paid)
# There should be no credit notes # 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()) self.assertEqual(0, credit_notes.count())
def test_refund_partially_paid_invoice_generates_correct_credit_note(self): def test_refund_partially_paid_invoice_generates_correct_credit_note(self):
@ -276,7 +288,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertTrue(invoice.invoice.is_void) self.assertTrue(invoice.invoice.is_void)
# There should be a credit note generated out of the invoice. # 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(1, credit_notes.count())
self.assertEqual(to_pay, credit_notes[0].value) self.assertEqual(to_pay, credit_notes[0].value)
@ -297,7 +311,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertTrue(invoice.invoice.is_refunded) self.assertTrue(invoice.invoice.is_refunded)
# There should be a credit note generated out of the invoice. # 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(1, credit_notes.count())
self.assertEqual(to_pay, credit_notes[0].value) self.assertEqual(to_pay, credit_notes[0].value)
@ -314,11 +330,11 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice.refund() invoice.refund()
# There should be one credit note generated out of the invoice. # 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) cn = TestingCreditNoteController(credit_note)
# That credit note should be in the unclaimed pile # 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 # Create a new (identical) cart with invoice
cart = TestingCartController.for_user(self.USER_1) cart = TestingCartController.for_user(self.USER_1)
@ -330,7 +346,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
self.assertTrue(invoice2.invoice.is_paid) self.assertTrue(invoice2.invoice.is_paid)
# That invoice should not show up as unclaimed any more # 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): def test_apply_credit_note_generates_new_credit_note_if_overpaying(self):
cart = TestingCartController.for_user(self.USER_1) cart = TestingCartController.for_user(self.USER_1)
@ -345,10 +361,10 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice.refund() invoice.refund()
# There should be one credit note generated out of the invoice. # 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) 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 # Create a new cart (of half value of inv 1) and get invoice
cart = TestingCartController.for_user(self.USER_1) 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, # We generated a new credit note, and spent the old one,
# unclaimed should still be 1. # 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 # The new credit note should be the residual of the cost of cart 1
# minus the cost of cart 2. # minus the cost of cart 2.
@ -385,7 +403,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice.refund() invoice.refund()
# There should be one credit note generated out of the invoice. # 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) cn = TestingCreditNoteController(credit_note)
# Create a new cart with invoice, pay it # Create a new cart with invoice, pay it
@ -426,15 +444,15 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice.refund() 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 = TestingCreditNoteController(credit_note)
cn.refund() cn.refund()
# Refunding a credit note should mark it as claimed # 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 # Create a new cart with invoice
cart = TestingCartController.for_user(self.USER_1) cart = TestingCartController.for_user(self.USER_1)
@ -458,9 +476,9 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice.refund() 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 = TestingCreditNoteController(credit_note)
@ -471,7 +489,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
invoice_2 = TestingInvoiceController.for_cart(self.reget(cart.cart)) invoice_2 = TestingInvoiceController.for_cart(self.reget(cart.cart))
cn.apply_to_invoice(invoice_2.invoice) 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. # Cannot refund this credit note as it is already applied.
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):

View file

@ -5,7 +5,8 @@ from decimal import Decimal
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import IntegrityError 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 TestingCartController
from controller_helpers import TestingInvoiceController from controller_helpers import TestingInvoiceController
@ -32,14 +33,14 @@ class VoucherTestCases(RegistrationCartTestCase):
# After the reservation duration # After the reservation duration
# user 2 should be able to apply voucher # user 2 should be able to apply voucher
self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2) self.add_timedelta(inventory.Voucher.RESERVATION_DURATION * 2)
cart_2.apply_voucher(voucher.code) cart_2.apply_voucher(voucher.code)
cart_2.next_cart() cart_2.next_cart()
# After the reservation duration, even though the voucher has applied, # After the reservation duration, even though the voucher has applied,
# it exceeds the number of vouchers available. # 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): with self.assertRaises(ValidationError):
cart_1.validate_cart() cart_1.validate_cart()
@ -58,10 +59,10 @@ class VoucherTestCases(RegistrationCartTestCase):
def test_voucher_enables_item(self): def test_voucher_enables_item(self):
voucher = self.new_voucher() voucher = self.new_voucher()
flag = rego.VoucherFlag.objects.create( flag = conditions.VoucherFlag.objects.create(
description="Voucher condition", description="Voucher condition",
voucher=voucher, voucher=voucher,
condition=rego.FlagBase.ENABLE_IF_TRUE, condition=conditions.FlagBase.ENABLE_IF_TRUE,
) )
flag.save() flag.save()
flag.products.add(self.PROD_1) flag.products.add(self.PROD_1)
@ -79,12 +80,12 @@ class VoucherTestCases(RegistrationCartTestCase):
def test_voucher_enables_discount(self): def test_voucher_enables_discount(self):
voucher = self.new_voucher() voucher = self.new_voucher()
discount = rego.VoucherDiscount.objects.create( discount = conditions.VoucherDiscount.objects.create(
description="VOUCHER RECIPIENT", description="VOUCHER RECIPIENT",
voucher=voucher, voucher=voucher,
) )
discount.save() discount.save()
rego.DiscountForProduct.objects.create( conditions.DiscountForProduct.objects.create(
discount=discount, discount=discount,
product=self.PROD_1, product=self.PROD_1,
percentage=Decimal(100), percentage=Decimal(100),

View file

@ -1,7 +1,9 @@
import sys import sys
from registrasion import forms 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 import discount
from registrasion.controllers.cart import CartController from registrasion.controllers.cart import CartController
from registrasion.controllers.credit_note import CreditNoteController from registrasion.controllers.credit_note import CreditNoteController
@ -60,7 +62,7 @@ def guided_registration(request, page_id=0):
sections = [] sections = []
attendee = rego.Attendee.get_instance(request.user) attendee = people.Attendee.get_instance(request.user)
if attendee.completed_registration: if attendee.completed_registration:
return render( return render(
@ -111,7 +113,7 @@ def guided_registration(request, page_id=0):
starting = attendee.guided_categories_complete.count() == 0 starting = attendee.guided_categories_complete.count() == 0
# Get the next category # Get the next category
cats = rego.Category.objects cats = inventory.Category.objects
if SESSION_KEY in request.session: if SESSION_KEY in request.session:
_cats = request.session[SESSION_KEY] _cats = request.session[SESSION_KEY]
cats = cats.filter(id__in=_cats) cats = cats.filter(id__in=_cats)
@ -134,7 +136,7 @@ def guided_registration(request, page_id=0):
current_step = 3 current_step = 3
title = "Additional items" title = "Additional items"
all_products = rego.Product.objects.filter( all_products = inventory.Product.objects.filter(
category__in=cats, category__in=cats,
).select_related("category") ).select_related("category")
@ -217,11 +219,13 @@ def edit_profile(request):
def handle_profile(request, prefix): def handle_profile(request, prefix):
''' Returns a profile form instance, and a boolean which is true if the ''' Returns a profile form instance, and a boolean which is true if the
form was handled. ''' form was handled. '''
attendee = rego.Attendee.get_instance(request.user) attendee = people.Attendee.get_instance(request.user)
try: try:
profile = attendee.attendeeprofilebase profile = attendee.attendeeprofilebase
profile = rego.AttendeeProfileBase.objects.get_subclass(pk=profile.id) profile = people.AttendeeProfileBase.objects.get_subclass(
pk=profile.id,
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
profile = None profile = None
@ -270,7 +274,7 @@ def product_category(request, category_id):
voucher_form, voucher_handled = v voucher_form, voucher_handled = v
category_id = int(category_id) # Routing is [0-9]+ 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( products = ProductController.available_products(
request.user, request.user,
@ -316,7 +320,7 @@ def handle_products(request, category, products, prefix):
ProductsForm = forms.ProductsForm(category, products) ProductsForm = forms.ProductsForm(category, products)
# Create initial data for each of products in category # Create initial data for each of products in category
items = rego.ProductItem.objects.filter( items = commerce.ProductItem.objects.filter(
product__in=products, product__in=products,
cart=current_cart.cart, cart=current_cart.cart,
).select_related("product") ).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 # If category is required, the user must have at least one
# in an active+valid cart # in an active+valid cart
if category.required: if category.required:
carts = rego.Cart.objects.filter(user=request.user) carts = commerce.Cart.objects.filter(user=request.user)
items = rego.ProductItem.objects.filter( items = commerce.ProductItem.objects.filter(
product__category=category, product__category=category,
cart=carts, cart=carts,
) )
@ -366,7 +370,7 @@ def set_quantities_from_products_form(products_form, current_cart):
quantities = list(products_form.product_quantities()) quantities = list(products_form.product_quantities())
pks = [i[0] for i in quantities] pks = [i[0] for i in quantities]
products = rego.Product.objects.filter( products = inventory.Product.objects.filter(
id__in=pks, id__in=pks,
).select_related("category") ).select_related("category")
@ -384,7 +388,7 @@ def set_quantities_from_products_form(products_form, current_cart):
product, message = ve_field.message product, message = ve_field.message
if product in field_names: if product in field_names:
field = field_names[product] field = field_names[product]
elif isinstance(product, rego.Product): elif isinstance(product, inventory.Product):
continue continue
else: else:
field = None field = None
@ -402,7 +406,7 @@ def handle_voucher(request, prefix):
voucher_form.cleaned_data["voucher"].strip()): voucher_form.cleaned_data["voucher"].strip()):
voucher = voucher_form.cleaned_data["voucher"] 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: if len(current_cart.cart.vouchers.filter(code=voucher)) > 0:
# This voucher has already been applied to this cart. # 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 ''' Redirects to the first unpaid invoice for the attendee that matches
the given access code, if any. ''' the given access code, if any. '''
invoices = rego.Invoice.objects.filter( invoices = commerce.Invoice.objects.filter(
user__attendee__access_code=access_code, user__attendee__access_code=access_code,
status=rego.Invoice.STATUS_UNPAID, status=commerce.Invoice.STATUS_UNPAID,
).order_by("issue_time") ).order_by("issue_time")
if not invoices: if not invoices:
@ -478,7 +482,7 @@ def invoice(request, invoice_id, access_code=None):
''' '''
invoice_id = int(invoice_id) 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) current_invoice = InvoiceController(inv)
@ -505,7 +509,7 @@ def manual_payment(request, invoice_id):
raise Http404() raise Http404()
invoice_id = int(invoice_id) 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) current_invoice = InvoiceController(inv)
form = forms.ManualPaymentForm( form = forms.ManualPaymentForm(
@ -536,7 +540,7 @@ def refund(request, invoice_id):
raise Http404() raise Http404()
invoice_id = int(invoice_id) 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) current_invoice = InvoiceController(inv)
try: try:
@ -557,7 +561,7 @@ def credit_note(request, note_id, access_code=None):
raise Http404() raise Http404()
note_id = int(note_id) 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) 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(): if request.POST and apply_form.is_valid():
inv_id = apply_form.cleaned_data["invoice"] 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) current_note.apply_to_invoice(invoice)
messages.success(request, messages.success(
"Applied credit note %d to invoice." % note_id request,
"Applied credit note %d to invoice." % note_id,
) )
return redirect("invoice", invoice.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.entered_by = request.user
refund_form.instance.parent = note refund_form.instance.parent = note
refund_form.save() refund_form.save()
messages.success(request, messages.success(
request,
"Applied manual refund to credit note." "Applied manual refund to credit note."
) )
return redirect("invoice", invoice.id) return redirect("invoice", invoice.id)