diff --git a/doc/development/api/general.rst b/doc/development/api/general.rst index 6e95aa4ad..5b1d13609 100644 --- a/doc/development/api/general.rst +++ b/doc/development/api/general.rst @@ -34,7 +34,7 @@ Frontend -------- .. automodule:: pretix.presale.signals - :members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header + :members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header .. automodule:: pretix.presale.signals diff --git a/src/pretix/base/views/mixins.py b/src/pretix/base/views/mixins.py index f43e07757..edc12fb29 100644 --- a/src/pretix/base/views/mixins.py +++ b/src/pretix/base/views/mixins.py @@ -14,6 +14,7 @@ from pretix.base.models import ( CartPosition, InvoiceAddress, OrderPosition, Question, QuestionAnswer, QuestionOption, ) +from pretix.presale.signals import contact_form_fields_overrides class BaseQuestionsViewMixin: @@ -34,6 +35,9 @@ class BaseQuestionsViewMixin: def _positions_for_questions(self): raise NotImplementedError() + def get_question_override_sets(self, position): + return [] + @cached_property def forms(self): """ @@ -62,6 +66,23 @@ class BaseQuestionsViewMixin: self.request.event.settings.attendee_addresses_asked )) ) + + override_sets = self.get_question_override_sets(cr) + for overrides in override_sets: + for question_name, question_field in form.fields.items(): + if hasattr(question_field, 'question'): + if question_field.question.identifier in overrides: + if 'initial' in overrides[question_field.question.identifier]: + question_field.initial = overrides[question_field.question.identifier]['initial'] + if 'disabled' in overrides[question_field.question.identifier]: + question_field.disabled = overrides[question_field.question.identifier]['disabled'] + else: + if question_name in overrides: + if 'initial' in overrides[question_name]: + question_field.initial = overrides[question_name]['initial'] + if 'disabled' in overrides[question_name]: + question_field.disabled = overrides[question_name]['disabled'] + if len(form.fields) > 0: formlist.append(form) return formlist @@ -213,24 +234,47 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin): self.order.total != Decimal('0.00') or not self.request.event.settings.invoice_address_not_asked_free ) + @cached_property + def _contact_override_sets(self): + override_sets = [ + resp for recv, resp in contact_form_fields_overrides.send( + self.request.event, + request=self.request, + order=self.order, + ) + ] + for override in override_sets: + for k in override: + # We don't want initial values to be modified, they should come from the order directly + override[k].pop('initial', None) + return override_sets + @cached_property def invoice_form(self): if not self.address_asked and self.request.event.settings.invoice_name_required: - return self.invoice_name_form_class( + f = self.invoice_name_form_class( data=self.request.POST if self.request.method == "POST" else None, event=self.request.event, instance=self.invoice_address, validate_vat_id=False, all_optional=self.all_optional ) - if self.address_asked: - return self.invoice_form_class( + elif self.address_asked: + f = self.invoice_form_class( data=self.request.POST if self.request.method == "POST" else None, event=self.request.event, instance=self.invoice_address, validate_vat_id=False, all_optional=self.all_optional, ) else: - return forms.Form(data=self.request.POST if self.request.method == "POST" else None) + f = forms.Form(data=self.request.POST if self.request.method == "POST" else None) + + override_sets = self._contact_override_sets + for overrides in override_sets: + for fname, val in overrides.items(): + if 'disabled' in val and fname in f.fields: + f.fields[fname].disabled = val['disabled'] + + return f def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index e6108e4d9..8336e8d65 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -32,7 +32,9 @@ from pretix.presale.forms.checkout import ( ) from pretix.presale.signals import ( checkout_all_optional, checkout_confirm_messages, checkout_flow_steps, - contact_form_fields, order_meta_from_request, question_form_fields, + contact_form_fields, contact_form_fields_overrides, + order_meta_from_request, question_form_fields, + question_form_fields_overrides, ) from pretix.presale.views import ( CartMixin, get_cart, get_cart_is_free, get_cart_total, @@ -423,6 +425,16 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): return True return False + @cached_property + def _contact_override_sets(self): + return [ + resp for recv, resp in contact_form_fields_overrides.send( + self.request.event, + request=self.request, + order=None, + ) + ] + @cached_property def contact_form(self): wd = self.cart_session.get('widget_data', {}) @@ -433,14 +445,36 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): ) } initial.update(self.cart_session.get('contact_form_data', {})) + + override_sets = self._contact_override_sets + for overrides in override_sets: + initial.update({ + k: v['initial'] for k, v in overrides.items() if 'initial' in v + }) + f = ContactForm(data=self.request.POST if self.request.method == "POST" else None, event=self.request.event, request=self.request, initial=initial, all_optional=self.all_optional) if wd.get('email', '') and wd.get('fix', '') == "true": f.fields['email'].disabled = True + + for overrides in override_sets: + for fname, val in overrides.items(): + if 'disabled' in val and fname in f.fields: + f.fields[fname].disabled = val['disabled'] + return f + def get_question_override_sets(self, cart_position): + return [ + resp for recv, resp in question_form_fields_overrides.send( + self.request.event, + position=cart_position, + request=self.request + ) + ] + @cached_property def eu_reverse_charge_relevant(self): return any([p.item.tax_rule and (p.item.tax_rule.eu_reverse_charge or p.item.tax_rule.custom_rules) @@ -466,6 +500,13 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): else: wd_initial = {} initial = dict(wd_initial) + + override_sets = self._contact_override_sets + for overrides in override_sets: + initial.update({ + k: v['initial'] for k, v in overrides.items() if 'initial' in v + }) + if not self.address_asked and self.request.event.settings.invoice_name_required: f = InvoiceNameForm(data=self.request.POST if self.request.method == "POST" else None, event=self.request.event, @@ -483,6 +524,12 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): for name, field in f.fields.items(): if wd_initial.get(name) and wd.get('fix', '') == 'true': field.disabled = True + + for overrides in override_sets: + for fname, val in overrides.items(): + if 'disabled' in val and fname in f.fields: + f.fields[fname].disabled = val['disabled'] + return f @cached_property diff --git a/src/pretix/presale/signals.py b/src/pretix/presale/signals.py index eba269aea..4c2e0ae52 100644 --- a/src/pretix/presale/signals.py +++ b/src/pretix/presale/signals.py @@ -180,6 +180,21 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve argument will contain the request object. """ +contact_form_fields_overrides = EventPluginSignal( + providing_args=["request", "order"] +) +""" +This signal allows you to override fields of the contact form that is presented during checkout +and by default only asks for the email address. It is also being used for the invoice address +form. You are supposed to return a dictionary of dictionaries with globally unique keys. The +value-dictionary should contain one or more of the following keys: ``initial``, ``disabled``. The +key of the dictionary should be the name of the form field. + +As with all plugin signals, the ``sender`` keyword argument will contain the event. A ``request`` +argument will contain the request object. The ``order`` argument is ``None`` during the checkout +process and contains an order if the customer is trying to change an existing order. +""" + question_form_fields = EventPluginSignal( providing_args=["position"] ) @@ -196,6 +211,22 @@ later. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ +question_form_fields_overrides = EventPluginSignal( + providing_args=["position", "request"] +) +""" +This signal allows you to override fields of the questions form that is presented during checkout +and by default only asks for the questions configured in the backend. You are supposed to return a +dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or +more of the following keys: ``initial``, ``disabled``. The key of the dictionary should not be the +question's form field name (``question_n``) but rather the questions ``identifier``. + +The ``position`` keyword argument will contain a ``CartPosition`` or ``OrderPosition`` object. + +As with all plugin signals, the ``sender`` keyword argument will contain the event. A ``request`` +argument will contain the request object. +""" + order_info = EventPluginSignal( providing_args=["order", "request"] ) diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 3c7a6b751..313079ad3 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -50,6 +50,7 @@ from pretix.helpers.safedownload import check_token from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse from pretix.presale.forms.checkout import InvoiceAddressForm, QuestionsForm from pretix.presale.forms.order import OrderPositionChangeForm +from pretix.presale.signals import question_form_fields_overrides from pretix.presale.views import ( CartMixin, EventViewMixin, iframe_entry_view_wrapper, ) @@ -673,6 +674,20 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem return [] return super().positions + def get_question_override_sets(self, order_position): + override_sets = [ + resp for recv, resp in question_form_fields_overrides.send( + self.request.event, + position=order_position, + request=self.request + ) + ] + for override in override_sets: + for k in override: + # We don't want initial values to be modified, they should come from the order directly + override[k].pop('initial', None) + return override_sets + def post(self, request, *args, **kwargs): failed = not self.save() or not self.invoice_form.is_valid() if failed: