Changeset - cbecbf9a4102
[Not reviewed]
0 3 0
Christopher Neugebauer - 8 years ago 2016-04-25 08:50:09
chrisjrn@gmail.com
Tidies up some docs
3 files changed with 6 insertions and 2 deletions:
0 comments (0 inline, 0 general)
docs/integration.rst
Show inline comments
 
Integrating Registrasion
 
========================
 

	
 
Registrasion is a Django app. It does not provide any templates -- you'll need to develop these yourself. You can use the ``registrasion-demo`` project as a starting point.
 
Registrasion is a Django app. It does not provide any templates -- you'll need to develop these yourself. You can use the `registrasion-demo <https://github.com/chrisjrn/registrasion-demo>`_ project as a starting point.
 

	
 
To use Registrasion for your own conference, you'll need to do a small amount of development work, usually in your own Django App.
 

	
 
The first is to define a model and form for your attendee profile, and the second is to implement a payment app.
 

	
 

	
 
Attendee profile
 
----------------
 

	
 
.. automodule:: registrasion.models.people
 

	
 
Attendee profiles are where you ask for information such as what your attendee wants on their badge, and what the attendee's dietary and accessibility requirements are.
 

	
 
Because every conference is different, Registrasion lets you define your own attendee profile model, and your own form for requesting this information. The only requirement is that you derive your model from ``AttendeeProfileBase``.
 

	
 
.. autoclass :: AttendeeProfileBase
 
    :members: name_field, invoice_recipient
 

	
 
Once you've subclassed ``AttendeeProfileBase``, you'll need to implement a form that lets attendees fill out their profile.
 

	
 
You specify how to find that form in your Django ``settings.py`` file::
 

	
 
    ATTENDEE_PROFILE_FORM = "democon.forms.AttendeeProfileForm"
 

	
 
The only contract is that this form creates an instance of ``AttendeeProfileBase`` when saved, and that it can take an instance of your subclass on creation (so that your attendees can edit their profile).
 

	
 

	
 
Payments
 
--------
 

	
 
Registrasion does not implement its own credit card processing. You'll need to do that yourself. Registrasion *does* provide a mechanism for recording cheques and direct deposits, if you do end up taking registrations that way.
 

	
 
See :ref:`payments_and_refunds` for a guide on how to correctly implement payments.
docs/inventory.rst
Show inline comments
 

	
 
Inventory Management
 
====================
 

	
 
Registrasion uses an inventory model to keep track of tickets, and the other various products that attendees of your conference might want to have, such as t-shirts and dinner tickets.
 

	
 
All of the classes described herein are available through the Django Admin interface.
 

	
 
Overview
 
--------
 

	
 
The inventory model is split up into Categories and Products. Categories are used to group Products.
 

	
 
Registrasion uses conditionals to build up complex tickets, or enable/disable specific items to specific users:
 

	
 
Often, you will want to offer free items, such as t-shirts or dinner tickets to your attendees. Registrasion has a Discounts facility that lets you automatically offer free items to your attendees as part of their tickets. You can also automatically offer promotional discounts, such as Early Bird discounts.
 

	
 
Sometimes, you may want to restrict parts of the conference to specific attendees, for example, you might have a Speakers Dinner to only speakers. Or you might want to restrict certain Products to attendees who have purchased other items, for example, you might want to sell Comfy Chairs to people who've bought VIP tickets. You can control showing and hiding specific products using Flags.
 

	
 

	
 
.. automodule:: registrasion.models.inventory
 

	
 
Categories
 
----------
 

	
 
Categories are logical groups of Products. Generally, you should keep like products in the same category, and use as many categories as you need.
 

	
 
You will need at least one Category to be able to sell tickets to your attendees.
 

	
 
Each category has the following attributes:
 

	
 
.. autoclass :: Category
 

	
 

	
 
Products
 
--------
 

	
 
Products represent the different items that comprise a user's conference ticket.
 

	
 
Each product has the following attributes:
 

	
 
.. autoclass :: Product
 

	
 

	
 
Vouchers
 
--------
 

	
 
