forked from CGM_Public/pretix_original
Compare commits
20 Commits
seat-orgch
...
copy-from-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa372b3df0 | ||
|
|
9c28537d1c | ||
|
|
95b1a4a434 | ||
|
|
019801e9dc | ||
|
|
3e97fd87d1 | ||
|
|
8bcc0e4641 | ||
|
|
878becfee9 | ||
|
|
273c34999c | ||
|
|
3f4f9d98de | ||
|
|
d703eeb770 | ||
|
|
b7754d8737 | ||
|
|
ed62ecaccb | ||
|
|
5c3ef3f2b9 | ||
|
|
f3282807e2 | ||
|
|
52944ff3a3 | ||
|
|
23a9018988 | ||
|
|
936c771d5e | ||
|
|
d064a7affa | ||
|
|
4b894eb433 | ||
|
|
6c7ef89779 |
@@ -608,6 +608,7 @@ Order position endpoints
|
||||
:<json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
|
||||
:<json boolean force: Specifies that the check-in should succeed regardless of previous check-ins or required
|
||||
questions that have not been filled. Defaults to ``false``.
|
||||
:<json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
|
||||
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
|
||||
Defaults to ``false`` and only works when ``include_pending`` is set on the check-in
|
||||
list.
|
||||
|
||||
@@ -16,3 +16,4 @@ If you want to **create** a plugin, please go to the
|
||||
badges
|
||||
campaigns
|
||||
digital
|
||||
webinar
|
||||
|
||||
43
doc/plugins/webinar.rst
Normal file
43
doc/plugins/webinar.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
pretix Webinar
|
||||
==============
|
||||
|
||||
Fetch host URLs
|
||||
---------------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/webinars/
|
||||
|
||||
Returns a list of all currently available webinar calls configured for an event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/webinars/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "Webinar B – Sept. 8th, 2020",
|
||||
"hosturl": "http://pretix.eu/demo/museum/webinar/host/a9aded3d7bd4df60/30611a34f9fee5d3/"
|
||||
},
|
||||
{
|
||||
"name": "Webinar A – Sept. 8, 2020",
|
||||
"hosturl": "http://pretix.eu/demo/museum/webinar/host/e714x7d4a4a36a04/b9cc444665xxx757/"
|
||||
}
|
||||
]
|
||||
|
||||
:query subevent: Limit the result to the webinar(s) for a specific subevent.
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to view it.
|
||||
@@ -128,6 +128,8 @@ class ListExporter(BaseExporter):
|
||||
|
||||
def _render_csv(self, form_data, output_file=None, **kwargs):
|
||||
if output_file:
|
||||
if 'b' in output_file.mode:
|
||||
output_file = io.TextIOWrapper(output_file, encoding='utf-8', newline='')
|
||||
writer = csv.writer(output_file, **kwargs)
|
||||
total = 0
|
||||
counter = 0
|
||||
@@ -246,6 +248,8 @@ class MultiSheetListExporter(ListExporter):
|
||||
total = 0
|
||||
counter = 0
|
||||
if output_file:
|
||||
if 'b' in output_file.mode:
|
||||
output_file = io.TextIOWrapper(output_file, encoding='utf-8', newline='')
|
||||
writer = csv.writer(output_file, **kwargs)
|
||||
for line in self.iterate_sheet(form_data, sheet):
|
||||
if isinstance(line, self.ProgressSetTotal):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
import pytz
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import override
|
||||
from django_scopes import scope
|
||||
@@ -8,45 +9,83 @@ from tqdm import tqdm
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.base.signals import register_data_exporters
|
||||
from pretix.base.signals import (
|
||||
register_data_exporters, register_multievent_data_exporters,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Run an exporter to get data out of pretix"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('organizer_slug', nargs=1, type=str)
|
||||
parser.add_argument('event_slug', nargs=1, type=str)
|
||||
parser.add_argument('export_provider', nargs=1, type=str)
|
||||
parser.add_argument('output_file', nargs=1, type=str)
|
||||
parser.add_argument('organizer_slug', type=str)
|
||||
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('event_slug', nargs="?", type=str)
|
||||
group.add_argument('--all-events', action='store_true')
|
||||
group.add_argument('--event_slugs', nargs="+", type=str)
|
||||
|
||||
parser.add_argument('export_provider', type=str)
|
||||
parser.add_argument('output_file', type=str)
|
||||
parser.add_argument('--parameters', action='store', type=str, help='JSON-formatted parameters')
|
||||
parser.add_argument('--locale', action='store', type=str, help='...')
|
||||
parser.add_argument('--timezone', action='store', type=str, help='...')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
o = Organizer.objects.get(slug=options['organizer_slug'][0])
|
||||
o = Organizer.objects.get(slug=options['organizer_slug'])
|
||||
except Organizer.DoesNotExist:
|
||||
self.stderr.write(self.style.ERROR('Organizer not found.'))
|
||||
sys.exit(1)
|
||||
|
||||
locale = options.get("locale", None)
|
||||
timezone = pytz.timezone(options['timezone']) if options.get('timezone') else None
|
||||
|
||||
with scope(organizer=o):
|
||||
try:
|
||||
e = o.events.get(slug=options['event_slug'][0])
|
||||
except Event.DoesNotExist:
|
||||
self.stderr.write(self.style.ERROR('Event not found.'))
|
||||
sys.exit(1)
|
||||
if options['event_slug']:
|
||||
try:
|
||||
e = o.events.get(slug=options['event_slug'])
|
||||
except Event.DoesNotExist:
|
||||
self.stderr.write(self.style.ERROR('Event not found.'))
|
||||
sys.exit(1)
|
||||
if not locale:
|
||||
locale = e.settings.locale
|
||||
if not timezone:
|
||||
timezone = e.settings.timezone
|
||||
signal_result = register_data_exporters.send(e)
|
||||
else:
|
||||
e = o.events.all()
|
||||
if options['event_slugs']:
|
||||
e = e.filter(slug__in=options['event_slugs'])
|
||||
not_found = set(options['event_slugs']).difference(event.slug for event in e)
|
||||
if not_found:
|
||||
self.stderr.write(self.style.ERROR('The following events were not found: {}'.format(", ".join(not_found))))
|
||||
sys.exit(1)
|
||||
if not e.exists():
|
||||
self.stderr.write(self.style.ERROR('No events found.'))
|
||||
sys.exit(1)
|
||||
|
||||
if not locale:
|
||||
locale = e.first().settings.locale
|
||||
self.stderr.write(self.style.WARNING(
|
||||
"Guessing locale '{}' based on event '{}'.".format(locale, e.first().slug)))
|
||||
if not timezone:
|
||||
timezone = e.first().settings.timezone
|
||||
self.stderr.write(self.style.WARNING(
|
||||
"Guessing timezone '{}' based on event '{}'.".format(timezone, e.first().slug)))
|
||||
signal_result = register_multievent_data_exporters.send(o)
|
||||
|
||||
pbar = tqdm(total=100)
|
||||
|
||||
def report_status(val):
|
||||
pbar.update(round(val, 2) - pbar.n)
|
||||
|
||||
with language(e.settings.locale), override(e.settings.timezone):
|
||||
responses = register_data_exporters.send(e)
|
||||
for receiver, response in responses:
|
||||
with language(locale), override(timezone):
|
||||
for receiver, response in signal_result:
|
||||
ex = response(e, report_status)
|
||||
if ex.identifier == options['export_provider'][0]:
|
||||
if ex.identifier == options['export_provider']:
|
||||
params = json.loads(options.get('parameters') or '{}')
|
||||
with open(options['output_file'][0], 'wb') as f:
|
||||
with open(options['output_file'], 'wb') as f:
|
||||
try:
|
||||
ex.render(form_data=params, output_file=f)
|
||||
except TypeError:
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.dispatch.dispatcher import NO_RECEIVERS
|
||||
|
||||
from pretix.helpers.periodic import SKIPPED
|
||||
|
||||
from ...signals import periodic_task
|
||||
|
||||
@@ -9,12 +14,30 @@ class Command(BaseCommand):
|
||||
help = "Run periodic tasks"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for recv, resp in periodic_task.send_robust(self):
|
||||
if isinstance(resp, Exception):
|
||||
verbosity = int(options['verbosity'])
|
||||
|
||||
if not periodic_task.receivers or periodic_task.sender_receivers_cache.get(self) is NO_RECEIVERS:
|
||||
return
|
||||
|
||||
for receiver in periodic_task._live_receivers(self):
|
||||
if verbosity > 1:
|
||||
self.stdout.write(f'Running {receiver.__module__}.{receiver.__name__}…')
|
||||
t0 = time.time()
|
||||
try:
|
||||
r = receiver(signal=periodic_task, sender=self)
|
||||
except Exception as err:
|
||||
if isinstance(Exception, KeyboardInterrupt):
|
||||
raise err
|
||||
if settings.SENTRY_ENABLED:
|
||||
from sentry_sdk import capture_exception
|
||||
capture_exception(resp)
|
||||
capture_exception(err)
|
||||
self.stdout.write(self.style.ERROR(f'FAIL: {str(err)}\n'))
|
||||
else:
|
||||
raise resp
|
||||
|
||||
call_command('clearsessions')
|
||||
self.stdout.write(self.style.ERROR(f'FAIL: {str(err)}\n'))
|
||||
traceback.print_exc()
|
||||
else:
|
||||
if options.get('verbosity') > 1:
|
||||
if r is SKIPPED:
|
||||
self.stdout.write(self.style.SUCCESS(f'Skipped {receiver.__module__}.{receiver.__name__}'))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS(f'Completed {receiver.__module__}.{receiver.__name__} in {round(time.time() - t0, 3)}s'))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
@@ -40,3 +41,9 @@ def clean_cached_tickets(sender, **kwargs):
|
||||
cf.delete()
|
||||
for cf in CachedCombinedTicket.objects.filter(created__lte=now() - timedelta(minutes=30), file__isnull=True):
|
||||
cf.delete()
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def clearsessions(sender, **kwargs):
|
||||
call_command('clearsessions')
|
||||
|
||||
@@ -406,7 +406,7 @@ class QuotaAvailability:
|
||||
@receiver(signal=periodic_task)
|
||||
@minimum_interval(minutes_after_success=60)
|
||||
def build_all_quota_caches(sender, **kwargs):
|
||||
refresh_quota_caches.apply_async()
|
||||
refresh_quota_caches.apply()
|
||||
|
||||
|
||||
def grouper(iterable, n, fillvalue=None):
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, User, WaitingListEntry
|
||||
@@ -77,6 +80,11 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
|
||||
def process_waitinglist(sender, **kwargs):
|
||||
qs = Event.objects.filter(
|
||||
live=True
|
||||
).exclude(
|
||||
Q(date_to__isnull=True) | Q(date_to__lt=now() - timedelta(days=14)),
|
||||
Q(presale_end__isnull=True) | Q(presale_end__lt=now() - timedelta(days=14)),
|
||||
has_subevents=False,
|
||||
date_from__lt=now() - timedelta(days=14),
|
||||
).prefetch_related('_settings_objects', 'organizer___settings_objects').select_related('organizer')
|
||||
for e in qs:
|
||||
if e.settings.waiting_list_auto and (e.presale_is_running or e.has_subevents):
|
||||
|
||||
@@ -71,12 +71,9 @@
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
@@ -53,7 +54,31 @@ class BaseQuestionsViewMixin:
|
||||
data=(self.request.POST if self.request.method == 'POST' else None),
|
||||
files=(self.request.FILES if self.request.method == 'POST' else None))
|
||||
form.pos = cartpos or orderpos
|
||||
form.show_copy_answers_to_addon_button = form.pos.addon_to and set(form.pos.addon_to.item.questions.all()) & set(form.pos.item.questions.all())
|
||||
|
||||
if form.pos.addon_to_id is not None:
|
||||
form.copy_answer_from = None
|
||||
# addons typically do not have the same item as the main position, so we look for overlapping questions
|
||||
form.show_copy_answers_to_addon_button = bool(
|
||||
set(form.pos.addon_to.item.questions.values_list("id", flat=True)) &
|
||||
set(form.pos.item.questions.values_list("id", flat=True)))
|
||||
else:
|
||||
form.show_copy_answers_to_addon_button = False
|
||||
# look for a position we can best copy answers from
|
||||
form.copy_answer_from = next(
|
||||
itertools.chain(
|
||||
( # match a position with the same item
|
||||
other_form.pos.id for other_form in formlist
|
||||
if other_form.pos.addon_to_id is None and form.pos.item.id == other_form.pos.item.id
|
||||
),
|
||||
( # match a position with questions in common
|
||||
other_form.pos.id for other_form in formlist
|
||||
if other_form.pos.addon_to_id is None
|
||||
and set(form.pos.item.questions.values_list("id", flat=True)) & set(other_form.pos.item.questions.values_list("id", flat=True))
|
||||
)
|
||||
),
|
||||
None # didn't find a position to copy answers from
|
||||
)
|
||||
|
||||
if len(form.fields) > 0:
|
||||
formlist.append(form)
|
||||
return formlist
|
||||
@@ -105,8 +130,7 @@ class BaseQuestionsViewMixin:
|
||||
if hasattr(field, 'answer'):
|
||||
# We already have a cached answer object, so we don't
|
||||
# have to create a new one
|
||||
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
|
||||
or (isinstance(v, QuerySet) and not v.exists()):
|
||||
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) or (isinstance(v, QuerySet) and not v.exists()):
|
||||
if field.answer.file:
|
||||
field.answer.file.delete()
|
||||
field.answer.delete()
|
||||
|
||||
@@ -440,7 +440,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
bleach.clean(logentry.parsed_data.get('msg'), tags=[], strip=True)
|
||||
)
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.checkin'):
|
||||
if sender and logentry.action_type.startswith('pretix.event.checkin'):
|
||||
return _display_checkin(sender, logentry)
|
||||
|
||||
if logentry.action_type == 'pretix.control.views.checkin':
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
{% if d.revoked %}<del>{% endif %}
|
||||
{{ d.name }}
|
||||
{% if d.revoked %}</del>{% endif %}
|
||||
<br>
|
||||
<small>{{ d.unique_serial }}</small>
|
||||
</td>
|
||||
<td>
|
||||
{{ d.hardware_brand|default_if_none:"" }} {{ d.hardware_model|default_if_none:"" }}
|
||||
|
||||
@@ -751,7 +751,9 @@ class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
||||
qs = LogEntry.objects.filter(
|
||||
device_id=self.device
|
||||
).select_related(
|
||||
'user', 'content_type', 'api_token', 'oauth_application', 'device', 'event'
|
||||
'user', 'content_type', 'api_token', 'oauth_application',
|
||||
).prefetch_related(
|
||||
'device', 'event'
|
||||
).order_by('-datetime')
|
||||
return qs
|
||||
|
||||
|
||||
@@ -33,6 +33,15 @@ class CachedCountries(Countries):
|
||||
class FastCountryField(CountryField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("countries", CachedCountries)
|
||||
|
||||
if "max_length" not in kwargs:
|
||||
# Override logic from CountryField to include 20% buffer. We don't want to migrate our database
|
||||
# every time a new country is added to the system!
|
||||
if kwargs.get("multiple", False):
|
||||
kwargs["max_length"] = int(len(kwargs['countries']()) * 3 * 1.2)
|
||||
else:
|
||||
kwargs["max_length"] = 2
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def check(self, **kwargs):
|
||||
|
||||
@@ -6,6 +6,8 @@ from django.core.cache import cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SKIPPED = object()
|
||||
|
||||
|
||||
def minimum_interval(minutes_after_success, minutes_after_error=0, minutes_running_timeout=30):
|
||||
"""
|
||||
@@ -26,12 +28,12 @@ def minimum_interval(minutes_after_success, minutes_after_error=0, minutes_runni
|
||||
running_val = cache.get(key_running)
|
||||
if running_val:
|
||||
# Currently running
|
||||
return
|
||||
return SKIPPED
|
||||
|
||||
result_val = cache.get(key_result)
|
||||
if result_val:
|
||||
# Has run recently
|
||||
return
|
||||
return SKIPPED
|
||||
|
||||
uniqid = str(uuid.uuid4())
|
||||
cache.set(key_running, uniqid, timeout=minutes_running_timeout * 60)
|
||||
|
||||
@@ -21430,7 +21430,7 @@ msgstr "Dies ist keine Veranstaltungsreihe."
|
||||
#: pretix/presale/views/widget.py:329
|
||||
#, python-format
|
||||
msgid "from %(start_date)s"
|
||||
msgstr "von %(start_date)s"
|
||||
msgstr "ab %(start_date)s"
|
||||
|
||||
#: pretix/presale/views/widget.py:332
|
||||
msgid "Sale soon"
|
||||
|
||||
@@ -21382,7 +21382,7 @@ msgstr "Dies ist keine Veranstaltungsreihe."
|
||||
#: pretix/presale/views/widget.py:329
|
||||
#, python-format
|
||||
msgid "from %(start_date)s"
|
||||
msgstr "von %(start_date)s"
|
||||
msgstr "ab %(start_date)s"
|
||||
|
||||
#: pretix/presale/views/widget.py:332
|
||||
msgid "Sale soon"
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
{% if pos.variation %}
|
||||
– {{ pos.variation }}
|
||||
{% endif %}
|
||||
{% if forloop.counter > 1 %}
|
||||
{% if forms.0.copy_answer_from is not None %}
|
||||
<span class="text-right flip">
|
||||
<button type="button" data-id="{{ forloop.counter0 }}" name="copy" class="js-copy-answers btn btn-default btn-xs">{% trans "Copy answers from above" %}</button>
|
||||
<button type="button" data-id="{{ forms.0.pos.id }}" data-copy-from="{{ forms.0.copy_answer_from }}" name="copy" class="js-copy-answers btn btn-default btn-xs">{% trans "Copy answers from above" %}</button>
|
||||
<i class="fa fa-angle-down collapse-indicator"></i>
|
||||
</span>
|
||||
{% else %}
|
||||
@@ -122,13 +122,13 @@
|
||||
<legend>
|
||||
{% if form.show_copy_answers_to_addon_button %}
|
||||
<span class="pull-right flip">
|
||||
<button type="button" data-id="{{ forloop.parentloop.counter0 }}" data-addonid="{{ forloop.counter0 }}" name="copy" class="js-copy-answers-addon btn btn-default btn-xs">{% trans "Copy answers" %}</button>
|
||||
<button type="button" data-id="{{ forms.0.pos.id }}" data-addonid="{{ forloop.counter0 }}" name="copy" class="js-copy-answers-addon btn btn-default btn-xs">{% trans "Copy answers" %}</button>
|
||||
</span>
|
||||
{% endif %}
|
||||
+ {{ form.pos.item.name }}{% if form.pos.variation %} – {{ form.pos.variation.value }}{% endif %}
|
||||
</legend>
|
||||
{% endif %}
|
||||
<div data-idx="{{ forloop.parentloop.counter0 }}" data-addonidx="{{ forloop.counter0 }}">
|
||||
<div data-idx="{{ forms.0.pos.id }}" data-addonidx="{{ forloop.counter0 }}">
|
||||
{% bootstrap_form form layout="checkout" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -16,9 +16,13 @@ function ngettext(singular, plural, count) {
|
||||
|
||||
function interpolate(fmt, object, named) {
|
||||
if (named) {
|
||||
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
||||
return fmt.replace(/%\(\w+\)s/g, function (match) {
|
||||
return String(obj[match.slice(2, -2)])
|
||||
});
|
||||
} else {
|
||||
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
||||
return fmt.replace(/%s/g, function (match) {
|
||||
return String(obj.shift())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,13 +175,13 @@ $(function () {
|
||||
$(".js-copy-answers").click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let idx = $(this).data('id');
|
||||
const addonDivs = $('div[data-idx="' + idx +'"]')
|
||||
const idx = $(this).data('id');
|
||||
const copyFromIdx = $(this).data('copy-from')
|
||||
const addonDivs = $('div[data-idx="' + idx + '"]')
|
||||
addonDivs.each(function (index) {
|
||||
const elements = $(this).find('input, select, textarea');
|
||||
|
||||
const addonIdx = $(this).attr("data-addonidx");
|
||||
const answersDiv = $('div[data-idx="0"][data-addonidx="' + addonIdx + '"]');
|
||||
const addonIdx = $(this).data("addonidx");
|
||||
const answersDiv = $('div[data-idx="' + copyFromIdx + '"][data-addonidx="' + addonIdx + '"]');
|
||||
const answers = answersDiv.find('input, select, textarea');
|
||||
|
||||
copy_answers(elements, answers);
|
||||
@@ -189,7 +193,7 @@ $(function () {
|
||||
e.stopPropagation();
|
||||
const id = $(this).data('id');
|
||||
const addonId = $(this).data('addonid');
|
||||
const addonDiv = $('div[data-idx="' + id +'"][data-addonidx="' + addonId + '"]');
|
||||
const addonDiv = $('div[data-idx="' + id + '"][data-addonidx="' + addonId + '"]');
|
||||
const elements = addonDiv.find('input, select, textarea');
|
||||
const answers = $('*[data-idx="' + id + '"] input, *[data-idx="' + id + '"] select, *[data-idx="' + id + '"] textarea');
|
||||
copy_answers(elements, answers);
|
||||
@@ -249,7 +253,7 @@ $(function () {
|
||||
is_enabled = true;
|
||||
}
|
||||
});
|
||||
$(".input-seat-selection option").each(function() {
|
||||
$(".input-seat-selection option").each(function () {
|
||||
if ($(this).val() && $(this).val() !== "" && $(this).prop('selected')) {
|
||||
is_enabled = true;
|
||||
}
|
||||
@@ -257,7 +261,9 @@ $(function () {
|
||||
}
|
||||
if (!is_enabled && !$(".has-seating").length) {
|
||||
$("#btn-add-to-cart").prop("disabled", !is_enabled).popover({
|
||||
'content': function () { return gettext("Please enter a quantity for one of the ticket types.") },
|
||||
'content': function () {
|
||||
return gettext("Please enter a quantity for one of the ticket types.")
|
||||
},
|
||||
'placement': 'top',
|
||||
'trigger': 'hover focus'
|
||||
});
|
||||
@@ -353,7 +359,9 @@ $(function () {
|
||||
if (counter > curCounter) {
|
||||
return; // Lost race
|
||||
}
|
||||
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
|
||||
dependent.find("option").filter(function (t) {
|
||||
return !!$(this).attr("value")
|
||||
}).remove();
|
||||
if (data.data.length > 0) {
|
||||
$.each(data.data, function (k, s) {
|
||||
dependent.append($("<option>").attr("value", s.code).text(s.name));
|
||||
@@ -400,8 +408,7 @@ $(function () {
|
||||
true
|
||||
));
|
||||
}
|
||||
var cancel_fee_slider = $('#cancel-fee-slider').slider({
|
||||
}).on('slide', function () {
|
||||
var cancel_fee_slider = $('#cancel-fee-slider').slider({}).on('slide', function () {
|
||||
cancel_fee_slider_update();
|
||||
}).data('slider');
|
||||
if (cancel_fee_slider) {
|
||||
@@ -417,7 +424,7 @@ $(function () {
|
||||
}
|
||||
|
||||
var local_tz = moment.tz.guess()
|
||||
$("span[data-timezone]").each(function() {
|
||||
$("span[data-timezone]").each(function () {
|
||||
var t = moment.tz($(this).attr("data-time"), $(this).attr("data-timezone"))
|
||||
var tz = moment.tz.zone($(this).attr("data-timezone"))
|
||||
|
||||
@@ -445,39 +452,47 @@ $(function () {
|
||||
});
|
||||
|
||||
function copy_answers(elements, answers) {
|
||||
elements.each(function (index) {
|
||||
elements.each(function (index) {
|
||||
var input = $(this),
|
||||
tagName = input.prop('tagName').toLowerCase(),
|
||||
attributeType = input.attr('type'),
|
||||
suffix = input.attr('name').split('-')[1];
|
||||
|
||||
let a = false;
|
||||
switch (tagName) {
|
||||
case "textarea":
|
||||
input.val(answers.filter("[name$=" + suffix + "]").val());
|
||||
a = answers.filter("[name$=" + suffix + "]");
|
||||
if (a.length) input.val(a.val());
|
||||
break;
|
||||
case "select":
|
||||
input.val(answers.filter("[name$=" + suffix + "]").find(":selected").val()).change();
|
||||
a = answers.filter("[name$=" + suffix + "]").find(":selected");
|
||||
if (a.length) input.val(a.val()).change();
|
||||
break;
|
||||
case "input":
|
||||
switch (attributeType) {
|
||||
case "text":
|
||||
case "number":
|
||||
input.val(answers.filter("[name$=" + suffix + "]").val());
|
||||
a = answers.filter("[name$=" + suffix + "]")
|
||||
if (a.length) input.val(a.val());
|
||||
break;
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
if (input.attr('value')) {
|
||||
input.prop("checked", answers.filter("[name$=" + suffix + "][value=" + input.attr('value') + "]").prop("checked"));
|
||||
a = answers.filter("[name$=" + suffix + "][value=" + input.attr('value') + "]")
|
||||
if (a.length) input.prop("checked", a.prop("checked"));
|
||||
} else {
|
||||
input.prop("checked", answers.filter("[name$=" + suffix + "]").prop("checked"));
|
||||
a = answers.filter("[name$=" + suffix + "]")
|
||||
if (a.length) input.prop("checked", a.prop("checked"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
input.val(answers.filter("[name$=" + suffix + "]").val());
|
||||
a = answers.filter("[name$=" + suffix + "]")
|
||||
if (a.length) input.val(a.val());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
input.val(answers.filter("[name$=" + suffix + "]").val());
|
||||
a = answers.filter("[name$=" + suffix + "]")
|
||||
if (a.length) input.val(a.val());
|
||||
}
|
||||
});
|
||||
questions_toggle_dependent(true);
|
||||
|
||||
@@ -1225,7 +1225,7 @@ Vue.component('pretix-widget', {
|
||||
data: shared_widget_data,
|
||||
methods: shared_methods,
|
||||
mounted: function () {
|
||||
this.mobile = this.$refs.wrapper.clientWidth <= 800;
|
||||
this.mobile = this.$refs.wrapper.clientWidth <= 600;
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
@@ -1418,7 +1418,7 @@ var shared_root_computed = {
|
||||
},
|
||||
formTarget: function () {
|
||||
var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
var is_android = navigator.platform.toLowerCase().indexOf("android") > -1;
|
||||
var is_android = navigator.userAgent.toLowerCase().indexOf("android") > -1;
|
||||
if (is_android && is_firefox) {
|
||||
// Opening a POST form in a new browser fails in Firefox. This is supposed to be fixed since FF 76
|
||||
// but for some reason, it is still the case in FF for Android.
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
z-index: 16777271;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s, visibility 0.5s;
|
||||
transition: opacity 0.5s; /* do not animate visibility or we'll have a flashing thing on load */
|
||||
|
||||
&.pretix-widget-alert-shown {
|
||||
visibility: visible;
|
||||
|
||||
Reference in New Issue
Block a user