forked from CGM_Public/pretix_original
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:
@@ -1,5 +1,6 @@
|
|||||||
addon
|
addon
|
||||||
addons
|
addons
|
||||||
|
Analytics
|
||||||
anonymize
|
anonymize
|
||||||
api
|
api
|
||||||
auditability
|
auditability
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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', {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user