Vouchers are used to enable Discounts or Flags for people who enter a voucher
 
code.
 

	
 
.. autoclass :: Voucher
 

	
 
If an attendee enters a voucher code, they have at least an hour to finalise
 
their registration before the voucher becomes unreserved. Only as many people
 
as allowed by ``limit`` are allowed to have a voucher reserved.
 

	
 

	
 
.. automodule:: registrasion.models.conditions
 

	
 
Discounts
 
---------
 

	
 
Discounts serve multiple purposes: they can be used to build up complex tickets by automatically waiving the costs for sub-products; they can be used to offer freebie tickets to specific people, or people who hold voucher codes; or they can be used to enable short-term promotional discounts.
 

	
 
Registrasion has several types of discounts, which enable themselves under specific conditions. We'll explain how these work later on, but first:
 

	
 
Common features
 
~~~~~~~~~~~~~~~
 
Each discount type has the following common attributes:
 

	
 
.. autoclass :: DiscountBase
 

	
 
You can apply a discount to individual products, or to whole categories, or both. All of the products and categories affected by the discount are displayed on the discount's admin page.
 

	
 
If you choose to specify individual products, you have these options:
 

	
 
.. autoclass :: DiscountForProduct
 

	
 
If you choose to specify whole categories, you have these options:
 

	
 
.. autoclass :: DiscountForCategory
 

	
 
Note that you cannot have a discount apply to both a category, and a product within that category.
 

	
 
Product Inclusions
 
~~~~~~~~~~~~~~~~~~
 
Product inclusion discounts allow you to enable a discount when an attendee has selected a specific enabling Product.
 

	
 
For example, if you want to give everyone with a ticket a free t-shirt, you can use a product inclusion to offer a 100% discount on the t-shirt category, if the attendee has selected one of your ticket Products.
 

	
 
Once a discount has been enabled in one Invoice, it is available until the quantities are exhausted for that attendee.
 

	
 
.. autoclass :: IncludedProductDiscount
 

	
 
Time/stock limit discounts
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 
These discounts allow you to offer a limited promotion that is automatically offered to all attendees. You can specify a time range for when the discount should be enabled, you can also specify a stock limit.
 

	
 
.. autoclass :: TimeOrStockLimitDiscount
 

	
 
Voucher discounts
 
~~~~~~~~~~~~~~~~~
 
Vouchers can be used to enable discounts.
 

	
 
.. autoclass :: VoucherDiscount
 

	
 
How discounts get applied
registrasion/controllers/cart.py
Show inline comments
...
 
