Add signal validate_cart_addons

This commit is contained in:
Raphael Michel
2019-07-12 13:06:29 +02:00
parent f8bb139651
commit 35037c79cc
6 changed files with 68 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:members: validate_cart, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download
Frontend
--------

View File

@@ -85,8 +85,14 @@ Use case: Conference with workshops
When running a conference, you might also organize a number of workshops with smaller capacity. To be able to plan, it would be great to know which workshops an attendee plans to attend.
Option A: Questions
"""""""""""""""""""
Your first and simplest option is to just create a multiple-choice question. This has the upside of making it easy for users to change their mind later on, but will not allow you to restrict the number of attendees signing up for a given workshop or even charge extra for a given workshop.
Option B: Add-on products with fixed time slots
"""""""""""""""""""""""""""""""""""""""""""""""
The usually better option is to go with add-on products. Let's take for example the following conference schedule, in which the lecture can be attended by anyone, but the workshops only have space for 20 persons each:
==================== =================================== ===================================
@@ -117,6 +123,28 @@ Assuming you already created one or more products for your general conference ad
* One add-on configuration on your base product that allows users to choose between 0 and 2 products from the category "Workshops"
Option C: Add-on products with variable time slots
""""""""""""""""""""""""""""""""""""""""""""""""""
The above option only works if your conference uses fixed timeslots and every workshop uses exactly one timeslot. If
your schedule looks like this, it's not going to work great:
+------------+------------+-----------+
| Time | Room A | Room B |
+============+============+===========+
| body row 1 | column 2 | column 3 |
+------------+------------+-----------+
| body row 2 | Cells may span columns.|
+------------+------------+-----------+
| body row 3 | Cells may | - Cells |
+------------+ span rows. | - contain |
| body row 4 | | - blocks. |
+------------+------------+-----------+
**This option is currently only available on pretix Hosted. If you are interested in using it with pretix Enterprise,
please contact sales@pretix.eu.**
Use case: Discounted packages
-----------------------------

View File

@@ -26,6 +26,7 @@ from pretix.base.services.locking import LockTimeoutException, NoLockManager
from pretix.base.services.pricing import get_price
from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import validate_cart_addons
from pretix.base.templatetags.rich_text import rich_text
from pretix.celery_app import app
from pretix.presale.signals import (
@@ -643,6 +644,15 @@ class CartManager:
'cat': str(iao.addon_category.name),
}
)
validate_cart_addons.send(
sender=self.event,
addons={
(self._items_cache[s[0]], self._variations_cache[s[1]] if s[1] else None)
for s in selected
},
base_position=cp,
iao=iao
)
# Detect removed add-ons and create RemoveOperations
for cp, al in current_addons.items():

View File

@@ -265,6 +265,21 @@ appropriate exception message.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
validate_cart_addons = EventPluginSignal(
providing_args=["addons", "base_position", "iao"]
)
"""
This signal is sent when a user tries to select a combination of addons. In contrast to
``validate_cart``, this is executed before the cart is actually modified. You are passed
an argument ``addons`` containing a set of ``(item, variation or None)`` tuples as well
as the ``ItemAddOn`` object as the argument ``iao`` and the base cart position as
``base_position``.
The response of receivers will be ignored, but you can raise a CartError with an
appropriate exception message.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_placed = EventPluginSignal(
providing_args=["order"]
)

View File

@@ -239,6 +239,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
'form': AddOnsForm(
event=self.request.event,
prefix='{}_{}'.format(cartpos.pk, iao.addon_category.pk),
base_position=cartpos,
iao=iao,
price_included=iao.price_included,
initial=current_addon_products,

View File

@@ -14,7 +14,8 @@ from pretix.base.forms.questions import (
)
from pretix.base.models import ItemVariation
from pretix.base.models.tax import TAXED_ZERO
from pretix.base.services.cart import error_messages
from pretix.base.services.cart import CartError, error_messages
from pretix.base.signals import validate_cart_addons
from pretix.base.templatetags.money import money_filter
from pretix.base.templatetags.rich_text import rich_text
from pretix.base.validators import EmailBlacklistValidator
@@ -205,13 +206,14 @@ class AddOnsForm(forms.Form):
"""
self.iao = kwargs.pop('iao')
category = self.iao.addon_category
event = kwargs.pop('event')
self.event = kwargs.pop('event')
subevent = kwargs.pop('subevent')
current_addons = kwargs.pop('initial')
quota_cache = kwargs.pop('quota_cache')
item_cache = kwargs.pop('item_cache')
self.price_included = kwargs.pop('price_included')
self.sales_channel = kwargs.pop('sales_channel')
self.base_position = kwargs.pop('base_position')
super().__init__(*args, **kwargs)
@@ -231,12 +233,12 @@ class AddOnsForm(forms.Form):
).select_related('tax_rule').prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.filter(subevent=subevent)),
queryset=self.event.quotas.filter(subevent=subevent)),
Prefetch('variations', to_attr='available_variations',
queryset=ItemVariation.objects.filter(active=True, quotas__isnull=False).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.filter(subevent=subevent))
queryset=self.event.quotas.filter(subevent=subevent))
).distinct()),
'event'
).annotate(
@@ -260,7 +262,7 @@ class AddOnsForm(forms.Form):
self.vars_cache[v.pk] = v
choices.append(
(v.pk,
self._label(event, v, cached_availability,
self._label(self.event, v, cached_availability,
override_price=var_price_override.get(v.pk)),
v.description)
)
@@ -294,7 +296,7 @@ class AddOnsForm(forms.Form):
continue
cached_availability = i.check_quotas(subevent=subevent, _cache=quota_cache)
field = forms.BooleanField(
label=self._label(event, i, cached_availability,
label=self._label(self.event, i, cached_availability,
override_price=item_price_override.get(i.pk)),
required=False,
initial=i.pk in current_addons,
@@ -333,3 +335,8 @@ class AddOnsForm(forms.Form):
'cat': str(self.iao.addon_category.name),
}
)
try:
validate_cart_addons.send(sender=self.event, addons=selected, base_position=self.base_position,
iao=self.iao)
except CartError as e:
raise ValidationError(str(e))