mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Merge branch 'master' into a11y-add-landmarks
This commit is contained in:
@@ -409,8 +409,8 @@ class SubEventSerializer(I18nAwareModelSerializer):
|
||||
model = SubEvent
|
||||
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
|
||||
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
|
||||
'seating_plan', 'item_price_overrides', 'variation_price_overrides', 'meta_data',
|
||||
'seat_category_mapping', 'last_modified')
|
||||
'frontpage_text', 'seating_plan', 'item_price_overrides', 'variation_price_overrides',
|
||||
'meta_data', 'seat_category_mapping', 'last_modified')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
@@ -468,7 +468,8 @@ def base_placeholders(sender, **kwargs):
|
||||
'68CYU2H6ZTP3WLK5'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'voucher_list', ['voucher_list'], lambda voucher_list: '\n'.join(voucher_list),
|
||||
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
|
||||
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
|
||||
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import io
|
||||
import re
|
||||
import tempfile
|
||||
from collections import OrderedDict, namedtuple
|
||||
from decimal import Decimal
|
||||
@@ -10,11 +11,21 @@ from django.db.models import QuerySet
|
||||
from django.utils.formats import localize
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell.cell import KNOWN_TYPES
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES
|
||||
|
||||
from pretix.base.models import Event
|
||||
|
||||
|
||||
def excel_safe(val):
|
||||
if not isinstance(val, KNOWN_TYPES):
|
||||
val = str(val)
|
||||
|
||||
if isinstance(val, str):
|
||||
val = re.sub(ILLEGAL_CHARACTERS_RE, '', val)
|
||||
|
||||
return val
|
||||
|
||||
|
||||
class BaseExporter:
|
||||
"""
|
||||
This is the base class for all data exporters
|
||||
@@ -181,7 +192,7 @@ class ListExporter(BaseExporter):
|
||||
total = line.total
|
||||
continue
|
||||
ws.append([
|
||||
str(val) if not isinstance(val, KNOWN_TYPES) else val
|
||||
excel_safe(val) if not isinstance(val, KNOWN_TYPES) else val
|
||||
for val in line
|
||||
])
|
||||
if total:
|
||||
@@ -301,7 +312,7 @@ class MultiSheetListExporter(ListExporter):
|
||||
total = line.total
|
||||
continue
|
||||
ws.append([
|
||||
str(val) if not isinstance(val, KNOWN_TYPES) else val
|
||||
excel_safe(val)
|
||||
for val in line
|
||||
])
|
||||
if total:
|
||||
|
||||
@@ -4,6 +4,7 @@ from datetime import date, datetime, time
|
||||
from django.core.validators import MinLengthValidator, RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import get_current_timezone, make_aware, now
|
||||
@@ -88,6 +89,15 @@ class Organizer(LoggedModel):
|
||||
|
||||
return ObjectRelatedCache(self)
|
||||
|
||||
@cached_property
|
||||
def all_logentries_link(self):
|
||||
return reverse(
|
||||
'control:organizer.log',
|
||||
kwargs={
|
||||
'organizer': self.slug,
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def has_gift_cards(self):
|
||||
return self.cache.get_or_set(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, time
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode
|
||||
|
||||
@@ -766,10 +766,15 @@ class SubEventFilterForm(FilterForm):
|
||||
),
|
||||
required=False
|
||||
)
|
||||
date = forms.DateField(
|
||||
label=_('Date'),
|
||||
date_from = forms.DateField(
|
||||
label=_('Date from'),
|
||||
required=False,
|
||||
widget=DatePickerWidget
|
||||
widget=DatePickerWidget,
|
||||
)
|
||||
date_until = forms.DateField(
|
||||
label=_('Date until'),
|
||||
required=False,
|
||||
widget=DatePickerWidget,
|
||||
)
|
||||
weekday = forms.ChoiceField(
|
||||
label=_('Weekday'),
|
||||
@@ -796,7 +801,8 @@ class SubEventFilterForm(FilterForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['date'].widget = DatePickerWidget()
|
||||
self.fields['date_from'].widget = DatePickerWidget()
|
||||
self.fields['date_until'].widget = DatePickerWidget()
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
@@ -838,19 +844,21 @@ class SubEventFilterForm(FilterForm):
|
||||
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
|
||||
)
|
||||
|
||||
if fdata.get('date'):
|
||||
date_start = make_aware(datetime.combine(
|
||||
fdata.get('date'),
|
||||
if fdata.get('date_until'):
|
||||
date_end = make_aware(datetime.combine(
|
||||
fdata.get('date_until') + timedelta(days=1),
|
||||
time(hour=0, minute=0, second=0, microsecond=0)
|
||||
), get_current_timezone())
|
||||
date_end = make_aware(datetime.combine(
|
||||
fdata.get('date'),
|
||||
time(hour=23, minute=59, second=59, microsecond=999999)
|
||||
), get_current_timezone())
|
||||
qs = qs.filter(
|
||||
Q(date_to__isnull=True, date_from__gte=date_start, date_from__lte=date_end) |
|
||||
Q(date_to__isnull=False, date_from__lte=date_end, date_to__gte=date_start)
|
||||
Q(date_to__isnull=True, date_from__lt=date_end) |
|
||||
Q(date_to__isnull=False, date_to__lt=date_end)
|
||||
)
|
||||
if fdata.get('date_from'):
|
||||
date_start = make_aware(datetime.combine(
|
||||
fdata.get('date_from'),
|
||||
time(hour=0, minute=0, second=0, microsecond=0)
|
||||
), get_current_timezone())
|
||||
qs = qs.filter(date_from__gte=date_start)
|
||||
|
||||
if fdata.get('ordering'):
|
||||
qs = qs.order_by(self.get_order_by())
|
||||
|
||||
@@ -104,7 +104,7 @@ class ConfirmPaymentForm(forms.Form):
|
||||
class CancelForm(ConfirmPaymentForm):
|
||||
send_email = forms.BooleanField(
|
||||
required=False,
|
||||
label=_('Notify user by e-mail'),
|
||||
label=_('Notify customer by email'),
|
||||
initial=True
|
||||
)
|
||||
cancellation_fee = forms.DecimalField(
|
||||
@@ -139,6 +139,11 @@ class CancelForm(ConfirmPaymentForm):
|
||||
|
||||
|
||||
class MarkPaidForm(ConfirmPaymentForm):
|
||||
send_email = forms.BooleanField(
|
||||
required=False,
|
||||
label=_('Notify customer by email'),
|
||||
initial=True
|
||||
)
|
||||
amount = forms.DecimalField(
|
||||
required=True,
|
||||
max_digits=10, decimal_places=2,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from bootstrap3.renderers import FieldRenderer
|
||||
from bootstrap3.renderers import FieldRenderer, InlineFieldRenderer
|
||||
from bootstrap3.text import text_value
|
||||
from django.forms import CheckboxInput
|
||||
from django.forms.utils import flatatt
|
||||
@@ -58,3 +58,40 @@ class ControlFieldRenderer(FieldRenderer):
|
||||
optional=not required and not isinstance(self.widget, CheckboxInput)
|
||||
) + html
|
||||
return html
|
||||
|
||||
|
||||
class BulkEditMixin:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['layout'] = self.layout
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def wrap_field(self, html):
|
||||
field_class = self.get_field_class()
|
||||
name = '{}{}'.format(self.field.form.prefix, self.field.name)
|
||||
checked = self.field.form.data and name in self.field.form.data.getlist('_bulk')
|
||||
html = (
|
||||
'<div class="{klass} bulk-edit-field-group">'
|
||||
'<label class="field-toggle">'
|
||||
'<input type="checkbox" name="_bulk" value="{name}" {checked}> {label}'
|
||||
'</label>'
|
||||
'<div class="field-content">'
|
||||
'{html}'
|
||||
'</div>'
|
||||
'</div>'
|
||||
).format(
|
||||
klass=field_class or '',
|
||||
name=name,
|
||||
label=pgettext('form_bulk', 'change'),
|
||||
checked='checked' if checked else '',
|
||||
html=html
|
||||
)
|
||||
return html
|
||||
|
||||
|
||||
class BulkEditFieldRenderer(BulkEditMixin, FieldRenderer):
|
||||
layout = 'horizontal'
|
||||
|
||||
|
||||
class InlineBulkEditFieldRenderer(BulkEditMixin, InlineFieldRenderer):
|
||||
layout = 'inline'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django import forms
|
||||
from django.forms import formset_factory
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
from django.utils.dates import MONTHS, WEEKDAYS
|
||||
from django.utils.functional import cached_property
|
||||
@@ -11,6 +12,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from i18nfield.forms import I18nInlineFormSet
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.forms.widgets import DatePickerWidget, TimePickerWidget
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.base.reldate import RelativeDateTimeField
|
||||
@@ -88,6 +90,142 @@ class SubEventBulkForm(SubEventForm):
|
||||
del self.fields['date_admission']
|
||||
|
||||
|
||||
class NullBooleanSelect(forms.NullBooleanSelect):
|
||||
def __init__(self, attrs=None):
|
||||
choices = (
|
||||
('unknown', _('Keep the current values')),
|
||||
('true', _('Yes')),
|
||||
('false', _('No')),
|
||||
)
|
||||
super(forms.NullBooleanSelect, self).__init__(attrs, choices)
|
||||
|
||||
|
||||
class SubEventBulkEditForm(I18nModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.mixed_values = kwargs.pop('mixed_values')
|
||||
self.queryset = kwargs.pop('queryset')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['location'].widget.attrs['rows'] = '3'
|
||||
|
||||
for k in ('name', 'location', 'frontpage_text'):
|
||||
# i18n fields
|
||||
if k in self.mixed_values:
|
||||
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||
else:
|
||||
self.fields[k].widget.attrs['placeholder'] = ''
|
||||
self.fields[k].one_required = False
|
||||
|
||||
for k in ('geo_lat', 'geo_lon'):
|
||||
# scalar fields
|
||||
if k in self.mixed_values:
|
||||
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||
else:
|
||||
self.fields[k].widget.attrs['placeholder'] = ''
|
||||
self.fields[k].widget.is_required = False
|
||||
self.fields[k].required = False
|
||||
|
||||
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end'):
|
||||
self.fields[k + '_day'] = forms.DateField(
|
||||
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||
widget=DatePickerWidget(),
|
||||
required=False,
|
||||
)
|
||||
self.fields[k + '_time'] = forms.TimeField(
|
||||
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||
widget=TimePickerWidget(),
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SubEvent
|
||||
localized_fields = '__all__'
|
||||
fields = [
|
||||
'name',
|
||||
'location',
|
||||
'frontpage_text',
|
||||
'geo_lat',
|
||||
'geo_lon',
|
||||
'is_public',
|
||||
'active',
|
||||
]
|
||||
field_classes = {
|
||||
}
|
||||
widgets = {
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
objs = list(self.queryset)
|
||||
fields = set()
|
||||
|
||||
check_map = {
|
||||
'geo_lat': '__geo',
|
||||
'geo_lon': '__geo',
|
||||
}
|
||||
for k in self.fields:
|
||||
cb_val = self.prefix + check_map.get(k, k)
|
||||
if cb_val not in self.data.getlist('_bulk'):
|
||||
continue
|
||||
|
||||
if k.endswith('_day'):
|
||||
for obj in objs:
|
||||
oldval = getattr(obj, k.replace('_day', ''))
|
||||
cval = self.cleaned_data[k]
|
||||
if cval is None:
|
||||
newval = None
|
||||
if not self._meta.model._meta.get_field(k.replace('_day', '')).null:
|
||||
continue
|
||||
elif oldval:
|
||||
oldval = oldval.astimezone(self.event.timezone)
|
||||
newval = oldval.replace(
|
||||
year=cval.year,
|
||||
month=cval.month,
|
||||
day=cval.day,
|
||||
)
|
||||
else:
|
||||
# If there is no previous date/time set, we'll just set to midnight
|
||||
# If the user also selected a time, this will be overridden anyways
|
||||
newval = datetime(
|
||||
year=cval.year,
|
||||
month=cval.month,
|
||||
day=cval.day,
|
||||
tzinfo=self.event.timezone
|
||||
)
|
||||
setattr(obj, k.replace('_day', ''), newval)
|
||||
fields.add(k.replace('_day', ''))
|
||||
elif k.endswith('_time'):
|
||||
for obj in objs:
|
||||
# If there is no previous date/time set and only a time is changed not the
|
||||
# date, we instead use the date of the event
|
||||
oldval = getattr(obj, k.replace('_time', '')) or obj.date_from
|
||||
cval = self.cleaned_data[k]
|
||||
if cval is None:
|
||||
continue
|
||||
oldval = oldval.astimezone(self.event.timezone)
|
||||
newval = oldval.replace(
|
||||
hour=cval.hour,
|
||||
minute=cval.minute,
|
||||
second=cval.second,
|
||||
)
|
||||
setattr(obj, k.replace('_time', ''), newval)
|
||||
fields.add(k.replace('_time', ''))
|
||||
else:
|
||||
fields.add(k)
|
||||
for obj in objs:
|
||||
setattr(obj, k, self.cleaned_data[k])
|
||||
|
||||
if fields:
|
||||
SubEvent.objects.bulk_update(objs, fields, 200)
|
||||
|
||||
def full_clean(self):
|
||||
if len(self.data) == 0:
|
||||
# form wasn't submitted
|
||||
self._errors = ErrorDict()
|
||||
return
|
||||
super().full_clean()
|
||||
|
||||
|
||||
class SubEventItemOrVariationFormMixin:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.item = kwargs.pop('item')
|
||||
@@ -162,7 +300,7 @@ class SubEventMetaValueForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.property = kwargs.pop('property')
|
||||
self.default = kwargs.pop('default', None)
|
||||
self.disabled = kwargs.pop('disabled')
|
||||
self.disabled = kwargs.pop('disabled', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.property.allowed_values:
|
||||
self.fields['value'] = forms.ChoiceField(
|
||||
|
||||
@@ -273,8 +273,15 @@ def _display_checkin(event, logentry):
|
||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
plains = {
|
||||
'pretix.object.cloned': _('This object has been created by cloning.'),
|
||||
'pretix.organizer.changed': _('The organizer has been changed.'),
|
||||
'pretix.organizer.settings': _('The organizer settings have been changed.'),
|
||||
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||
'pretix.webhook.created': _('The webhook has been created.'),
|
||||
'pretix.webhook.changed': _('The webhook has been changed.'),
|
||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||
'pretix.event.canceled': _('The event has been canceled.'),
|
||||
'pretix.event.deleted': _('An event has been deleted.'),
|
||||
'pretix.event.order.modified': _('The order details have been changed.'),
|
||||
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
||||
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.amount layout='horizontal' %}
|
||||
{% bootstrap_field form.payment_date layout='horizontal' %}
|
||||
{% bootstrap_field form.send_email layout='horizontal' %}
|
||||
{% if form.force %}
|
||||
{% bootstrap_field form.force layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='col-md-12' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
{% endblocktrans %}
|
||||
</a>
|
||||
</h1>
|
||||
<form method="post" href="">
|
||||
<form method="post" href=""
|
||||
>
|
||||
{% csrf_token %}
|
||||
<fieldset class="form-inline form-refund-choose">
|
||||
<legend>{% trans "How should the refund be sent?" %}</legend>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% for e in exporters %}
|
||||
<details class="panel panel-default" {% if "identifier" in request.GET %}open{% endif %}>
|
||||
<details class="panel panel-default" {% if "identifier" in request.GET or "exporter" in request.POST %}open{% endif %}>
|
||||
<summary class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{{ e.verbose_name }}
|
||||
|
||||
@@ -22,54 +22,68 @@
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors sform %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<div class="tabbed-form">
|
||||
<fieldset>
|
||||
<legend>{% trans "General" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.slug layout="control" %}
|
||||
{% if form.domain %}
|
||||
{% bootstrap_field form.domain layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Organizer page" %}</legend>
|
||||
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
||||
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
||||
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
||||
{% bootstrap_field sform.event_list_type layout="control" %}
|
||||
{% bootstrap_field sform.event_list_availability layout="control" %}
|
||||
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Localization" %}</legend>
|
||||
{% bootstrap_field sform.locales layout="control" %}
|
||||
{% bootstrap_field sform.region layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Shop design" %}</legend>
|
||||
<p class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
These settings will be used for the organizer page as well as for the default settings
|
||||
for all events in this account that do not have their own design settings.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field sform.primary_color layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||
{% bootstrap_field sform.primary_font layout="control" %}
|
||||
{% bootstrap_field sform.favicon layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Gift cards" %}</legend>
|
||||
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||
</fieldset>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-10">
|
||||
<div class="tabbed-form">
|
||||
<fieldset>
|
||||
<legend>{% trans "General" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.slug layout="control" %}
|
||||
{% if form.domain %}
|
||||
{% bootstrap_field form.domain layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Organizer page" %}</legend>
|
||||
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
||||
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
||||
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
||||
{% bootstrap_field sform.event_list_type layout="control" %}
|
||||
{% bootstrap_field sform.event_list_availability layout="control" %}
|
||||
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Localization" %}</legend>
|
||||
{% bootstrap_field sform.locales layout="control" %}
|
||||
{% bootstrap_field sform.region layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Shop design" %}</legend>
|
||||
<p class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
These settings will be used for the organizer page as well as for the default settings
|
||||
for all events in this account that do not have their own design settings.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field sform.primary_color layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||
{% bootstrap_field sform.primary_font layout="control" %}
|
||||
{% bootstrap_field sform.favicon layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Gift cards" %}</legend>
|
||||
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Change history" %}
|
||||
</h3>
|
||||
</div>
|
||||
{% include "pretixcontrol/includes/logs.html" with obj=organizer %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% for e in exporters %}
|
||||
<details class="panel panel-default" {% if "identifier" in request.GET %}open{% endif %}>
|
||||
<details class="panel panel-default" {% if "identifier" in request.GET or "exporter" in request.POST %}open{% endif %}>
|
||||
<summary class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{{ e.verbose_name }}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Organizer logs" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>{% trans "Organizer logs" %}</h1>
|
||||
<form class="form-inline helper-display-inline" action="" method="get">
|
||||
<input type="hidden" name="content_type" value="{{ request.GET.content_type }}">
|
||||
<input type="hidden" name="object" value="{{ request.GET.object }}">
|
||||
<p>
|
||||
<select name="user" class="form-control">
|
||||
<option value="">{% trans "All actions" %}</option>
|
||||
{% for up in userlist %}
|
||||
{% if up.user__id %}
|
||||
<option value="{{ up.user__id }}"
|
||||
{% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
|
||||
{{ up.user__email }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
|
||||
</p>
|
||||
</form>
|
||||
<ul class="list-group">
|
||||
{% for log in logs %}
|
||||
<li class="list-group-item logentry">
|
||||
<div class="row">
|
||||
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||
<span class="fa fa-clock-o"></span>
|
||||
{{ log.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{% if log.shredded %}
|
||||
<span class="fa fa-eraser fa-danger fa-fw"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Personal data was cleared from this log entry." %}">
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||
{% if log.user %}
|
||||
{% if log.user.is_staff %}
|
||||
<span class="fa fa-id-card fa-danger fa-fw"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "This change was performed by a pretix administrator." %}">
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="fa fa-user fa-fw"></span>
|
||||
{% endif %}
|
||||
{{ log.user.get_full_name }}
|
||||
{% if log.oauth_application %}
|
||||
<br><span class="fa fa-plug fa-fw"></span>
|
||||
{{ log.oauth_application.name }}
|
||||
{% endif %}
|
||||
{% elif log.device %}
|
||||
<span class="fa fa-mobile fa-fw"></span>
|
||||
{{ log.device.name }}
|
||||
{% elif log.api_token %}
|
||||
<span class="fa fa-key fa-fw"></span>
|
||||
{{ log.api_token.name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-sm-12 col-xs-12">
|
||||
{% if log.display_object %}
|
||||
<span class="fa fa-flag"></span> {{ log.display_object|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class="list-group-item">
|
||||
<em>{% trans "No results" %}</em>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,351 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% load captureas %}
|
||||
{% load static %}
|
||||
{% load eventsignal %}
|
||||
{% block title %}{% trans "Change multiple dates" context "subevent" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans "Change multiple dates" context "subevent" %}
|
||||
<small>
|
||||
{% blocktrans trimmed with number=subevents.count %}
|
||||
{{ number }} selected
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
</h1>
|
||||
<form action="" method="post" class="form-horizontal" id="subevent-bulk-create-form">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% for f in itemvar_forms %}
|
||||
{% bootstrap_form_errors f %}
|
||||
{% endfor %}
|
||||
<div class="hidden">
|
||||
{% for se in subevents %}
|
||||
<input type="hidden" name="subevent" value="{{ se.pk }}">
|
||||
{% endfor %}
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="bulkedit" %}
|
||||
{% bootstrap_field form.active layout="bulkedit" %}
|
||||
<div class="geodata-section">
|
||||
{% bootstrap_field form.location layout="bulkedit" %}
|
||||
<div class="form-group geodata-group"
|
||||
data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}"
|
||||
data-attrib="{{ global_settings.leaflet_tiles_attribution }}"
|
||||
data-icon="{% static "leaflet/images/marker-icon.png" %}"
|
||||
data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
|
||||
<label class="col-md-3 control-label">
|
||||
{% trans "Geo coordinates" %}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<div class="bulk-edit-field-group">
|
||||
<label class="field-toggle">
|
||||
<input type="checkbox" name="_bulk" value="{{ form.prefix }}__geo" {% if form.prefix|add:"__geo" in bulk_selected %}checked{% endif %}>
|
||||
{% trans "change" context "form_bulk" %}
|
||||
</label>
|
||||
<div class="field-content">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field form.geo_lat layout="inline" %}
|
||||
{% if global_settings.opencagedata_apikey %}
|
||||
<p class="attrib">
|
||||
<a href="https://openstreetmap.org/" target="_blank" tabindex="-1">
|
||||
{% trans "Geocoding data © OpenStreetMap" %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field form.geo_lon layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field form.frontpage_text layout="bulkedit" %}
|
||||
{% bootstrap_field form.is_public layout="bulkedit" %}
|
||||
{% if meta_forms %}
|
||||
<div class="form-group metadata-group">
|
||||
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||
<div class="col-md-9">
|
||||
{% for form in meta_forms %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.value.id_for_label }}">
|
||||
{{ form.property.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="bulkedit_inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="{{ form.date_from_day.id_for_label }}">
|
||||
{{ form.date_from_day.label }}
|
||||
</label>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field form.date_from_day layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.date_from_time layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="{{ form.date_to_day.id_for_label }}">
|
||||
{{ form.date_to_day.label }}
|
||||
</label>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field form.date_to_day layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.date_to_time layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="{{ form.date_admission_day.id_for_label }}">
|
||||
{{ form.date_admission_day.label }}
|
||||
</label>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field form.date_admission_day layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.date_admission_time layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="{{ form.presale_start_day.id_for_label }}">
|
||||
{{ form.presale_start_day.label }}
|
||||
</label>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field form.presale_start_day layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.presale_start_time layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="{{ form.presale_end_day.id_for_label }}">
|
||||
{{ form.presale_end_day.label }}
|
||||
</label>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field form.presale_end_day layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.presale_end_time layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
{% for f in itemvar_forms %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="bulkedit_inline" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field f.disabled layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Quotas" %}</legend>
|
||||
{% if sampled_quotas|default_if_none:"NONE" == "NONE" %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
You selected a set of dates that currently have different quota setups. You can therefore
|
||||
not change their quotas in bulk. If you want, you can set up a new set of quotas to
|
||||
<strong>replace</strong> the quota setup of all selected dates.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="bulk-edit-field-group">
|
||||
<label class="field-toggle">
|
||||
<input type="checkbox" name="_bulk" value="__quotas" {% if "__quotas" in bulk_selected %}checked{% endif %}>
|
||||
{% trans "change" context "form_bulk" %}
|
||||
</label>
|
||||
<div class="field-content">
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.size layout="control" %}
|
||||
{% bootstrap_field form.itemvars layout="control" %}
|
||||
{% bootstrap_field form.release_after_exit layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_field formset.empty_form.size layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.itemvars layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.release_after_exit layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new quota" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<p> </p>
|
||||
<fieldset>
|
||||
<legend>{% trans "Check-in lists" %}</legend>
|
||||
{% if sampled_lists|default_if_none:"NONE" == "NONE" %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
You selected a set of dates that currently have different check-in list setups. You can
|
||||
therefore not change their check-in lists in bulk.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bulk-edit-field-group">
|
||||
<label class="field-toggle">
|
||||
<input type="checkbox" name="_bulk" value="__checkinlists" {% if "__checkinlists" in bulk_selected %}checked{% endif %}>
|
||||
{% trans "change" context "form_bulk" %}
|
||||
</label>
|
||||
<div class="field-content">
|
||||
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
||||
{{ cl_formset.management_form }}
|
||||
{% bootstrap_formset_errors cl_formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in cl_formset %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.include_pending layout="control" %}
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||
{% if form.gates %}
|
||||
{% bootstrap_field form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="panel panel-default" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ cl_formset.empty_form.id }}
|
||||
{% bootstrap_field cl_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% bootstrap_field cl_formset.empty_form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_field cl_formset.empty_form.include_pending layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
|
||||
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
|
||||
{% if cl_formset.empty_form.gates %}
|
||||
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new check-in list" %}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -22,14 +22,17 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<form class="row filter-form" action="" method="get">
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.query layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.status layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.date layout='inline' %}
|
||||
{% bootstrap_field filter_form.date_from layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.date_until layout='inline' %}
|
||||
</div>
|
||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.weekday layout='inline' %}
|
||||
@@ -43,23 +46,28 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create a new date" context "subevent" %}</a>
|
||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create many new dates" context "subevent" %}</a>
|
||||
</p>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<p>
|
||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create a new date" context "subevent" %}</a>
|
||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create many new dates" context "subevent" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<form action="{% url "control:event.subevents.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="hidden">
|
||||
{{ filter_form.as_p }}
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<input type="checkbox" data-toggle-table/>
|
||||
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>
|
||||
@@ -67,28 +75,40 @@
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Begin" %}
|
||||
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Paid tickets per quota" %}
|
||||
<a href="?{% url_replace request 'ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Status" %}
|
||||
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<tr class="table-select-all warning hidden">
|
||||
<td>
|
||||
<input type="checkbox" name="__ALL" id="__all">
|
||||
</td>
|
||||
<td colspan="5">
|
||||
<label for="__all">
|
||||
{% trans "Select all results on other pages as well" %}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in subevents %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<input type="checkbox" name="subevent" class="" value="{{ s.pk }}"/>
|
||||
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="subevent" class="" value="{{ s.pk }}"/></label>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
@@ -150,6 +170,10 @@
|
||||
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
||||
{% trans "Delete selected" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default btn-save" name="action" value="disable"
|
||||
formaction="{% url "control:event.subevents.bulkedit" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
{% trans "Change selected" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-default btn-save" name="action" value="enable">
|
||||
{% trans "Enable selected" %}
|
||||
</button>
|
||||
|
||||
@@ -122,6 +122,7 @@ urlpatterns = [
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
|
||||
name='organizer.team.delete'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
|
||||
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
||||
@@ -173,6 +174,7 @@ urlpatterns = [
|
||||
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
||||
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
||||
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
||||
url(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
|
||||
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
||||
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
||||
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
||||
|
||||
@@ -1105,6 +1105,7 @@ class OrderTransition(OrderView):
|
||||
|
||||
try:
|
||||
p.confirm(user=self.request.user, count_waitinglist=False, payment_date=payment_date,
|
||||
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||
force=self.mark_paid_form.cleaned_data.get('force', False))
|
||||
except Quota.QuotaExceededException as e:
|
||||
p.state = OrderPayment.PAYMENT_STATE_FAILED
|
||||
@@ -2079,11 +2080,17 @@ class ExportMixin:
|
||||
exporters.append(ex)
|
||||
return exporters
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['exporters'] = self.exporters
|
||||
return ctx
|
||||
|
||||
class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View):
|
||||
|
||||
class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
|
||||
permission = 'can_view_orders'
|
||||
known_errortypes = ['ExportError']
|
||||
task = export
|
||||
template_name = 'pretixcontrol/orders/export.html'
|
||||
|
||||
def get_success_message(self, value):
|
||||
return None
|
||||
@@ -2103,6 +2110,11 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
|
||||
if ex.identifier == self.request.POST.get("exporter"):
|
||||
return ex
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'async_id' in request.GET and settings.HAS_CELERY:
|
||||
return self.get_result(request)
|
||||
return TemplateView.get(self, request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not self.exporter:
|
||||
messages.error(self.request, _('The selected exporter was not found.'))
|
||||
@@ -2112,16 +2124,8 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
|
||||
}))
|
||||
|
||||
if not self.exporter.form.is_valid():
|
||||
messages.error(
|
||||
self.request,
|
||||
str(_('There was a problem processing your input:')) + ' ' + ', '.join(
|
||||
', '.join(line) for line in self.exporter.form.errors.values()
|
||||
)
|
||||
)
|
||||
return redirect(reverse('control:event.orders.export', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug
|
||||
}) + '?identifier=' + self.exporter.identifier)
|
||||
messages.error(self.request, _('There was a problem processing your input. See below for error details.'))
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
cf = CachedFile(web_download=True, session_key=request.session.session_key)
|
||||
cf.date = now()
|
||||
@@ -2134,11 +2138,6 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, TemplateView):
|
||||
permission = 'can_view_orders'
|
||||
template_name = 'pretixcontrol/orders/export.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['exporters'] = self.exporters
|
||||
return ctx
|
||||
|
||||
|
||||
class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
model = OrderRefund
|
||||
|
||||
@@ -51,6 +51,7 @@ from pretix.control.forms.organizer import (
|
||||
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
|
||||
)
|
||||
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||
from pretix.control.permissions import (
|
||||
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
||||
)
|
||||
@@ -1147,8 +1148,9 @@ class ExportMixin:
|
||||
organizer=self.request.organizer
|
||||
)
|
||||
responses = register_multievent_data_exporters.send(self.request.organizer)
|
||||
id = self.request.GET.get("identifier") or self.request.POST.get("exporter")
|
||||
for ex in sorted([response(events) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)):
|
||||
if self.request.GET.get("identifier") and ex.identifier != self.request.GET.get("identifier"):
|
||||
if id and ex.identifier != id:
|
||||
continue
|
||||
|
||||
# Use form parse cycle to generate useful defaults
|
||||
@@ -1180,10 +1182,16 @@ class ExportMixin:
|
||||
exporters.append(ex)
|
||||
return exporters
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['exporters'] = self.exporters
|
||||
return ctx
|
||||
|
||||
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, View):
|
||||
|
||||
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
|
||||
known_errortypes = ['ExportError']
|
||||
task = multiexport
|
||||
template_name = 'pretixcontrol/organizers/export.html'
|
||||
|
||||
def get_success_message(self, value):
|
||||
return None
|
||||
@@ -1202,6 +1210,11 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, V
|
||||
if ex.identifier == self.request.POST.get("exporter"):
|
||||
return ex
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'async_id' in request.GET and settings.HAS_CELERY:
|
||||
return self.get_result(request)
|
||||
return TemplateView.get(self, request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not self.exporter:
|
||||
messages.error(self.request, _('The selected exporter was not found.'))
|
||||
@@ -1231,11 +1244,6 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, V
|
||||
class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
|
||||
template_name = 'pretixcontrol/organizers/export.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['exporters'] = self.exporters
|
||||
return ctx
|
||||
|
||||
|
||||
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
||||
model = Gate
|
||||
@@ -1427,3 +1435,24 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
|
||||
self.object.delete()
|
||||
messages.success(request, _('The selected property has been deleted.'))
|
||||
return redirect(success_url)
|
||||
|
||||
|
||||
class LogView(OrganizerPermissionRequiredMixin, ListView):
|
||||
template_name = 'pretixcontrol/organizers/logs.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
model = LogEntry
|
||||
context_object_name = 'logs'
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.all_logentries().select_related(
|
||||
'user', 'content_type', 'api_token', 'oauth_application', 'device'
|
||||
).order_by('-datetime')
|
||||
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
|
||||
if self.request.GET.get('user'):
|
||||
qs = qs.filter(user_id=self.request.GET.get('user'))
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
return ctx
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import copy
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
||||
from django.contrib import messages
|
||||
from django.core.files import File
|
||||
from django.db import connections, transaction
|
||||
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models import (
|
||||
Count, F, IntegerField, OuterRef, Prefetch, Subquery, Sum,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, TruncDate, TruncTime
|
||||
from django.forms import inlineformset_factory
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import get_format
|
||||
@@ -16,7 +19,9 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django.views import View
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import (
|
||||
CreateView, DeleteView, FormView, ListView, UpdateView,
|
||||
)
|
||||
|
||||
from pretix.base.models import CartPosition, LogEntry
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
@@ -31,24 +36,27 @@ from pretix.control.forms.checkin import SimpleCheckinListForm
|
||||
from pretix.control.forms.filter import SubEventFilterForm
|
||||
from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkForm,
|
||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
||||
SubEventMetaValueForm, TimeFormSet,
|
||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkEditForm,
|
||||
SubEventBulkForm, SubEventForm, SubEventItemForm,
|
||||
SubEventItemVariationForm, SubEventMetaValueForm, TimeFormSet,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import subevent_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.control.views.event import MetaDataEditorMixin
|
||||
from pretix.helpers import GroupConcat
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
|
||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
model = SubEvent
|
||||
context_object_name = 'subevents'
|
||||
template_name = 'pretixcontrol/subevents/index.html'
|
||||
permission = 'can_change_settings'
|
||||
class SubEventQueryMixin:
|
||||
|
||||
def get_queryset(self):
|
||||
@cached_property
|
||||
def request_data(self):
|
||||
if self.request.method == "POST":
|
||||
return self.request.POST
|
||||
return self.request.GET
|
||||
|
||||
def get_queryset(self, list=False):
|
||||
sum_tickets_paid = Quota.objects.filter(
|
||||
subevent=OuterRef('pk')
|
||||
).order_by().values('subevent').annotate(
|
||||
@@ -56,18 +64,39 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
).values(
|
||||
's'
|
||||
)
|
||||
|
||||
qs = self.request.event.subevents.annotate(
|
||||
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||
to_attr='first_quotas')
|
||||
)
|
||||
qs = self.request.event.subevents
|
||||
if list:
|
||||
qs = qs.annotate(
|
||||
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||
to_attr='first_quotas')
|
||||
)
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
if 'subevent' in self.request_data and '__ALL' not in self.request_data:
|
||||
qs = qs.filter(
|
||||
id__in=self.request_data.getlist('subevent')
|
||||
)
|
||||
|
||||
return qs
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return SubEventFilterForm(data=self.request_data, prefix='filter')
|
||||
|
||||
|
||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView):
|
||||
model = SubEvent
|
||||
context_object_name = 'subevents'
|
||||
template_name = 'pretixcontrol/subevents/index.html'
|
||||
permission = 'can_change_settings'
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset(True)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['filter_form'] = self.filter_form
|
||||
@@ -95,10 +124,6 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
)
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return SubEventFilterForm(data=self.request.GET)
|
||||
|
||||
|
||||
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
model = SubEvent
|
||||
@@ -535,19 +560,13 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
return formlist
|
||||
|
||||
|
||||
class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
||||
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
|
||||
permission = 'can_change_settings'
|
||||
|
||||
@cached_property
|
||||
def objects(self):
|
||||
return self.request.event.subevents.filter(
|
||||
id__in=self.request.POST.getlist('subevent')
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get('action') == 'disable':
|
||||
for obj in self.objects:
|
||||
for obj in self.get_queryset():
|
||||
obj.log_action(
|
||||
'pretix.subevent.changed', user=self.request.user, data={
|
||||
'active': False
|
||||
@@ -557,7 +576,7 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
||||
obj.save(update_fields=['active'])
|
||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
|
||||
elif request.POST.get('action') == 'enable':
|
||||
for obj in self.objects:
|
||||
for obj in self.get_queryset():
|
||||
obj.log_action(
|
||||
'pretix.subevent.changed', user=self.request.user, data={
|
||||
'active': True
|
||||
@@ -568,11 +587,11 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
|
||||
elif request.POST.get('action') == 'delete':
|
||||
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
|
||||
'allowed': self.objects.filter(orderposition__isnull=True),
|
||||
'forbidden': self.objects.filter(orderposition__isnull=False),
|
||||
'allowed': self.get_queryset().filter(orderposition__isnull=True),
|
||||
'forbidden': self.get_queryset().filter(orderposition__isnull=False).distinct(),
|
||||
})
|
||||
elif request.POST.get('action') == 'delete_confirm':
|
||||
for obj in self.objects:
|
||||
for obj in self.get_queryset():
|
||||
if obj.allow_delete():
|
||||
CartPosition.objects.filter(addon_to__subevent=obj).delete()
|
||||
obj.cartposition_set.all().delete()
|
||||
@@ -899,3 +918,537 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
|
||||
permission = 'can_change_settings'
|
||||
form_class = SubEventBulkEditForm
|
||||
template_name = 'pretixcontrol/subevents/bulk_edit.html'
|
||||
context_object_name = 'subevent'
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().prefetch_related(None).order_by()
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return HttpResponse(status=405)
|
||||
|
||||
@cached_property
|
||||
def cached_num(self):
|
||||
return self.get_queryset().count()
|
||||
|
||||
@cached_property
|
||||
def itemvar_forms(self):
|
||||
matches = defaultdict(list)
|
||||
for sei in SubEventItem.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).order_by().values('item', 'price', 'disabled').annotate(c=Count('*')):
|
||||
matches['item', sei['item']].append(sei)
|
||||
for sei in SubEventItemVariation.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).order_by().values('variation', 'price', 'disabled').annotate(c=Count('*')):
|
||||
matches['variation', sei['variation']].append(sei)
|
||||
total = self.cached_num
|
||||
|
||||
formlist = []
|
||||
for i in self.request.event.items.filter(active=True).prefetch_related('variations'):
|
||||
if i.has_variations:
|
||||
for v in i.variations.all():
|
||||
m = matches['variation', v.pk]
|
||||
if m and len(m) == 1 and m[0]['c'] == total:
|
||||
inst = SubEventItemVariation(variation=v, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||
else:
|
||||
inst = SubEventItemVariation(variation=v)
|
||||
formlist.append(SubEventItemVariationForm(
|
||||
prefix='itemvar-{}'.format(v.pk),
|
||||
item=i, variation=v,
|
||||
instance=inst,
|
||||
data=(self.request.POST if self.is_submitted else None)
|
||||
))
|
||||
else:
|
||||
m = matches['item', i.pk]
|
||||
if m and len(m) == 1 and m[0]['c'] == total:
|
||||
inst = SubEventItem(item=i, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||
else:
|
||||
inst = SubEventItem(item=i)
|
||||
formlist.append(SubEventItemForm(
|
||||
prefix='item-{}'.format(i.pk),
|
||||
item=i,
|
||||
instance=inst,
|
||||
data=(self.request.POST if self.is_submitted else None)
|
||||
))
|
||||
return formlist
|
||||
|
||||
@cached_property
|
||||
def meta_forms(self):
|
||||
matches = defaultdict(list)
|
||||
for smv in SubEventMetaValue.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).order_by().values('property', 'value').annotate(c=Count('*')):
|
||||
matches[smv['property']].append(smv)
|
||||
total = self.cached_num
|
||||
|
||||
formlist = []
|
||||
|
||||
if not hasattr(self, '_default_meta'):
|
||||
self._default_meta = self.request.event.meta_data
|
||||
|
||||
for p in self.request.organizer.meta_properties.all():
|
||||
inst = SubEventMetaValue(property=p)
|
||||
if len(matches[p.id]) == 1 and matches[p.id][0]['c'] == total:
|
||||
inst.value = matches[p.id][0]['value']
|
||||
formlist.append(SubEventMetaValueForm(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
default=self._default_meta.get(p.name, ''),
|
||||
instance=inst,
|
||||
data=(self.request.POST if self.is_submitted else None)
|
||||
))
|
||||
return formlist
|
||||
|
||||
@cached_property
|
||||
def quota_formset(self):
|
||||
extra = 0
|
||||
kwargs = {}
|
||||
|
||||
if self.sampled_quotas is not None:
|
||||
kwargs['instance'] = self.get_queryset()[0]
|
||||
|
||||
formsetclass = inlineformset_factory(
|
||||
SubEvent, Quota,
|
||||
form=QuotaForm, formset=QuotaFormSet, min_num=0, validate_min=False,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
return formsetclass(
|
||||
self.request.POST if self.is_submitted else None,
|
||||
event=self.request.event, **kwargs
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def list_formset(self):
|
||||
extra = 0
|
||||
kwargs = {}
|
||||
|
||||
if self.sampled_lists is not None:
|
||||
kwargs['instance'] = self.get_queryset()[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
formsetclass = inlineformset_factory(
|
||||
SubEvent, CheckinList,
|
||||
form=SimpleCheckinListForm, formset=CheckinListFormSet, min_num=0, validate_min=False,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
return formsetclass(
|
||||
self.request.POST if self.is_submitted else None,
|
||||
event=self.request.event, **kwargs
|
||||
)
|
||||
|
||||
def save_list_formset(self, log_entries):
|
||||
if not self.list_formset.has_changed() or self.sampled_lists is None:
|
||||
return
|
||||
qidx = 0
|
||||
subevents = list(self.get_queryset().prefetch_related('checkinlist_set'))
|
||||
to_save_products = []
|
||||
to_save_gates = []
|
||||
|
||||
for f in self.list_formset.forms:
|
||||
if self.list_formset._should_delete_form(f) and f in self.list_formset.extra_forms:
|
||||
continue
|
||||
|
||||
if self.list_formset._should_delete_form(f):
|
||||
for se in subevents:
|
||||
q = list(se.checkinlist_set.all())[qidx]
|
||||
log_entries += [
|
||||
q.log_action(action='pretix.event.checkinlist.deleted', user=self.request.user, save=False),
|
||||
]
|
||||
q.delete()
|
||||
elif f in self.list_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
q = copy.copy(f.instance)
|
||||
q.pk = None
|
||||
q.subevent = se
|
||||
q.event = self.request.event
|
||||
q.save()
|
||||
for _i in f.cleaned_data.get('limit_products', []):
|
||||
to_save_products.append(CheckinList.limit_products.through(checkinlist_id=q.pk, item_id=_i.pk))
|
||||
for _i in f.cleaned_data.get('gates', []):
|
||||
to_save_gates.append(CheckinList.gates.through(checkinlist_id=q.pk, gate_id=_i.pk))
|
||||
change_data['id'] = q.pk
|
||||
log_entries.append(
|
||||
q.log_action(action='pretix.event.checkinlist.added', user=self.request.user,
|
||||
data=change_data, save=False)
|
||||
)
|
||||
else:
|
||||
if f.changed_data:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
q = list(se.checkinlist_set.all())[qidx]
|
||||
for fname in ('name', 'all_products', 'include_pending', 'allow_entry_after_exit'):
|
||||
setattr(q, fname, f.cleaned_data.get(fname))
|
||||
q.save()
|
||||
if 'limit_products' in f.changed_data:
|
||||
q.limit_products.set(f.cleaned_data.get('limit_products', []))
|
||||
if 'gates' in f.changed_data:
|
||||
q.gates.set(f.cleaned_data.get('limit_products', []))
|
||||
log_entries.append(
|
||||
q.log_action(action='pretix.event.checkinlist.changed', user=self.request.user,
|
||||
data=change_data, save=False)
|
||||
)
|
||||
qidx += 1
|
||||
if to_save_products:
|
||||
CheckinList.limit_products.through.objects.bulk_create(to_save_products)
|
||||
if to_save_gates:
|
||||
CheckinList.gates.through.objects.bulk_create(to_save_gates)
|
||||
|
||||
def save_quota_formset(self, log_entries):
|
||||
if not self.quota_formset.has_changed():
|
||||
return
|
||||
qidx = 0
|
||||
subevents = list(self.get_queryset().prefetch_related('quotas'))
|
||||
to_save_items = []
|
||||
to_save_variations = []
|
||||
to_delete_quota_ids = []
|
||||
|
||||
if self.sampled_quotas is None:
|
||||
if len(self.quota_formset.forms) == 0:
|
||||
return
|
||||
else:
|
||||
for se in subevents:
|
||||
for q in se.quotas.all():
|
||||
to_delete_quota_ids.append(q.pk)
|
||||
log_entries += [
|
||||
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||
'id': q.pk
|
||||
}, save=False)
|
||||
]
|
||||
|
||||
if to_delete_quota_ids:
|
||||
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||
|
||||
for f in self.quota_formset.forms:
|
||||
if self.quota_formset._should_delete_form(f) and f in self.quota_formset.extra_forms:
|
||||
continue
|
||||
|
||||
selected_items = set(list(self.request.event.items.filter(id__in=[
|
||||
i.split('-')[0] for i in f.cleaned_data.get('itemvars', [])
|
||||
])))
|
||||
selected_variations = list(ItemVariation.objects.filter(item__event=self.request.event, id__in=[
|
||||
i.split('-')[1] for i in f.cleaned_data.get('itemvars', []) if '-' in i
|
||||
]))
|
||||
|
||||
if self.quota_formset._should_delete_form(f):
|
||||
for se in subevents:
|
||||
q = list(se.quotas.all())[qidx]
|
||||
log_entries += [
|
||||
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||
'id': q.pk
|
||||
}, save=False)
|
||||
]
|
||||
q.delete()
|
||||
elif f in self.quota_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
q = copy.copy(f.instance)
|
||||
q.pk = None
|
||||
q.subevent = se
|
||||
q.event = self.request.event
|
||||
q.save(clear_cache=False)
|
||||
for _i in selected_items:
|
||||
to_save_items.append(Quota.items.through(quota_id=q.pk, item_id=_i.pk))
|
||||
for _i in selected_variations:
|
||||
to_save_variations.append(Quota.variations.through(quota_id=q.pk, itemvariation_id=_i.pk))
|
||||
|
||||
change_data['id'] = q.pk
|
||||
log_entries.append(
|
||||
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||
data=change_data, save=False)
|
||||
)
|
||||
log_entries.append(
|
||||
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data,
|
||||
save=False)
|
||||
)
|
||||
else:
|
||||
if f.changed_data:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
q = list(se.quotas.all())[qidx]
|
||||
for fname in ('size', 'name', 'release_after_exit'):
|
||||
setattr(q, fname, f.cleaned_data.get(fname))
|
||||
q.save(clear_cache=False)
|
||||
if 'itemvar' in f.changed_data:
|
||||
q.items.set(selected_items)
|
||||
q.variations.set(selected_variations)
|
||||
log_entries.append(
|
||||
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||
data=change_data, save=False)
|
||||
)
|
||||
qidx += 1
|
||||
if to_save_items:
|
||||
Quota.items.through.objects.bulk_create(to_save_items)
|
||||
if to_save_variations:
|
||||
Quota.variations.through.objects.bulk_create(to_save_variations)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['subevents'] = self.get_queryset()
|
||||
ctx['filter_form'] = self.filter_form
|
||||
ctx['sampled_quotas'] = self.sampled_quotas
|
||||
ctx['sampled_lists'] = self.sampled_lists
|
||||
ctx['formset'] = self.quota_formset
|
||||
ctx['cl_formset'] = self.list_formset
|
||||
ctx['itemvar_forms'] = self.itemvar_forms
|
||||
ctx['bulk_selected'] = self.request.POST.getlist("_bulk")
|
||||
ctx['meta_forms'] = self.meta_forms
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def sampled_quotas(self):
|
||||
all_quotas = Quota.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).annotate(
|
||||
item_list=GroupConcat('items__id'),
|
||||
var_list=GroupConcat('variations__id'),
|
||||
).values(
|
||||
'item_list', 'var_list',
|
||||
*(f.name for f in Quota._meta.fields if f.name not in (
|
||||
'id', 'event', 'items', 'variations', 'cached_availability_state', 'cached_availability_number',
|
||||
'cached_availability_paid_orders', 'cached_availability_time', 'closed',
|
||||
))
|
||||
).order_by('subevent_id')
|
||||
|
||||
if not all_quotas:
|
||||
return Quota.objects.none()
|
||||
|
||||
quotas_by_subevent = defaultdict(list)
|
||||
for q in all_quotas:
|
||||
quotas_by_subevent[q.pop('subevent')].append(q)
|
||||
|
||||
prev = None
|
||||
for se in self.get_queryset():
|
||||
if se.pk not in quotas_by_subevent:
|
||||
return None
|
||||
|
||||
if prev is None:
|
||||
prev = quotas_by_subevent[se.pk]
|
||||
|
||||
if quotas_by_subevent[se.pk] != prev:
|
||||
return None
|
||||
return se.quotas.all()
|
||||
|
||||
@cached_property
|
||||
def sampled_lists(self):
|
||||
all_lists = CheckinList.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).annotate(
|
||||
item_list=GroupConcat('limit_products__id'),
|
||||
gates_list=GroupConcat('gates__id'),
|
||||
).values(
|
||||
'item_list', 'gates_list',
|
||||
*(f.name for f in CheckinList._meta.fields if f.name not in (
|
||||
'id', 'event', 'limit_products', 'gates',
|
||||
))
|
||||
).order_by('subevent_id')
|
||||
|
||||
if not all_lists:
|
||||
return SubEvent.objects.none()
|
||||
|
||||
lists_by_subevent = defaultdict(list)
|
||||
for cl in all_lists:
|
||||
lists_by_subevent[cl.pop('subevent')].append(cl)
|
||||
|
||||
prev = None
|
||||
for se in self.get_queryset():
|
||||
if se.pk not in lists_by_subevent:
|
||||
return None
|
||||
|
||||
if prev is None:
|
||||
prev = lists_by_subevent[se.pk]
|
||||
|
||||
if lists_by_subevent[se.pk] != prev:
|
||||
return None
|
||||
return se.checkinlist_set.all()
|
||||
|
||||
@cached_property
|
||||
def is_submitted(self):
|
||||
# Usually, django considers a form "bound" / "submitted" on every POST request. However, this view is always
|
||||
# called with POST method, even if just to pass the selection of objects to work on, so we want to modify
|
||||
# that behaviour
|
||||
return '_bulk' in self.request.POST
|
||||
|
||||
def get_form_kwargs(self):
|
||||
initial = {}
|
||||
mixed_values = set()
|
||||
qs = self.get_queryset()
|
||||
|
||||
qs = qs.annotate(
|
||||
**{
|
||||
# TODO: Once we're on Django 3.2, pass a tzinfo parameter
|
||||
# Before Django 3.2, it uses the current timezone, which is hopefully fine
|
||||
# as well in all cases we are concerned about
|
||||
# See also: https://code.djangoproject.com/ticket/31948
|
||||
k + '_day': TruncDate(k)
|
||||
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||
},
|
||||
**{
|
||||
k + '_time': TruncTime(k)
|
||||
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||
},
|
||||
)
|
||||
|
||||
fields = {
|
||||
'name',
|
||||
'location',
|
||||
'frontpage_text',
|
||||
'geo_lat',
|
||||
'geo_lon',
|
||||
'is_public',
|
||||
'active',
|
||||
'date_from_day',
|
||||
'date_from_time',
|
||||
'date_to_day',
|
||||
'date_to_time',
|
||||
'date_admission_day',
|
||||
'date_admission_time',
|
||||
'presale_start_day',
|
||||
'presale_start_time',
|
||||
'presale_end_day',
|
||||
'presale_end_time',
|
||||
}
|
||||
for k in fields:
|
||||
existing_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
|
||||
if len(existing_values) == 1:
|
||||
initial[k] = existing_values[0][k]
|
||||
elif len(existing_values) > 1:
|
||||
mixed_values.add(k)
|
||||
initial[k] = None
|
||||
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['event'] = self.request.event
|
||||
kwargs['prefix'] = 'bulkedit'
|
||||
kwargs['initial'] = initial
|
||||
kwargs['queryset'] = self.get_queryset()
|
||||
kwargs['mixed_values'] = mixed_values
|
||||
if not self.is_submitted:
|
||||
kwargs['data'] = None
|
||||
kwargs['files'] = None
|
||||
return kwargs
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
is_valid = (
|
||||
self.is_submitted and
|
||||
form.is_valid() and
|
||||
self.quota_formset.is_valid() and
|
||||
(not self.list_formset or self.list_formset.is_valid()) and
|
||||
all(f.is_valid() for f in self.itemvar_forms)and
|
||||
all(f.is_valid() for f in self.meta_forms)
|
||||
)
|
||||
if is_valid:
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
if self.is_submitted:
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return self.form_invalid(form)
|
||||
|
||||
def save_meta(self):
|
||||
for f in self.meta_forms:
|
||||
if f.prefix + 'value' not in self.request.POST.getlist('_bulk'):
|
||||
continue
|
||||
|
||||
if f.cleaned_data.get('value'):
|
||||
for obj in self.get_queryset():
|
||||
SubEventMetaValue.objects.update_or_create(
|
||||
property=f.instance.property,
|
||||
subevent=obj,
|
||||
defaults={
|
||||
'value': f.cleaned_data['value']
|
||||
}
|
||||
)
|
||||
else:
|
||||
SubEventMetaValue.objects.filter(
|
||||
property=f.instance.property,
|
||||
subevent__in=self.get_queryset()
|
||||
).delete()
|
||||
|
||||
def save_itemvars(self):
|
||||
for f in self.itemvar_forms:
|
||||
u = {}
|
||||
if f.prefix + 'price' in self.request.POST.getlist('_bulk'):
|
||||
u['price'] = f.cleaned_data.get('price')
|
||||
if f.prefix + 'disabled' in self.request.POST.getlist('_bulk'):
|
||||
u['disabled'] = f.cleaned_data.get('disabled')
|
||||
|
||||
if not u:
|
||||
continue
|
||||
|
||||
if isinstance(f, SubEventItemForm):
|
||||
if u.get('price') is None and not u.get('disabled'):
|
||||
SubEventItem.objects.filter(
|
||||
subevent__in=self.get_queryset(),
|
||||
item=f.instance.item,
|
||||
).delete()
|
||||
else:
|
||||
for obj in self.get_queryset():
|
||||
SubEventItem.objects.update_or_create(
|
||||
subevent=obj,
|
||||
item=f.instance.item,
|
||||
defaults=u
|
||||
)
|
||||
elif isinstance(f, SubEventItemVariationForm):
|
||||
if u.get('price') is None and not u.get('disabled'):
|
||||
SubEventItemVariation.objects.filter(
|
||||
subevent__in=self.get_queryset(),
|
||||
variation=f.instance.variation,
|
||||
).delete()
|
||||
else:
|
||||
for obj in self.get_queryset():
|
||||
SubEventItemVariation.objects.update_or_create(
|
||||
subevent=obj,
|
||||
variation=f.instance.variation,
|
||||
defaults=u
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def form_valid(self, form):
|
||||
log_entries = []
|
||||
|
||||
# Main form
|
||||
form.save()
|
||||
data = {
|
||||
k: v for k, v in form.cleaned_data.items() if k in form.changed_data
|
||||
}
|
||||
data['_raw_bulk_data'] = self.request.POST.dict()
|
||||
for obj in self.get_queryset():
|
||||
log_entries.append(
|
||||
obj.log_action('pretix.subevent.changed', data=data, user=self.request.user, save=False)
|
||||
)
|
||||
|
||||
# Formsets
|
||||
if '__quotas' in self.request.POST.getlist('_bulk'):
|
||||
self.save_quota_formset(log_entries)
|
||||
if '__checkinlists' in self.request.POST.getlist('_bulk'):
|
||||
self.save_list_formset(log_entries)
|
||||
|
||||
self.save_itemvars()
|
||||
self.save_meta()
|
||||
|
||||
if connections['default'].features.can_return_rows_from_bulk_insert:
|
||||
LogEntry.objects.bulk_create(log_entries, batch_size=200)
|
||||
LogEntry.bulk_postprocess(log_entries)
|
||||
else:
|
||||
for le in log_entries:
|
||||
le.save()
|
||||
LogEntry.bulk_postprocess(log_entries)
|
||||
|
||||
self.request.event.cache.clear()
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
@@ -67,7 +67,8 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
|
||||
headers = [
|
||||
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
|
||||
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat')
|
||||
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat'),
|
||||
_('Comment')
|
||||
]
|
||||
writer.writerow(headers)
|
||||
|
||||
@@ -92,7 +93,8 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
v.tag,
|
||||
str(v.redeemed),
|
||||
str(v.max_usages),
|
||||
str(v.seat) if v.seat else ""
|
||||
str(v.seat) if v.seat else "",
|
||||
str(v.comment) if v.comment else ""
|
||||
]
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-27 17:45+0000\n"
|
||||
"PO-Revision-Date: 2020-12-14 10:00+0000\n"
|
||||
"PO-Revision-Date: 2021-02-16 06:00+0000\n"
|
||||
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
|
||||
">\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
|
||||
"\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 3.10.3\n"
|
||||
"X-Generator: Weblate 4.4.2\n"
|
||||
|
||||
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
||||
@@ -218,17 +218,17 @@ msgstr ""
|
||||
#: pretix/api/serializers/organizer.py:142
|
||||
#: pretix/control/views/organizer.py:539
|
||||
msgid "pretix account invitation"
|
||||
msgstr ""
|
||||
msgstr "pozvánka k pretix účtu"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:164
|
||||
#: pretix/control/views/organizer.py:638
|
||||
msgid "This user already has been invited for this team."
|
||||
msgstr "Tento uživatel byl již pozván do této skupiny."
|
||||
msgstr "Tento uživatel byl již pozván do tohoto týmu."
|
||||
|
||||
#: pretix/api/serializers/organizer.py:180
|
||||
#: pretix/control/views/organizer.py:655
|
||||
msgid "This user already has permissions for this team."
|
||||
msgstr ""
|
||||
msgstr "Tento uživatel již má nastavena práva pro tento tým."
|
||||
|
||||
#: pretix/api/views/oauth.py:85 pretix/control/logdisplay.py:356
|
||||
#, python-brace-format
|
||||
@@ -240,7 +240,7 @@ msgstr ""
|
||||
#: pretix/api/views/order.py:460 pretix/control/views/orders.py:1186
|
||||
#: pretix/presale/views/order.py:663 pretix/presale/views/order.py:728
|
||||
msgid "You cannot generate an invoice for this order."
|
||||
msgstr ""
|
||||
msgstr "Nemůžete vygenerovat fakturu pro tuto objednávku."
|
||||
|
||||
#: pretix/api/views/order.py:465 pretix/control/views/orders.py:1188
|
||||
#: pretix/presale/views/order.py:665 pretix/presale/views/order.py:730
|
||||
@@ -1868,7 +1868,7 @@ msgstr "Časové pásmo"
|
||||
|
||||
#: pretix/base/models/auth.py:108
|
||||
msgid "Two-factor authentication is required to log in"
|
||||
msgstr ""
|
||||
msgstr "Pro přihlášení je vyžadována dvoufaktorová autentizace"
|
||||
|
||||
#: pretix/base/models/auth.py:112
|
||||
msgid "Receive notifications according to my settings below"
|
||||
@@ -1991,7 +1991,7 @@ msgstr ""
|
||||
#: pretix/base/models/devices.py:91
|
||||
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
||||
msgid "Gate"
|
||||
msgstr ""
|
||||
msgstr "Brána"
|
||||
|
||||
#: pretix/base/models/devices.py:109
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:38
|
||||
@@ -2008,7 +2008,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/event.py:46
|
||||
msgid "The end of the event has to be later than its start."
|
||||
msgstr ""
|
||||
msgstr "Konec události musí být pozdější než začátek."
|
||||
|
||||
#: pretix/base/models/event.py:353
|
||||
msgid ""
|
||||
@@ -2030,7 +2030,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/event.py:369
|
||||
msgid "Shop is live"
|
||||
msgstr ""
|
||||
msgstr "Obchod je spuštěný"
|
||||
|
||||
#: pretix/base/models/event.py:371
|
||||
msgid "Event currency"
|
||||
@@ -2057,7 +2057,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/event.py:380 pretix/base/models/event.py:1106
|
||||
msgid "Show in lists"
|
||||
msgstr ""
|
||||
msgstr "Zobrazit v seznamu"
|
||||
|
||||
#: pretix/base/models/event.py:381
|
||||
msgid ""
|
||||
@@ -2068,7 +2068,7 @@ msgstr ""
|
||||
#: pretix/base/models/event.py:384 pretix/base/models/event.py:1120
|
||||
#: pretix/control/forms/subevents.py:75
|
||||
msgid "End of presale"
|
||||
msgstr ""
|
||||
msgstr "Konec předprodeje"
|
||||
|
||||
#: pretix/base/models/event.py:385 pretix/base/models/event.py:1121
|
||||
#: pretix/control/forms/subevents.py:76
|
||||
@@ -2080,7 +2080,7 @@ msgstr ""
|
||||
#: pretix/base/models/event.py:390 pretix/base/models/event.py:1126
|
||||
#: pretix/control/forms/subevents.py:69
|
||||
msgid "Start of presale"
|
||||
msgstr ""
|
||||
msgstr "Začátek předprodeje"
|
||||
|
||||
#: pretix/base/models/event.py:391 pretix/base/models/event.py:1127
|
||||
#: pretix/control/forms/subevents.py:70
|
||||
@@ -2101,13 +2101,13 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/event.py:416 pretix/control/navigation.py:44
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Zásuvné moduly"
|
||||
|
||||
#: pretix/base/models/event.py:419
|
||||
#: pretix/control/templates/pretixcontrol/event/index.html:143
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:865
|
||||
msgid "Internal comment"
|
||||
msgstr ""
|
||||
msgstr "Interní poznámka"
|
||||
|
||||
#: pretix/base/models/event.py:423 pretix/control/forms/event.py:211
|
||||
#: pretix/control/forms/filter.py:988
|
||||
@@ -2133,7 +2133,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/search/orders.html:44
|
||||
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
|
||||
msgid "Event"
|
||||
msgstr ""
|
||||
msgstr "Událost"
|
||||
|
||||
#: pretix/base/models/event.py:437 pretix/control/navigation.py:305
|
||||
#: pretix/control/navigation.py:407
|
||||
@@ -2144,7 +2144,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:37
|
||||
#: pretix/control/views/organizer.py:1205
|
||||
msgid "Events"
|
||||
msgstr ""
|
||||
msgstr "Události"
|
||||
|
||||
#: pretix/base/models/event.py:951
|
||||
msgid ""
|
||||
@@ -2182,7 +2182,7 @@ msgstr ""
|
||||
#: pretix/control/forms/filter.py:1250
|
||||
#: pretix/control/templates/pretixcontrol/users/index.html:46
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
msgstr "Aktivní"
|
||||
|
||||
#: pretix/base/models/event.py:1103
|
||||
msgid ""
|
||||
@@ -6399,7 +6399,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py:1694
|
||||
msgid "Primary color"
|
||||
msgstr ""
|
||||
msgstr "Hlavní barva"
|
||||
|
||||
#: pretix/base/settings.py:1714
|
||||
msgid "Accent color for success"
|
||||
@@ -9737,7 +9737,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:3
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:5
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
msgstr "Nástěnka"
|
||||
|
||||
#: pretix/control/navigation.py:28 pretix/control/navigation.py:329
|
||||
#: pretix/control/navigation.py:424
|
||||
@@ -10328,7 +10328,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:123
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:80
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Filtrovat"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:52
|
||||
msgid "No attendee record was found."
|
||||
@@ -10558,11 +10558,11 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:9
|
||||
msgid "Go to event"
|
||||
msgstr ""
|
||||
msgstr "Jít na událost"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:15
|
||||
msgid "Your upcoming events"
|
||||
msgstr ""
|
||||
msgstr "Vaše nadcházející události"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:20
|
||||
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
|
||||
@@ -10571,11 +10571,11 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:57
|
||||
#: pretix/control/templates/pretixcontrol/organizers/detail.html:12
|
||||
msgid "Create a new event"
|
||||
msgstr ""
|
||||
msgstr "Vytvořit novou událost"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:39
|
||||
msgid "View all upcoming events"
|
||||
msgstr ""
|
||||
msgstr "Zobrazit všechny nadcházející události"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:44
|
||||
msgid "Your most recent events"
|
||||
@@ -13546,7 +13546,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:98
|
||||
msgid "Remove filter"
|
||||
msgstr ""
|
||||
msgstr "Odstranit filtr"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:116
|
||||
msgid "Order paid / total"
|
||||
@@ -13618,7 +13618,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:69
|
||||
#: pretix/plugins/reports/exporters.py:259
|
||||
msgid "Purchased"
|
||||
msgstr ""
|
||||
msgstr "Zakoupeno"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:178
|
||||
msgid ""
|
||||
@@ -13679,7 +13679,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
||||
msgid "Download pretixSCAN"
|
||||
msgstr ""
|
||||
msgstr "Stáhnout pretixSCAN"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:18
|
||||
msgid ""
|
||||
@@ -17735,12 +17735,12 @@ msgstr ""
|
||||
#: pretix/plugins/reports/exporters.py:120
|
||||
#, python-format
|
||||
msgid "Page %d"
|
||||
msgstr ""
|
||||
msgstr "Strana %d"
|
||||
|
||||
#: pretix/plugins/reports/exporters.py:122
|
||||
#, python-format
|
||||
msgid "Created: %s"
|
||||
msgstr ""
|
||||
msgstr "Vytvořeno: %s"
|
||||
|
||||
#: pretix/plugins/reports/exporters.py:162
|
||||
msgid "Order overview (PDF)"
|
||||
@@ -17810,10 +17810,8 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/reports/exporters.py:668
|
||||
#: pretix/plugins/reports/exporters.py:713
|
||||
#, fuzzy
|
||||
#| msgid "Country"
|
||||
msgid "Country code"
|
||||
msgstr "Stát"
|
||||
msgstr "Kód země"
|
||||
|
||||
#: pretix/plugins/returnurl/__init__.py:9
|
||||
#: pretix/plugins/returnurl/__init__.py:12
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-12-22 11:05+0000\n"
|
||||
"PO-Revision-Date: 2021-01-26 03:00+0000\n"
|
||||
"PO-Revision-Date: 2021-02-15 05:00+0000\n"
|
||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"fi/>\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 3.10.3\n"
|
||||
"X-Generator: Weblate 4.4.2\n"
|
||||
|
||||
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
||||
@@ -3136,10 +3136,8 @@ msgstr "Ulkoinen"
|
||||
|
||||
#: pretix/base/models/orders.py:1720
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:191
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund reason"
|
||||
msgstr "Hyvitä tilaus"
|
||||
msgstr "Hyvityksen syy"
|
||||
|
||||
#: pretix/base/models/orders.py:1721
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:192
|
||||
@@ -4537,10 +4535,8 @@ msgstr ""
|
||||
|
||||
#: pretix/base/services/cancelevent.py:200
|
||||
#: pretix/base/services/cancelevent.py:258
|
||||
#, fuzzy
|
||||
#| msgid "Event created"
|
||||
msgid "Event canceled"
|
||||
msgstr "Tapahtuma luotu"
|
||||
msgstr "Tapahtuma peruttu"
|
||||
|
||||
#: pretix/base/services/cart.py:52 pretix/base/services/orders.py:73
|
||||
msgid ""
|
||||
@@ -7817,16 +7813,12 @@ msgid "Order placed before"
|
||||
msgstr "Tilattu jälkeen"
|
||||
|
||||
#: pretix/control/forms/filter.py:468
|
||||
#, fuzzy
|
||||
#| msgid "Order payments and refunds"
|
||||
msgid "Minimal sum of payments and refunds"
|
||||
msgstr "Tilauksen maksut ja palautukset"
|
||||
msgstr "Maksujen ja palautusten vähimmäissumma"
|
||||
|
||||
#: pretix/control/forms/filter.py:473
|
||||
#, fuzzy
|
||||
#| msgid "Order payments and refunds"
|
||||
msgid "Maximal sum of payments and refunds"
|
||||
msgstr "Tilauksen maksut ja palautukset"
|
||||
msgstr "Tilausten ja maksujen enimmäissumma"
|
||||
|
||||
#: pretix/control/forms/filter.py:515 pretix/control/forms/filter.py:520
|
||||
#: pretix/control/forms/filter.py:546 pretix/control/forms/filter.py:551
|
||||
@@ -8487,11 +8479,9 @@ msgid "Automatically refund money if possible"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/orders.py:619
|
||||
#, fuzzy
|
||||
#| msgid "This payment method does not support automatic refunds."
|
||||
msgid ""
|
||||
"Create manual refund if the payment method does not support automatic refunds"
|
||||
msgstr "Tätä maksutapa ei tue automaattisia hyvityksiä."
|
||||
msgstr "Luo manuaalinen hyvitys, jos maksutapa ei tue automaattista hyvitystä"
|
||||
|
||||
#: pretix/control/forms/orders.py:623
|
||||
msgid ""
|
||||
@@ -12876,10 +12866,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:104
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund for overpayment"
|
||||
msgstr "Hyvitä tilaus"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:106
|
||||
#, python-format
|
||||
@@ -13262,16 +13250,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:30
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund to original payment method"
|
||||
msgstr "Hyvitä tilaus"
|
||||
msgstr "Hyvitä alkuperäiseen maksutapaan"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:36
|
||||
#, fuzzy
|
||||
#| msgid "Payment method"
|
||||
msgid "Payment details"
|
||||
msgstr "Maksutapa"
|
||||
msgstr "Maksun tiedot"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:37
|
||||
msgid "Amount not refunded"
|
||||
@@ -13279,10 +13263,8 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:38
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:85
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund amount"
|
||||
msgstr "Hyvitä tilaus"
|
||||
msgstr "Hyvitettävä summa"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:66
|
||||
msgid "Full amount"
|
||||
@@ -13293,16 +13275,12 @@ msgid "This payment method does not support automatic refunds."
|
||||
msgstr "Tätä maksutapa ei tue automaattisia hyvityksiä."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:78
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund to a different payment method"
|
||||
msgstr "Hyvitä tilaus"
|
||||
msgstr "Hyvitä eri maksutavalle"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:84
|
||||
#, fuzzy
|
||||
#| msgid "Question options"
|
||||
msgid "Recipient / options"
|
||||
msgstr "Kysymysvaihtoehdot"
|
||||
msgstr "Vastaanottaja / vaihtoehdot"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:112
|
||||
msgid "Transfer to other order"
|
||||
@@ -14500,16 +14478,12 @@ msgid "Times"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:339
|
||||
#, fuzzy
|
||||
#| msgid "Start of presale"
|
||||
msgid "Start of first slot"
|
||||
msgstr "Ennakkomyynnin alku"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:345
|
||||
#, fuzzy
|
||||
#| msgid "End of presale"
|
||||
msgid "End of time slots"
|
||||
msgstr "Ennakkomyynnin loppu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:351
|
||||
msgid "Length of slots"
|
||||
@@ -14525,11 +14499,8 @@ msgid "Break between slots"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:370
|
||||
#, fuzzy
|
||||
#| msgctxt "payment_state"
|
||||
#| msgid "created"
|
||||
msgid "Create"
|
||||
msgstr "luotu"
|
||||
msgstr "Luo"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:377
|
||||
msgid "Add a single time slot"
|
||||
@@ -16187,10 +16158,8 @@ msgid "The selected exporter was not found."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/views/orders.py:2117
|
||||
#, fuzzy
|
||||
#| msgid "We are processing your request …"
|
||||
msgid "There was a problem processing your input:"
|
||||
msgstr "Pyyntöäsi käsitellään …"
|
||||
msgstr "Ongelma syötteen käsittelyssä:"
|
||||
|
||||
#: pretix/control/views/orders.py:2213
|
||||
msgid "All orders have been canceled."
|
||||
@@ -16949,17 +16918,12 @@ msgid "Can only create a bank transfer refund from an existing payment."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py:349
|
||||
#, fuzzy
|
||||
#| msgid "Price (optional)"
|
||||
msgid "BIC (optional)"
|
||||
msgstr "Hinta (valinnainen)"
|
||||
msgstr "BIC (valinnainen)"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py:388
|
||||
#, fuzzy
|
||||
#| msgid "Your order has been placed successfully. See below for details."
|
||||
msgid "Your input was invalid, please see below for details."
|
||||
msgstr ""
|
||||
"Tilauksesi on vastaanotettu onnistuneesti. Katso tilauksen tiedot alta."
|
||||
msgstr "Epäkelpo syöte, katso tiedot alta."
|
||||
|
||||
#: pretix/plugins/banktransfer/refund_export.py:25
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:5
|
||||
@@ -20213,10 +20177,8 @@ msgid "You chose an invalid cancellation fee."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/views/order.py:869
|
||||
#, fuzzy
|
||||
#| msgid "Any customer"
|
||||
msgid "Canceled by customer"
|
||||
msgstr "Kaikki asiakkaat"
|
||||
msgstr "Asiakkaan peruuttama"
|
||||
|
||||
#: pretix/presale/views/order.py:880
|
||||
msgid "The cancellation has been requested."
|
||||
|
||||
@@ -720,6 +720,8 @@ BOOTSTRAP3 = {
|
||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
||||
'bulkedit': 'pretix.control.forms.renderers.BulkEditFieldRenderer',
|
||||
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
|
||||
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
||||
},
|
||||
}
|
||||
@@ -758,3 +760,6 @@ OAUTH2_PROVIDER = {
|
||||
COUNTRIES_OVERRIDE = {
|
||||
'XK': _('Kosovo'),
|
||||
}
|
||||
|
||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 25000
|
||||
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
@@ -71,6 +71,7 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
form_handlers($("body"));
|
||||
setup_collapsible_details($("body"));
|
||||
} else if (c.length > 0) {
|
||||
// This is some kind of 500/404/403 page, show it in an overlay
|
||||
$("body").data('ajaxing', false);
|
||||
@@ -146,11 +147,20 @@ function async_task_error(jqXHR, textStatus, errorThrown) {
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
// This is a failed form validation, let's just use it
|
||||
waitingDialog.hide();
|
||||
$("body").html(jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
form_handlers($("body"));
|
||||
|
||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||
$("#page-wrapper").html(respdom.find("#page-wrapper").html());
|
||||
form_handlers($("#page-wrapper"));
|
||||
setup_collapsible_details($("#page-wrapper"));
|
||||
} else {
|
||||
$("body").html(jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
form_handlers($("body"));
|
||||
setup_collapsible_details($("body"));
|
||||
}
|
||||
|
||||
} else if (c.length > 0) {
|
||||
waitingDialog.hide();
|
||||
ajaxErrDialog.show(c.first().html());
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/*global $ */
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
setup_collapsible_details = function (el) {
|
||||
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
|
||||
|
||||
$("details summary, details summary a[data-toggle=variations]").click(function (e) {
|
||||
el.find("details summary, details summary a[data-toggle=variations]").click(function (e) {
|
||||
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -44,7 +41,12 @@ $(function () {
|
||||
$detailsNotSummary = $details.children(':not(summary)');
|
||||
$details.prop('open', typeof $details.attr('open') == 'string');
|
||||
if (!$details.prop('open')) {
|
||||
$detailsNotSummary.hide();
|
||||
if ($details.find(".has-error, .alert-danger").length) {
|
||||
$details.addClass("details-open");
|
||||
$details.prop('open', true);
|
||||
} else {
|
||||
$detailsNotSummary.hide();
|
||||
}
|
||||
} else {
|
||||
$details.addClass("details-open");
|
||||
}
|
||||
@@ -55,4 +57,10 @@ $(function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
setup_collapsible_details($("body"));
|
||||
});
|
||||
|
||||
@@ -362,6 +362,9 @@ $(document).ready(function () {
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, time);
|
||||
}
|
||||
if (event.target.value === "custom") {
|
||||
this.$set(this.rule[this.operator], 2, 0);
|
||||
}
|
||||
},
|
||||
setTimeValue: function (val) {
|
||||
console.log(val);
|
||||
|
||||
@@ -546,6 +546,25 @@ var form_handlers = function (el) {
|
||||
);
|
||||
});
|
||||
|
||||
el.find(".bulk-edit-field-group").each(function () {
|
||||
var $checkbox = $(this).find("input[type=checkbox][name=_bulk]");
|
||||
var $content = $(this).find(".field-content");
|
||||
var $fields = $content.find("input, select, textarea, button");
|
||||
|
||||
var update = function () {
|
||||
var isChecked = $checkbox.prop("checked");
|
||||
$content.toggleClass("enabled", isChecked);
|
||||
$fields.attr("tabIndex", isChecked ? 0 : -1);
|
||||
}
|
||||
$content.on("focusin change click", function () {
|
||||
if ($checkbox.prop("checked")) return;
|
||||
$checkbox.prop("checked", true);
|
||||
update();
|
||||
});
|
||||
$checkbox.on('change', update)
|
||||
update();
|
||||
});
|
||||
|
||||
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
|
||||
questions_toggle_dependent();
|
||||
};
|
||||
@@ -563,7 +582,6 @@ $(function () {
|
||||
}
|
||||
);
|
||||
$("[data-formset]").on("formAdded", "div", function (event) {
|
||||
console.log("formAdded")
|
||||
form_handlers($(event.target));
|
||||
});
|
||||
$(document).on("click", ".variations .variations-select-all", function (e) {
|
||||
@@ -672,29 +690,104 @@ $(function () {
|
||||
// Tables with bulk selection, e.g. subevent list
|
||||
$("input[data-toggle-table]").each(function (ev) {
|
||||
var $toggle = $(this);
|
||||
|
||||
var update = function () {
|
||||
var all_true = true;
|
||||
var all_false = true;
|
||||
$toggle.closest("table").find("td:first-child input[type=checkbox]").each(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
all_false = false;
|
||||
} else {
|
||||
all_true = false;
|
||||
}
|
||||
});
|
||||
if (all_true) {
|
||||
$toggle.prop("checked", true).prop("indeterminate", false);
|
||||
} else if (all_false) {
|
||||
$toggle.prop("checked", false).prop("indeterminate", false);
|
||||
} else {
|
||||
$toggle.prop("checked", false).prop("indeterminate", true);
|
||||
var $table = $toggle.closest("table");
|
||||
var $selectAll = $table.find(".table-select-all");
|
||||
var $rows = $table.find("tbody tr");
|
||||
var $checkboxes = $rows.find("td:first-child input[type=checkbox]");
|
||||
var firstIndex, lastIndex, selectionChecked, onChangeSelectionHappened = false;
|
||||
var updateSelection = function(a, b, checked) {
|
||||
if (a > b) {
|
||||
//[a, b] = [b, a];// ES6 not ready yet for pretix
|
||||
var tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
for (var i = a; i <= b; i++) {
|
||||
var checkbox = $checkboxes.get(i);
|
||||
if (!checkbox.hasAttribute("data-inital")) checkbox.setAttribute("data-inital", checkbox.checked);
|
||||
if (checked === undefined || checked === null) checkbox.checked = checkbox.getAttribute("data-inital") === "true";
|
||||
else checkbox.checked = checked;
|
||||
}
|
||||
};
|
||||
var onChangeSelection = function(ev) {
|
||||
onChangeSelectionHappened = true;
|
||||
|
||||
$(this).closest("table").find("td:first-child input[type=checkbox]").change(update);
|
||||
$(this).change(function (ev) {
|
||||
$(this).closest("table").find("td:first-child input[type=checkbox]").prop("checked", $(this).prop("checked"));
|
||||
var row = ev.target.closest("tr");
|
||||
var currentIndex = 0;
|
||||
while(row = row.previousSibling) {
|
||||
if (row.tagName) currentIndex++;
|
||||
}
|
||||
var dCurrent = currentIndex - firstIndex;
|
||||
var dLast = lastIndex - firstIndex;
|
||||
if (dCurrent*dLast < 0) {
|
||||
// direction of selection changed => reset all previously selected
|
||||
updateSelection(lastIndex, firstIndex);
|
||||
}
|
||||
else if (Math.abs(dCurrent) < Math.abs(dLast)) {
|
||||
// selection distance decreased => reset unselected
|
||||
updateSelection(currentIndex, lastIndex);
|
||||
}
|
||||
lastIndex = currentIndex;
|
||||
updateSelection(firstIndex, currentIndex, selectionChecked);
|
||||
|
||||
ev.preventDefault();
|
||||
};
|
||||
$table.on("pointerdown", function(ev) {
|
||||
if (!ev.target.closest("td:first-child")) return;
|
||||
var row = ev.target.closest("tr");
|
||||
selectionChecked = !row.querySelector("td:first-child input").checked;
|
||||
|
||||
firstIndex = 0;
|
||||
while(row = row.previousSibling) {
|
||||
if (row.tagName) firstIndex++;
|
||||
}
|
||||
lastIndex = firstIndex;
|
||||
|
||||
ev.preventDefault();
|
||||
$rows.on("pointerenter", onChangeSelection);
|
||||
|
||||
$(document).one("pointerup", function(ev) {
|
||||
if (onChangeSelectionHappened) {
|
||||
ev.preventDefault();
|
||||
onChangeSelectionHappened = false;
|
||||
$checkboxes.removeAttr("data-inital");
|
||||
|
||||
update();
|
||||
}
|
||||
$rows.off("pointerenter", onChangeSelection);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var update = function () {
|
||||
var all_same;
|
||||
var checkboxes = $checkboxes.toArray();
|
||||
var i = checkboxes.length;
|
||||
while (i--) {
|
||||
if (all_same === undefined) {
|
||||
all_same = checkboxes[i].checked;
|
||||
continue;
|
||||
}
|
||||
if (all_same != checkboxes[i].checked) {
|
||||
$toggle.prop("checked", false).prop("indeterminate", true).trigger("change");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$toggle.prop("checked", all_same).prop("indeterminate", false).trigger("change");
|
||||
};
|
||||
|
||||
var debounceUpdate;
|
||||
$checkboxes.change(function() {
|
||||
//$(this).closest("tr").toggleClass("warning", this.checked);
|
||||
// when changing the $toggle’s checked-property, lots of change events
|
||||
// get triggered => debounce
|
||||
if (debounceUpdate) window.clearTimeout(debounceUpdate);
|
||||
debounceUpdate = window.setTimeout(update, 10);
|
||||
});
|
||||
$toggle.change(function (ev) {
|
||||
if (!this.indeterminate) $checkboxes.prop("checked", this.checked);//.trigger("change");
|
||||
$selectAll.toggleClass("hidden", !this.checked).prop("hidden", !this.checked);
|
||||
if (!this.checked) $selectAll.find("input").prop("checked", false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -613,3 +613,41 @@ table td > .checkbox input[type="checkbox"] {
|
||||
border-bottom: 1px solid $input-border;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
.batch-select-label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bulk-edit-field-group {
|
||||
.field-toggle {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
background: $gray-lighter;
|
||||
padding: 2px 8px 4px;
|
||||
border-top-left-radius: $border-radius-base;
|
||||
border-top-right-radius: $border-radius-base;
|
||||
margin-bottom: 0;
|
||||
input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
.field-content {
|
||||
border: 2px solid $gray-lighter;
|
||||
padding: 15px;
|
||||
opacity: 0.5;
|
||||
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
&.enabled {
|
||||
opacity: 1;
|
||||
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user