@@ -185,193 +185,192 @@ class CartController(object):
 
                ))
 

	
 
        # Test the flag conditions
 
        errs = ConditionController.test_flags(
 
            self.cart.user,
 
            product_quantities=product_quantities,
 
        )
 

	
 
        if errs:
 
            for error in errs:
 
                errors.append(error)
 

	
 
        if errors:
 
            raise CartValidationError(errors)
 

	
 
    @_modifies_cart
 
    def apply_voucher(self, voucher_code):
 
        ''' Applies the voucher with the given code to this cart. '''
 

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

	
 
        # Re-applying vouchers should be idempotent
 
        if voucher in self.cart.vouchers.all():
 
            return
 

	
 
        self._test_voucher(voucher)
 

	
 
        # If successful...
 
        self.cart.vouchers.add(voucher)
 
        self.end_batch()
 

	
 
    def _test_voucher(self, voucher):
 
        ''' Tests whether this voucher is allowed to be applied to this cart.
 
        Raises ValidationError if not. '''
 

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

	
 
        # It's invalid for a user to enter a voucher that's exhausted
 
        carts_with_voucher = active_carts.filter(vouchers=voucher)
 
        carts_with_voucher = carts_with_voucher.exclude(pk=self.cart.id)
 
        if carts_with_voucher.count() >= voucher.limit:
 
            raise ValidationError(
 
                "Voucher %s is no longer available" % voucher.code)
 

	
 
        # It's not valid for users to re-enter a voucher they already have
 
        user_carts_with_voucher = carts_with_voucher.filter(
 
            user=self.cart.user,
 
        )
 

	
 
        if user_carts_with_voucher.count() > 0:
 
            raise ValidationError("You have already entered this voucher.")
 

	
 
    def _test_vouchers(self, vouchers):
 
        ''' Tests each of the vouchers against self._test_voucher() and raises
 
        the collective ValidationError.
 
        Future work will refactor _test_voucher in terms of this, and save some
 
        queries. '''
 
        errors = []
 
        for voucher in vouchers:
 
            try:
 
                self._test_voucher(voucher)
 
            except ValidationError as ve:
 
                errors.append(ve)
 

	
 
        if errors:
 
            raise(ValidationError(ve))
 

	
 
    def _test_required_categories(self):
 
        ''' Makes sure that the owner of this cart has satisfied all of the
 
        required category constraints in the inventory (be it in this cart
 
        or others). '''
 

	
 
        required = set(inventory.Category.objects.filter(required=True))
 

	
 
        items = commerce.ProductItem.objects.filter(
 
            product__category__required=True,
 
            cart__user=self.cart.user,
 
        ).exclude(
 
            cart__status=commerce.Cart.STATUS_RELEASED,
 
        )
 

	
 
        for item in items:
 
            required.remove(item.product.category)
 

	
 
        errors = []
 
        for category in required:
 
            msg = "You must have at least one item from: %s" % category
 
            errors.append((None, msg))
 

	
 
        if errors:
 
            raise ValidationError(errors)
 

	
 
    def _append_errors(self, errors, ve):
 
        for error in ve.error_list:
 
            print error.message
 
            errors.append(error.message[1])
 

	
 
    def validate_cart(self):
 
        ''' Determines whether the status of the current cart is valid;
 
        this is normally called before generating or paying an invoice '''
 

	
 
        cart = self.cart
 
        user = self.cart.user
 
        errors = []
 

	
 
        try:
 
            self._test_vouchers(self.cart.vouchers.all())
 
        except ValidationError as ve:
 
            errors.append(ve)
 

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

	
 
        product_quantities = list((i.product, i.quantity) for i in items)
 
        try:
 
            self._test_limits(product_quantities)
 
        except ValidationError as ve:
 
            self._append_errors(errors, ve)
 

	
 
        try:
 
            self._test_required_categories()
 
        except ValidationError as ve:
 
            self._append_errors(errors, ve)
 

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

	
 
        for discount_item in discount_items:
 
            discount = discount_item.discount
 
            if discount in seen_discounts:
 
                continue
 
            seen_discounts.add(discount)
 
            real_discount = conditions.DiscountBase.objects.get_subclass(
 
                pk=discount.pk)
 
            cond = ConditionController.for_condition(real_discount)
 

	
 
            if not cond.is_met(user):
 
                errors.append(
 
                    ValidationError("Discounts are no longer available")
 
                )
 

	
 
        if errors:
 
            raise ValidationError(errors)
 

	
 
    @_modifies_cart
 
    @transaction.atomic
 
    def fix_simple_errors(self):
 
        ''' This attempts to fix the easy errors raised by ValidationError.
 
        This includes removing items from the cart that are no longer
 
        available, recalculating all of the discounts, and removing voucher
 
        codes that are no longer available. '''
 

	
 
        # Fix vouchers first (this affects available discounts)
 
        to_remove = []
 
        for voucher in self.cart.vouchers.all():
 
            try:
 
                self._test_voucher(voucher)
 
            except ValidationError:
 
                to_remove.append(voucher)
 

	
 
        for voucher in to_remove:
 
            self.cart.vouchers.remove(voucher)
 

	
 
        # Fix products and discounts
 
        items = commerce.ProductItem.objects.filter(cart=self.cart)
 
        items = items.select_related("product")
 
        products = set(i.product for i in items)
 
        available = set(ProductController.available_products(
 
            self.cart.user,
 
            products=products,
 
        ))
 

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

	
 
        self.set_quantities(zeros)
 

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

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

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

	
 
        products = [i.product for i in product_items]
0 comments (0 inline, 0 general)