Compare commits

...

5 Commits

Author SHA1 Message Date
Raphael Michel
3b62737d4c Widget: Support for inline seating plan 2022-12-22 13:24:42 +01:00
Raphael Michel
d3698b3e2f Widget: Annotate parts of widget source code 2022-12-22 11:36:22 +01:00
Fazenda Dengo
ff828ecc92 Translations: Update Portuguese (Portugal)
Currently translated at 85.6% (4228 of 4934 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_PT/

powered by weblate
2022-12-22 10:47:53 +01:00
Fazenda Dengo
d0236572f0 Translations: Update Portuguese (Brazil)
Currently translated at 12.9% (637 of 4934 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2022-12-22 10:47:53 +01:00
Raphael Michel
8ea6f3bc7d Fix #2499 -- Incorrect type detection order in RelativeDateWrapper 2022-12-21 15:34:31 +01:00
7 changed files with 142 additions and 34 deletions

View File

@@ -87,6 +87,18 @@ website. If you confident to have a good reason for not using SSL, you can overr
<pretix-widget event="https://pretix.eu/demo/democon/" skip-ssl-check></pretix-widget>
Seating plans
-------------
By default, events with seating plans just show a button that opens the seating plan. You can also have the seating
plan embedded into the widget directly by using::
<pretix-widget event="https://pretix.eu/demo/democon/" seating-embedded></pretix-widget>
Note that the seating plan will only be embedded if the widget has enough space (currently min. 992 pixels width, may change
in the future) and that the seating plan part of the widget can unfortunately *not* be styled with CSS like the rest of
the widget.
Always open a new tab
---------------------

View File

@@ -59,10 +59,10 @@ class RelativeDateWrapper:
def date(self, event) -> datetime.date:
from .models import SubEvent
if isinstance(self.data, datetime.date):
return self.data
elif isinstance(self.data, datetime.datetime):
if isinstance(self.data, datetime.datetime):
return self.data.date()
elif isinstance(self.data, datetime.date):
return self.data
else:
if self.data.minutes_before is not None:
raise ValueError('A minute-based relative datetime can not be used as a date')

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
"PO-Revision-Date: 2021-09-27 06:00+0000\n"
"Last-Translator: Diego Rodrigo <diegorodrigo90@gmail.com>\n"
"PO-Revision-Date: 2022-12-21 20:00+0000\n"
"Last-Translator: Fazenda Dengo <fazendadengo@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
"Language: pt_BR\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.8\n"
"X-Generator: Weblate 4.15\n"
#: pretix/api/auth/devicesecurity.py:28
msgid ""
@@ -9268,10 +9268,8 @@ msgstr "Nome do evento"
#: pretix/base/settings.py:3031 pretix/base/settings.py:3045
#: pretix/base/settings.py:3096 pretix/base/settings.py:3114
#: pretix/base/settings.py:3133
#, fuzzy
#| msgid "Full name"
msgid "Family name"
msgstr "Nome completo"
msgstr "Sobrenome"
#: pretix/base/settings.py:2943 pretix/base/settings.py:2959
#: pretix/base/settings.py:2975 pretix/base/settings.py:2990

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
"PO-Revision-Date: 2022-11-28 19:03+0000\n"
"Last-Translator: Vasco Baleia <vb2003.12@gmail.com>\n"
"PO-Revision-Date: 2022-12-21 20:00+0000\n"
"Last-Translator: Fazenda Dengo <fazendadengo@gmail.com>\n"
"Language-Team: Portuguese (Portugal) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_PT/>\n"
"Language: pt_PT\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.14.1\n"
"X-Generator: Weblate 4.15\n"
#: pretix/api/auth/devicesecurity.py:28
msgid ""
@@ -9560,7 +9560,7 @@ msgstr "Nome próprio"
#: pretix/base/settings.py:3096 pretix/base/settings.py:3114
#: pretix/base/settings.py:3133
msgid "Family name"
msgstr "Apelido"
msgstr "Sobrenome"
#: pretix/base/settings.py:2943 pretix/base/settings.py:2959
#: pretix/base/settings.py:2975 pretix/base/settings.py:2990

View File

@@ -663,11 +663,15 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
return context
@method_decorator(allow_frame_if_namespaced, 'dispatch')
@method_decorator(iframe_entry_view_wrapper, 'dispatch')
class SeatingPlanView(EventViewMixin, TemplateView):
template_name = "pretixpresale/event/seatingplan.html"
def dispatch(self, request, *args, **kwargs):
r = super().dispatch(request, *args, **kwargs)
r.xframe_options_exempt = True
return r
def get(self, request, *args, **kwargs):
from pretix.presale.views.cart import get_or_create_cart_id

View File

@@ -141,18 +141,21 @@ var api = {
},
'_postFormJSON': function (endpoint, form, callback, err_callback) {
var params = [].filter.call(form.elements, function (el) {
return (el.type !== 'checkbox' && el.type !== 'radio') || el.checked;
})
.filter(function (el) {
return !!el.name && !!el.value;
})
.filter(function (el) {
var params;
if (Array.isArray(form)) {
params = form
} else {
params = [].filter.call(form.elements, function (el) {
return (el.type !== 'checkbox' && el.type !== 'radio') || el.checked;
}).filter(function (el) {
return !!el.name && !!el.value;
}).filter(function (el) {
return !el.disabled;
})
.map(function (el) {
return encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value);
}).join('&');
}
params = params.map(function (el) {
return encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value);
}).join('&');
var xhr = api._getXHR();
xhr.open("POST", endpoint, true);
@@ -361,6 +364,7 @@ Vue.component('variation', {
template: ('<div class="pretix-widget-variation">'
+ '<div class="pretix-widget-item-row">'
// Variation description
+ '<div class="pretix-widget-item-info-col">'
+ '<div class="pretix-widget-item-title-and-description">'
+ '<strong class="pretix-widget-item-title">{{ variation.value }}</strong>'
@@ -372,12 +376,15 @@ Vue.component('variation', {
+ '</div>'
+ '</div>'
// Price
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="variation.price" :free_price="item.free_price" :original_price="orig_price"'
+ ' :field_name="\'price_\' + item.id + \'_\' + variation.id" v-if="$root.showPrices">'
+ '</pricebox>'
+ '<span v-if="!$root.showPrices">&nbsp;</span>'
+ '</div>'
// Availability
+ '<div class="pretix-widget-item-availability-col">'
+ '<availbox :item="item" :variation="variation"></availbox>'
+ '</div>'
@@ -405,6 +412,7 @@ Vue.component('item', {
template: ('<div v-bind:class="classObject">'
+ '<div class="pretix-widget-item-row pretix-widget-main-item-row">'
// Product description
+ '<div class="pretix-widget-item-info-col">'
+ '<img :src="item.picture" v-if="item.picture" class="pretix-widget-item-picture">'
+ '<div class="pretix-widget-item-title-and-description">'
@@ -424,6 +432,7 @@ Vue.component('item', {
+ '</div>'
+ '</div>'
// Price
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="item.price" :free_price="item.free_price" v-if="!item.has_variations && $root.showPrices"'
+ ' :field_name="\'price_\' + item.id" :original_price="item.original_price">'
@@ -431,6 +440,8 @@ Vue.component('item', {
+ '<div class="pretix-widget-pricebox" v-if="item.has_variations && $root.showPrices">{{ pricerange }}</div>'
+ '<span v-if="!$root.showPrices">&nbsp;</span>'
+ '</div>'
// Availability
+ '<div class="pretix-widget-item-availability-col">'
+ '<a v-if="show_toggle" href="#" @click.prevent.stop="expand">'+ strings.variations + '</a>'
+ '<availbox v-if="!item.has_variations" :item="item"></availbox>'
@@ -439,6 +450,7 @@ Vue.component('item', {
+ '<div class="pretix-widget-clear"></div>'
+ '</div>'
// Variations
+ '<div :class="varClasses" v-if="item.has_variations">'
+ '<variation v-for="variation in item.variations" :variation="variation" :item="item" :key="variation.id">'
+ '</variation>'
@@ -513,7 +525,7 @@ Vue.component('category', {
});
var shared_methods = {
buy: function (event) {
buy: function (event, data) {
if (this.$root.useIframe) {
if (event) {
event.preventDefault();
@@ -532,7 +544,7 @@ var shared_methods = {
this.$root.overlay.frame_loading = true;
this.async_task_interval = 100;
var form = this.$refs.form;
var form = data === undefined ? this.$refs.form : data;
if (form === undefined) {
form = this.$refs.formcomp.$refs.form;
}
@@ -654,7 +666,7 @@ var shared_methods = {
}
},
handleResize: function () {
this.mobile = this.$refs.wrapper.clientWidth <= 800;
this.clientWidth = this.$refs.wrapper.clientWidth;
}
};
@@ -665,7 +677,7 @@ var shared_widget_data = function () {
async_task_timeout: null,
async_task_interval: 100,
voucher: null,
mobile: false,
clientWidth: 1000,
}
};
@@ -761,6 +773,7 @@ Vue.component('pretix-overlay', {
Vue.component('pretix-widget-event-form', {
template: ('<div class="pretix-widget-event-form">'
// Back navigation
+ '<div class="pretix-widget-event-list-back" v-if="$root.events || $root.weeks || $root.days">'
+ '<a href="#" @click.prevent.stop="back_to_list" v-if="!$root.subevent">&lsaquo; '
+ strings['back_to_list']
@@ -769,18 +782,28 @@ Vue.component('pretix-widget-event-form', {
+ strings['back_to_dates']
+ '</a>'
+ '</div>'
// Event name
+ '<div class="pretix-widget-event-header" v-if="$root.events || $root.weeks || $root.days">'
+ '<strong>{{ $root.name }}</strong>'
+ '</div>'
// Date range
+ '<div class="pretix-widget-event-details" v-if="($root.events || $root.weeks || $root.days) && $root.date_range">'
+ '{{ $root.date_range }}'
+ '</div>'
// Form start
+ '<div class="pretix-widget-event-description" v-if="($root.events || $root.weeks || $root.days) && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
+ '<form method="post" :action="$root.formAction" ref="form" :target="$root.formTarget">'
+ '<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="widget_data" :value="$root.widget_data_json" />'
// Error message
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
// Resume cart
+ '<div class="pretix-widget-info-message pretix-widget-clickable"'
+ ' v-if="$root.cart_exists">'
+ '<button @click.prevent.stop="$parent.resume" class="pretix-widget-resume-button" type="button">'
@@ -789,11 +812,20 @@ Vue.component('pretix-widget-event-form', {
+ strings['cart_exists']
+ '<div class="pretix-widget-clear"></div>'
+ '</div>'
+ '<div class="pretix-widget-seating-link-wrapper" v-if="this.$root.has_seating_plan">'
// Seating plan
+ '<div class="pretix-widget-seating-link-wrapper" v-if="$root.has_seating_plan && !show_seating_plan_inline">'
+ '<button class="pretix-widget-seating-link" @click.prevent.stop="$root.startseating">'
+ strings['show_seating']
+ '</button>'
+ '</div>'
+ '<div class="pretix-widget-seating-embed" v-else-if="$root.has_seating_plan && show_seating_plan_inline">'
+ '<iframe :key="\'seatingframe\' + $root.loadid" class="pretix-widget-seating-embed-iframe" ref="seatingframe"'
+ ' :src="seatingframe" frameborder="0" referrerpolicy="origin" allowtransparency="true">'
+ '</iframe>'
+ '</div>'
// Waiting list for seating plan
+ '<div class="pretix-widget-seating-waitinglist" v-if="this.$root.has_seating_plan && this.$root.has_seating_plan_waitinglist">'
+ '<div class="pretix-widget-seating-waitinglist-text">'
+ strings['seating_plan_waiting_list']
@@ -805,11 +837,18 @@ Vue.component('pretix-widget-event-form', {
+ '</div>'
+ '<div class="pretix-widget-clear"></div>'
+ '</div>'
// Actual product list
+ '<category v-for="category in this.$root.categories" :category="category" :key="category.id"></category>'
// Buy button
+ '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">'
+ '<button @click="$parent.buy" type="submit" :disabled="buy_disabled">{{ this.buy_label }}</button>'
+ '</div>'
+ '</form>'
// Voucher form
+ '<form method="get" :action="$root.voucherFormTarget" target="_blank" '
+ ' v-if="$root.vouchers_exist && !$root.disable_vouchers && !$root.voucher_code">'
+ '<div class="pretix-widget-voucher">'
@@ -827,6 +866,7 @@ Vue.component('pretix-widget-event-form', {
+ '<div class="pretix-widget-clear"></div>'
+ '</div>'
+ '</form>'
+ '</div>'
),
data: function () {
@@ -838,10 +878,14 @@ Vue.component('pretix-widget-event-form', {
this.$root.$on('amounts_changed', this.calculate_buy_disabled)
this.$root.$on('focus_voucher_field', this.focus_voucher_field)
this.calculate_buy_disabled()
window.addEventListener('message', this.on_seat_select);
},
beforeDestroy: function() {
this.$root.$off('amounts_changed', this.calculate_buy_disabled)
this.$root.$off('focus_voucher_field', this.focus_voucher_field)
window.addEventListener('message', this.on_seat_select);
},
computed: {
buy_label: function () {
@@ -871,9 +915,26 @@ Vue.component('pretix-widget-event-form', {
} else {
return strings.buy;
}
}
},
show_seating_plan_inline: function () {
return this.$root.seating_embedded && this.$parent.clientWidth > 992;
},
seatingframe: function () {
var seatingframe_url = this.$root.target_url;
if (this.$root.subevent){
seatingframe_url += '/' + this.$root.subevent;
}
seatingframe_url += '/seatingframe/?inline=1&locale=' + lang + '&widget_id=' + this.$root.widgetindex;
return seatingframe_url;
},
},
methods: {
on_seat_select: function (ev) {
if (ev.data.source !== "pretix_widget_seating") return;
if (parseInt(ev.data.widget_id) !== this.$root.widgetindex) return; // In case multiple widgets are on this page
if (ev.data.action !== "buy") return;
this.$parent.buy(null, ev.data.data);
},
focus_voucher_field: function() {
this.$refs.voucherinput.scrollIntoView(false)
this.$refs.voucherinput.focus()
@@ -1157,15 +1218,21 @@ Vue.component('pretix-widget-event-calendar-row', {
Vue.component('pretix-widget-event-calendar', {
template: ('<div class="pretix-widget-event-calendar" ref="calendar">'
// Back navigation
+ '<div class="pretix-widget-back" v-if="$root.events !== undefined">'
+ '<a href="#" @click.prevent.stop="back_to_list">&lsaquo; '
+ strings['back']
+ '</a>'
+ '</div>'
// Headline
+ '<div class="pretix-widget-event-header" v-if="$root.parent_stack.length > 0">'
+ '<strong>{{ $root.name }}</strong>'
+ '</div>'
+ '<div class="pretix-widget-event-description" v-if="$root.parent_stack.length > 0 && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
// Calendar navigation
+ '<div class="pretix-widget-event-calendar-head">'
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent.stop="prevmonth">&laquo; '
+ strings['previous_month']
@@ -1175,6 +1242,8 @@ Vue.component('pretix-widget-event-calendar', {
+ strings['next_month']
+ ' &raquo;</a>'
+ '</div>'
// Calendar
+ '<table class="pretix-widget-event-calendar-table">'
+ '<thead>'
+ '<tr>'
@@ -1233,14 +1302,19 @@ Vue.component('pretix-widget-event-calendar', {
Vue.component('pretix-widget-event-week-calendar', {
template: ('<div class="pretix-widget-event-calendar pretix-widget-event-week-calendar" ref="weekcalendar">'
// Back navigation
+ '<div class="pretix-widget-back" v-if="$root.events !== undefined">'
+ '<a href="#" @click.prevent.stop="back_to_list">&lsaquo; '
+ strings['back']
+ '</a>'
+ '</div>'
// Event header
+ '<div class="pretix-widget-event-header" v-if="$root.parent_stack.length > 0">'
+ '<strong>{{ $root.name }}</strong>'
+ '</div>'
// Calendar navigation
+ '<div class="pretix-widget-event-description" v-if="$root.parent_stack.length > 0 && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
+ '<div class="pretix-widget-event-calendar-head">'
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent.stop="prevweek">&laquo; '
@@ -1251,12 +1325,15 @@ Vue.component('pretix-widget-event-week-calendar', {
+ strings['next_week']
+ ' &raquo;</a>'
+ '</div>'
// Actual calendar
+ '<div class="pretix-widget-event-week-table">'
+ '<div class="pretix-widget-event-week-col" v-for="d in $root.days">'
+ '<pretix-widget-event-week-cell :day="d">'
+ '</pretix-widget-event-week-cell>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>'),
computed: {
@@ -1323,9 +1400,12 @@ Vue.component('pretix-widget', {
data: shared_widget_data,
methods: shared_methods,
mounted: function () {
this.mobile = this.$refs.wrapper.clientWidth <= 600;
this.clientWidth = this.$refs.wrapper.clientWidth;
},
computed: {
mobile: function () {
return this.clientWidth <= 600;
},
classObject: function () {
o = {'pretix-widget': true};
if (this.mobile) {
@@ -1488,6 +1568,7 @@ var shared_root_methods = {
root.has_seating_plan_waitinglist = data.has_seating_plan_waitinglist;
root.itemnum = data.itemnum;
}
root.loadid++; // force-reload iframes
root.poweredby = data.poweredby;
if (root.loading > 0) {
root.loading--;
@@ -1626,7 +1707,7 @@ var shared_root_computed = {
},
widget_data_json: function () {
return JSON.stringify(this.widget_data);
}
},
};
var create_overlay = function (app) {
@@ -1668,7 +1749,7 @@ function get_ga_client_id(tracking_id) {
return null;
}
var create_widget = function (element) {
var create_widget = function (element, widgetindex) {
var target_url = element.attributes.event.value;
if (!target_url.match(/\/$/)) {
target_url += "/";
@@ -1677,6 +1758,7 @@ var create_widget = function (element) {
var subevent = element.attributes.subevent ? element.attributes.subevent.value : null;
var style = element.attributes.style ? element.attributes.style.value : null;
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
var seating_embedded = element.attributes["seating-embedded"] ? true : false;
var disable_iframe = element.attributes["disable-iframe"] ? true : false;
var disable_vouchers = element.attributes["disable-vouchers"] ? true : false;
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
@@ -1699,6 +1781,7 @@ var create_widget = function (element) {
el: element,
data: function () {
return {
widgetindex: widgetindex,
target_url: target_url,
parent_stack: [],
subevent: subevent,
@@ -1720,6 +1803,7 @@ var create_widget = function (element) {
voucher_explanation_text: null,
show_variations_expanded: !!variations,
skip_ssl: skip_ssl,
seating_embedded: seating_embedded,
disable_iframe: disable_iframe,
style: style,
connection_error: false,
@@ -1734,6 +1818,7 @@ var create_widget = function (element) {
display_add_to_cart: false,
widget_data: widget_data,
loading: 1,
loadid: 1,
widget_id: 'pretix-widget-' + widget_id,
vouchers_exist: false,
disable_vouchers: disable_vouchers,
@@ -1834,7 +1919,7 @@ window.PretixWidget.buildWidgets = function () {
var wlength = widgets.length;
for (var i = 0; i < wlength; i++) {
var widget = widgets[i];
widgetlist.push(create_widget(widget));
widgetlist.push(create_widget(widget, i + 1));
}
var buttons = document.querySelectorAll("pretix-button, div.pretix-button-compat");

View File

@@ -331,6 +331,15 @@
width: 100%;
}
.pretix-widget-seating-embed {
margin: 0 -10px;
}
.pretix-widget-seating-embed-iframe {
width: 100%;
aspect-ratio: 2/1;
max-height: 60vh;
}
.pretix-widget-seating-waitinglist {
margin: 15px 0;
}