Allow to pass user data to the widget (#1095)

- [x] Logic
- [x] Tests
- [x] Docs
- [x] find a way to integrate with tracking
This commit is contained in:
Raphael Michel
2018-11-20 17:55:37 +01:00
committed by GitHub
parent b49b2035bd
commit beb0ded6dc
10 changed files with 335 additions and 29 deletions

View File

@@ -1,5 +1,6 @@
addon addon
addons addons
Analytics
anonymize anonymize
api api
auditability auditability

View File

@@ -149,8 +149,94 @@ Just as the widget, the button supports the optional attributes ``voucher`` and
You can style the button using the ``pretix-button`` CSS class. You can style the button using the ``pretix-button`` CSS class.
.. versionchanged:: 1.13 Dynamically loading the widget
------------------------------
The pretix Button has been added in version 1.13. If you need to control the way or timing the widget loads, for example because you want to modify user data (see
below) dynamically via JavaScript, you can register a listener that we will call before creating the widget::
<script type="text/javascript">
window.pretixWidgetCallback = function () {
// Will be run before we create the widget.
}
</script>
If you want, you can suppress us loading the widget and/or modify the user data passed to the widget::
<script type="text/javascript">
window.pretixWidgetCallback = function () {
window.PretixWidget.build_widgets = false;
window.PretixWidget.widget_data["email"] = "test@example.org";
}
</script>
If you then later want to trigger loading the widgets, just call ``window.PretixWidget.buildWidgets()``.
Passing user data to the widget
-------------------------------
If you display the widget in a restricted area of your website and you want to pre-fill fields in the checkout process
with known user data to save your users some typing and increase conversions, you can pass additional data attributes
with that information::
<pretix-widget event="https://pretix.eu/demo/democon/"
data-attendee-name-given-name="John"
data-attendee-name-family-name="Doe"
data-email="test@example.org"
data-question-L9G8NG9M="Foobar">
</pretix-widget>
This works for the pretix Button as well. Currently, the following attributes are understood by pretix itself:
* ``data-email`` will pre-fill the order email field as well as the attendee email field (if enabled).
* ``data-question-IDENTIFIER`` will pre-fill the answer for the question with the given identifier. You can view and set
identifiers in the *Questions* section of the backend.
* Depending on the person name scheme configured in your event settings, you can pass one or more of
``data-attendee-name-full-name``, ``data-attendee-name-given-name``, ``data-attendee-name-family-name``,
``data-attendee-name-middle-name``, ``data-attendee-name-title``, ``data-attendee-name-calling-name``,
``data-attendee-name-latin-transcription``. If you don't know or don't care, you can also just pass a string as
``data-attendee-name``, which will pre-fill the last part of the name, whatever that is.
Any configured pretix plugins might understand more data fields. For example, if the appropriate plugins on pretix
Hosted or pretix Enterprise are active, you can pass the following fields:
* If you use the campaigns plugin, you can pass a campaign ID as a value to ``data-campaign``. This way, all orders
made through this widget will be counted towards this campaign.
* If you use the tracking plugin, you can pass a Google Analytics User ID to enable cross-domain tracking. This will
require you to dynamically load the widget, like this::
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXX-1', 'auto');
ga('send', 'pageview');
window.pretixWidgetCallback = function () {
window.PretixWidget.build_widgets = false;
window.addEventListener('load', function() { // Wait for GA to be loaded
if(window.ga && ga.create) {
ga(function(tracker) {
window.PretixWidget.widget_data["tracking-ga-id"] = tracker.get('clientId');
window.PretixWidget.buildWidgets()
});
} else { // Tracking is probably blocked
window.PretixWidget.buildWidgets()
}
});
};
</script>
.. versionchanged:: 2.3
Data passing options have been added in pretix 2.3. If you use a self-hosted version of pretix, they only work
fully if you configured a redis server.
.. _Let's Encrypt: https://letsencrypt.org/ .. _Let's Encrypt: https://letsencrypt.org/

View File

@@ -750,7 +750,7 @@ class Question(LoggedModel):
@staticmethod @staticmethod
def _clean_identifier(event, code, instance=None): def _clean_identifier(event, code, instance=None):
qs = Question.objects.filter(event=event, identifier=code) qs = Question.objects.filter(event=event, identifier__iexact=code)
if instance: if instance:
qs = qs.exclude(pk=instance.pk) qs = qs.exclude(pk=instance.pk)
if qs.exists(): if qs.exists():

View File

@@ -4,6 +4,7 @@ from decimal import Decimal
from typing import List, Optional from typing import List, Optional
from celery.exceptions import MaxRetriesExceededError from celery.exceptions import MaxRetriesExceededError
from django.core.exceptions import ValidationError
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.dispatch import receiver from django.dispatch import receiver
@@ -17,9 +18,11 @@ from pretix.base.models import (
from pretix.base.models.event import SubEvent from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee from pretix.base.models.orders import OrderFee
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
from pretix.base.services.checkin import _save_answers
from pretix.base.services.locking import LockTimeoutException from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.pricing import get_price from pretix.base.services.pricing import get_price
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledTask
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.templatetags.rich_text import rich_text from pretix.base.templatetags.rich_text import rich_text
from pretix.celery_app import app from pretix.celery_app import app
from pretix.presale.signals import ( from pretix.presale.signals import (
@@ -99,7 +102,7 @@ class CartManager:
AddOperation: 30 AddOperation: 30
} }
def __init__(self, event: Event, cart_id: str, invoice_address: InvoiceAddress=None): def __init__(self, event: Event, cart_id: str, invoice_address: InvoiceAddress=None, widget_data=None):
self.event = event self.event = event
self.cart_id = cart_id self.cart_id = cart_id
self.now_dt = now() self.now_dt = now()
@@ -111,6 +114,7 @@ class CartManager:
self._variations_cache = {} self._variations_cache = {}
self._expiry = None self._expiry = None
self.invoice_address = invoice_address self.invoice_address = invoice_address
self._widget_data = widget_data or {}
@property @property
def positions(self): def positions(self):
@@ -605,12 +609,37 @@ class CartManager:
if isinstance(op, self.AddOperation): if isinstance(op, self.AddOperation):
for k in range(available_count): for k in range(available_count):
new_cart_positions.append(CartPosition( cp = CartPosition(
event=self.event, item=op.item, variation=op.variation, event=self.event, item=op.item, variation=op.variation,
price=op.price.gross, expires=self._expiry, cart_id=self.cart_id, price=op.price.gross, expires=self._expiry, cart_id=self.cart_id,
voucher=op.voucher, addon_to=op.addon_to if op.addon_to else None, voucher=op.voucher, addon_to=op.addon_to if op.addon_to else None,
subevent=op.subevent, includes_tax=op.includes_tax subevent=op.subevent, includes_tax=op.includes_tax
)) )
if self.event.settings.attendee_names_asked:
scheme = PERSON_NAME_SCHEMES.get(self.event.settings.name_scheme)
if 'attendee-name' in self._widget_data:
cp.attendee_name_parts = {'_legacy': self._widget_data['attendee-name']}
if any('attendee-name-{}'.format(k.replace('_', '-')) in self._widget_data for k, l, w
in scheme['fields']):
cp.attendee_name_parts = {
k: self._widget_data.get('attendee-name-{}'.format(k.replace('_', '-')), '')
for k, l, w in scheme['fields']
}
if self.event.settings.attendee_emails_asked and 'email' in self._widget_data:
cp.attendee_email = self._widget_data.get('email')
cp._answers = {}
for k, v in self._widget_data.items():
if not k.startswith('question-'):
continue
q = cp.item.questions.filter(ask_during_checkin=False, identifier__iexact=k[9:]).first()
if q:
try:
cp._answers[q] = q.clean_answer(v)
except ValidationError:
pass
new_cart_positions.append(cp)
elif isinstance(op, self.ExtendOperation): elif isinstance(op, self.ExtendOperation):
if available_count == 1: if available_count == 1:
op.position.expires = self._expiry op.position.expires = self._expiry
@@ -621,7 +650,11 @@ class CartManager:
else: else:
raise AssertionError("ExtendOperation cannot affect more than one item") raise AssertionError("ExtendOperation cannot affect more than one item")
CartPosition.objects.bulk_create(new_cart_positions) for p in new_cart_positions:
if p._answers:
p.save()
_save_answers(p, {}, p._answers)
CartPosition.objects.bulk_create([p for p in new_cart_positions if not p._answers])
return err return err
def commit(self): def commit(self):
@@ -702,7 +735,7 @@ def get_fees(event, request, total, invoice_address, provider):
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) @app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en', def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en',
invoice_address: int=None) -> None: invoice_address: int=None, widget_data=None) -> None:
""" """
Adds a list of items to a user's cart. Adds a list of items to a user's cart.
:param event: The event ID in question :param event: The event ID in question
@@ -722,7 +755,7 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
try: try:
try: try:
cm = CartManager(event=event, cart_id=cart_id, invoice_address=ia) cm = CartManager(event=event, cart_id=cart_id, invoice_address=ia, widget_data=widget_data)
cm.add_new_items(items) cm.add_new_items(items)
cm.commit() cm.commit()
except LockTimeoutException: except LockTimeoutException:

View File

@@ -309,7 +309,10 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
@cached_property @cached_property
def contact_form(self): def contact_form(self):
initial = { initial = {
'email': self.cart_session.get('email', '') 'email': (
self.cart_session.get('email', '') or
self.cart_session.get('widget_data', {}).get('email', '')
)
} }
initial.update(self.cart_session.get('contact_form_data', {})) initial.update(self.cart_session.get('contact_form_data', {}))
return ContactForm(data=self.request.POST if self.request.method == "POST" else None, return ContactForm(data=self.request.POST if self.request.method == "POST" else None,

View File

@@ -237,6 +237,9 @@
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %} {% endif %}
{% if "widget_data" in request.GET %}
<input type="hidden" value="{{ request.GET.widget_data }}" name="widget_data">
{% endif %}
</form> </form>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -1,8 +1,10 @@
import json
import mimetypes import mimetypes
import os import os
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.cache import caches
from django.db.models import Q from django.db.models import Q
from django.http import FileResponse, Http404, JsonResponse from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@@ -33,6 +35,11 @@ from pretix.presale.views.event import (
) )
from pretix.presale.views.robots import NoSearchIndexViewMixin from pretix.presale.views.robots import NoSearchIndexViewMixin
try:
widget_data_cache = caches['redis']
except:
widget_data_cache = caches['default']
class CartActionMixin: class CartActionMixin:
@@ -266,11 +273,20 @@ def get_or_create_cart_id(request, create=True):
cart_data = {} cart_data = {}
if prefix and 'take_cart_id' in request.GET and current_id: if prefix and 'take_cart_id' in request.GET and current_id:
new_id = current_id new_id = current_id
cached_widget_data = widget_data_cache.get('widget_data_{}'.format(current_id))
if cached_widget_data:
cart_data['widget_data'] = cached_widget_data
else: else:
if not create: if not create:
return None return None
new_id = generate_cart_id(request, prefix=prefix) new_id = generate_cart_id(request, prefix=prefix)
if 'widget_data' not in cart_data and 'widget_data' in request.GET:
try:
cart_data['widget_data'] = json.loads(request.GET.get('widget_data'))
except ValueError:
pass
if 'carts' not in request.session: if 'carts' not in request.session:
request.session['carts'] = {} request.session['carts'] = {}
if new_id not in request.session['carts']: if new_id not in request.session['carts']:
@@ -349,10 +365,24 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
} }
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
cart_id = get_or_create_cart_id(self.request)
if "widget_data" in request.POST:
try:
widget_data = json.loads(request.POST.get("widget_data", "{}"))
except ValueError:
widget_data = {}
else:
widget_data_cache.set('widget_data_{}'.format(cart_id), widget_data, 600)
cs = cart_session(request)
cs['widget_data'] = widget_data
else:
cs = cart_session(request)
widget_data = cs.get('widget_data', {})
items = self._items_from_post_data() items = self._items_from_post_data()
if items: if items:
return self.do(self.request.event.id, items, get_or_create_cart_id(self.request), translation.get_language(), return self.do(self.request.event.id, items, cart_id, translation.get_language(),
self.invoice_address.pk) self.invoice_address.pk, widget_data)
else: else:
if 'ajax' in self.request.GET or 'ajax' in self.request.POST: if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
return JsonResponse({ return JsonResponse({
@@ -449,6 +479,10 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'iframe' in request.GET and 'require_cookie' not in request.GET: if 'iframe' in request.GET and 'require_cookie' not in request.GET:
return redirect(request.get_full_path() + '&require_cookie=1') return redirect(request.get_full_path() + '&require_cookie=1')
if len(self.request.GET.get('widget_data', '{}')) > 3:
# We've been passed data from a widget, we need to create a cart session to store it.
get_or_create_cart_id(request)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@@ -189,6 +189,9 @@ class EventIndex(EventViewMixin, CartMixin, TemplateView):
return redirect(eventreverse(request.event, 'presale:event.index', kwargs=kwargs) + '?require_cookie=true&cart_id={}'.format( return redirect(eventreverse(request.event, 'presale:event.index', kwargs=kwargs) + '?require_cookie=true&cart_id={}'.format(
request.GET.get('take_cart_id') request.GET.get('take_cart_id')
)) ))
elif request.GET.get('iframe', '') == '1' and len(self.request.GET.get('widget_data', '{}')) > 3:
# We've been passed data from a widget, we need to create a cart session to store it.
get_or_create_cart_id(request)
elif 'require_cookie' in request.GET and settings.SESSION_COOKIE_NAME not in request.COOKIES: elif 'require_cookie' in request.GET and settings.SESSION_COOKIE_NAME not in request.COOKIES:
# Cookies are in fact not supported # Cookies are in fact not supported
r = render(request, 'pretixpresale/event/cookies.html', { r = render(request, 'pretixpresale/event/cookies.html', {

View File

@@ -3,6 +3,11 @@
/* This is embedded in an isolation wrapper that exposes siteglobals as the global /* This is embedded in an isolation wrapper that exposes siteglobals as the global
scope. */ scope. */
window.PretixWidget = {
'build_widgets': true,
'widget_data': {}
};
var Vue = module.exports; var Vue = module.exports;
var strings = { var strings = {
@@ -457,6 +462,9 @@ var shared_methods = {
return; return;
} }
var redirect_url = this.$root.voucherFormTarget + '&voucher=' + this.voucher + '&subevent=' + this.$root.subevent; var redirect_url = this.$root.voucherFormTarget + '&voucher=' + this.voucher + '&subevent=' + this.$root.subevent;
if (this.$root.widget_data) {
redirect_url += '&widget_data=' + escape(this.$root.widget_data_json);
}
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0]; var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true; this.$root.overlay.frame_loading = true;
iframe.src = redirect_url; iframe.src = redirect_url;
@@ -466,6 +474,9 @@ var shared_methods = {
if (this.$root.cart_id) { if (this.$root.cart_id) {
redirect_url += '&take_cart_id=' + this.$root.cart_id; redirect_url += '&take_cart_id=' + this.$root.cart_id;
} }
if (this.$root.widget_data) {
redirect_url += '&widget_data=' + escape(this.$root.widget_data_json);
}
if (this.$root.useIframe) { if (this.$root.useIframe) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0]; var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true; this.$root.overlay.frame_loading = true;
@@ -572,6 +583,7 @@ Vue.component('pretix-widget', {
+ '<form method="post" :action="$root.formTarget" ref="form" target="_blank">' + '<form method="post" :action="$root.formTarget" ref="form" target="_blank">'
+ '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">' + '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
+ '<input type="hidden" name="subevent" :value="$root.subevent" />' + '<input type="hidden" name="subevent" :value="$root.subevent" />'
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>' + '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
+ '<div class="pretix-widget-info-message pretix-widget-clickable"' + '<div class="pretix-widget-info-message pretix-widget-clickable"'
+ ' v-if="$root.cart_exists">' + ' v-if="$root.cart_exists">'
@@ -618,6 +630,7 @@ Vue.component('pretix-button', {
+ '<form method="post" :action="$root.formTarget" ref="form" target="_blank">' + '<form method="post" :action="$root.formTarget" ref="form" target="_blank">'
+ '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">' + '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
+ '<input type="hidden" name="subevent" :value="$root.subevent" />' + '<input type="hidden" name="subevent" :value="$root.subevent" />'
+ '<input type="hidden" name="widget_data" :value="$root.widget_data_json" />'
+ '<input type="hidden" v-for="item in $root.items" :name="item.item" :value="item.count" />' + '<input type="hidden" v-for="item in $root.items" :name="item.item" :value="item.count" />'
+ '<button class="pretix-button" @click="buy">{{ $root.button_text }}</button>' + '<button class="pretix-button" @click="buy">{{ $root.button_text }}</button>'
+ '</form>' + '</form>'
@@ -733,6 +746,9 @@ var shared_root_computed = {
} }
} }
return has_priced || cnt_items > 1; return has_priced || cnt_items > 1;
},
widget_data_json: function () {
return JSON.stringify(this.widget_data);
} }
}; };
@@ -757,6 +773,23 @@ var create_overlay = function (app) {
app.$root.overlay = framechild; app.$root.overlay = framechild;
}; };
function get_ga_client_id(tracking_id) {
if (typeof ga === "undefined") {
return null;
}
try {
var trackers = ga.getAll();
var i, len;
for (i = 0, len = trackers.length; i < len; i += 1) {
if (trackers[i].get('trackingId') === tracking_id) {
return trackers[i].get('clientId');
}
}
} catch (e) {
}
return null;
}
var create_widget = function (element) { var create_widget = function (element) {
var event_url = element.attributes.event.value; var event_url = element.attributes.event.value;
if (!event_url.match(/\/$/)) { if (!event_url.match(/\/$/)) {
@@ -766,6 +799,13 @@ var create_widget = function (element) {
var subevent = element.attributes.subevent ? element.attributes.subevent.value : null; var subevent = element.attributes.subevent ? element.attributes.subevent.value : null;
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false; var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
var disable_vouchers = element.attributes["disable-vouchers"] ? true : false; var disable_vouchers = element.attributes["disable-vouchers"] ? true : false;
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
for (var i = 0; i < element.attributes.length; i++) {
var attrib = element.attributes[i];
if (attrib.name.match(/^data-.*$/)) {
widget_data[attrib.name.replace(/^data-/, '')] = attrib.value;
}
}
if (element.tagName !== "pretix-widget") { if (element.tagName !== "pretix-widget") {
element.innerHTML = "<pretix-widget></pretix-widget>"; element.innerHTML = "<pretix-widget></pretix-widget>";
@@ -786,6 +826,7 @@ var create_widget = function (element) {
skip_ssl: skip_ssl, skip_ssl: skip_ssl,
error: null, error: null,
display_add_to_cart: false, display_add_to_cart: false,
widget_data: widget_data,
loading: 1, loading: 1,
widget_id: 'pretix-widget-' + widget_id, widget_id: 'pretix-widget-' + widget_id,
vouchers_exist: false, vouchers_exist: false,
@@ -815,6 +856,13 @@ var create_button = function (element) {
var raw_items = element.attributes.items ? element.attributes.items.value : ""; var raw_items = element.attributes.items ? element.attributes.items.value : "";
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false; var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
var button_text = element.innerHTML; var button_text = element.innerHTML;
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
for (var i = 0; i < element.attributes.length; i++) {
var attrib = element.attributes[i];
if (attrib.name.match(/^data-.*$/)) {
widget_data[attrib.name.replace(/^data-/, '')] = attrib.value;
}
}
if (element.tagName !== "pretix-button") { if (element.tagName !== "pretix-button") {
element.innerHTML = "<pretix-button>" + element.innerHTML + "</pretix-button>"; element.innerHTML = "<pretix-button>" + element.innerHTML + "</pretix-button>";
@@ -840,6 +888,7 @@ var create_button = function (element) {
voucher_code: voucher, voucher_code: voucher,
items: items, items: items,
error: null, error: null,
widget_data: widget_data,
widget_id: 'pretix-widget-' + widget_id, widget_id: 'pretix-widget-' + widget_id,
button_text: button_text button_text: button_text
} }
@@ -856,27 +905,35 @@ var create_button = function (element) {
/* Find all widgets on the page and render them */ /* Find all widgets on the page and render them */
widgetlist = []; widgetlist = [];
buttonlist = []; buttonlist = [];
document.createElement("pretix-widget"); window.PretixWidget.buildWidgets = function () {
document.createElement("pretix-button"); document.createElement("pretix-widget");
docReady(function () { document.createElement("pretix-button");
var widgets = document.querySelectorAll("pretix-widget, div.pretix-widget-compat"); docReady(function () {
var wlength = widgets.length; var widgets = document.querySelectorAll("pretix-widget, div.pretix-widget-compat");
for (var i = 0; i < wlength; i++) { var wlength = widgets.length;
var widget = widgets[i]; for (var i = 0; i < wlength; i++) {
widgetlist.push(create_widget(widget)); var widget = widgets[i];
} widgetlist.push(create_widget(widget));
}
var buttons = document.querySelectorAll("pretix-button, div.pretix-button-compat"); var buttons = document.querySelectorAll("pretix-button, div.pretix-button-compat");
var blength = buttons.length; var blength = buttons.length;
for (var i = 0; i < blength; i++) { for (var i = 0; i < blength; i++) {
var button = buttons[i]; var button = buttons[i];
buttonlist.push(create_button(button)); buttonlist.push(create_button(button));
} }
}); });
};
if (typeof window.pretixWidgetCallback !== "undefined") {
window.pretixWidgetCallback();
}
if (window.PretixWidget.build_widgets) {
window.PretixWidget.buildWidgets();
}
/* Set a global variable for debugging. In DEBUG mode, siteglobals will be window, otherwise it will be something /* Set a global variable for debugging. In DEBUG mode, siteglobals will be window, otherwise it will be something
unnamed. */ unnamed. */
siteglobals.pretixwidget = { siteglobals.pretixwidget_debug = {
'Vue': Vue, 'Vue': Vue,
'widgets': widgetlist, 'widgets': widgetlist,
'buttons': buttonlist 'buttons': buttonlist

View File

@@ -1,4 +1,5 @@
import datetime import datetime
import json
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
@@ -107,6 +108,91 @@ class CartTest(CartTestMixin, TestCase):
self.assertIsNone(objs[0].variation) self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23) self.assertEqual(objs[0].price, 23)
def test_widget_data_post(self):
self.event.settings.attendee_names_asked = True
self.event.settings.attendee_emails_asked = True
q = self.event.questions.create(
event=self.event, question='What is your shoe size?', type=Question.TYPE_NUMBER,
required=True
)
q.items.add(self.ticket)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'widget_data': json.dumps({
'attendee-name-full-name': 'John Doe',
'email': 'foo@example.com',
'question-' + q.identifier: '43'
})
}, follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23)
self.assertEqual(objs[0].attendee_email, "foo@example.com")
self.assertEqual(objs[0].attendee_name, "John Doe")
a = objs[0].answers.first()
self.assertEqual(a.answer, "43")
self.assertEqual(a.question, q)
def test_widget_data_ignored_unknown_or_unasked(self):
self.event.settings.attendee_names_asked = False
self.event.settings.attendee_emails_asked = False
q = self.event.questions.create(
event=self.event, question='What is your shoe size?', type=Question.TYPE_NUMBER,
required=True
)
q.items.add(self.ticket)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'widget_data': json.dumps({
'attendee-name-full-name': 'John Doe',
'email': 'foo@example.com',
'question-' + q.identifier: 'bla'
})
}, follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23)
assert not objs[0].attendee_email
assert not objs[0].attendee_name
assert not objs[0].answers.exists()
def test_widget_data_session(self):
self.event.settings.attendee_names_asked = True
self.event.settings.attendee_emails_asked = True
q = self.event.questions.create(
event=self.event, question='What is your shoe size?', type=Question.TYPE_NUMBER,
required=True
)
q.items.add(self.ticket)
self._set_session('widget_data', {
'attendee-name': 'John Doe',
'email': 'foo@example.com',
'question-' + q.identifier: '43'
})
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
}, follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, 23)
self.assertEqual(objs[0].attendee_email, "foo@example.com")
self.assertEqual(objs[0].attendee_name, "John Doe")
a = objs[0].answers.first()
self.assertEqual(a.answer, "43")
self.assertEqual(a.question, q)
def _set_session(self, key, value): def _set_session(self, key, value):
session = self.client.session session = self.client.session
session['carts'][get_cart_session_key(self.client, self.event)][key] = value session['carts'][get_cart_session_key(self.client, self.event)][key] = value