Compare commits

..
Author SHA1 Message Date
Felix Rindt fa372b3df0 match copy answer button to fitting position 2020-09-11 12:58:39 +02:00
91 changed files with 28722 additions and 33480 deletions
-2
View File
@@ -292,8 +292,6 @@ Flexible group sizes
If you want to give out discounted tickets to groups starting at a given size, but still billed per person, you can do so by creating a special **Group ticket** at the per-person price and set the **Minimum amount per order** option of the ticket to the minimal group size.
For more complex use cases, you can also use add-on products that can be chosen multiple times.
This way, your ticket can be bought an arbitrary number of times but no less than the given minimal amount per order.
Fixed group sizes
-1
View File
@@ -3,7 +3,6 @@ include README.rst
recursive-include pretix/static *
recursive-include pretix/static.dist *
recursive-include pretix/locale *
recursive-include pretix/helpers/locale *
recursive-include pretix/base/templates *
recursive-include pretix/control/templates *
recursive-include pretix/presale/templates *
+1 -1
View File
@@ -1 +1 @@
__version__ = "3.11.1"
__version__ = "3.11.0.dev0"
+1 -3
View File
@@ -576,11 +576,9 @@ class EventSettingsSerializer(serializers.Serializer):
'attendee_company_required',
'confirm_texts',
'order_email_asked_twice',
'payment_term_mode',
'payment_term_days',
'payment_term_weekdays',
'payment_term_minutes',
'payment_term_last',
'payment_term_weekdays',
'payment_term_expire_automatically',
'payment_term_accept_late',
'payment_explanation',
+3 -4
View File
@@ -12,9 +12,7 @@ from django.utils.timezone import now
from django.utils.translation import get_language, gettext_lazy as _
from inlinestyler.utils import inline_css
from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
)
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber
from pretix.base.models import Event
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import (
@@ -317,8 +315,9 @@ def base_placeholders(sender, **kwargs):
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
),
SimpleFunctionalMailTextPlaceholder(
'expire_date', ['event', 'order'], lambda event, order: LazyExpiresDate(order.expires.astimezone(event.timezone)),
'expire_date', ['event', 'order'], lambda event, order: LazyDate(order.expires.astimezone(event.timezone)),
lambda event: LazyDate(now() + timedelta(days=15))
# TODO: This used to be "date" in some placeholders, add a migration!
),
SimpleFunctionalMailTextPlaceholder(
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
-32
View File
@@ -1,17 +1,12 @@
import hashlib
import ipaddress
from django import forms
from django.conf import settings
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from pretix.base.models import User
from pretix.helpers.dicts import move_to_end
from pretix.helpers.http import get_client_ip
class LoginForm(forms.Form):
@@ -23,7 +18,6 @@ class LoginForm(forms.Form):
error_messages = {
'invalid_login': _("This combination of credentials is not known to our system."),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
'inactive': _("This account is inactive.")
}
@@ -45,36 +39,10 @@ class LoginForm(forms.Form):
else:
move_to_end(self.fields, 'keep_logged_in')
@cached_property
def ratelimit_key(self):
if not settings.HAS_REDIS:
return None
client_ip = get_client_ip(self.request)
if not client_ip:
return None
try:
client_ip = ipaddress.ip_address(client_ip)
except ValueError:
# Web server not set up correctly
return None
if client_ip.is_private:
# This is the private IP of the server, web server not set up correctly
return None
return 'pretix_login_{}'.format(hashlib.sha1(str(client_ip).encode()).hexdigest())
def clean(self):
if all(k in self.cleaned_data for k, f in self.fields.items() if f.required):
if self.ratelimit_key:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.get(self.ratelimit_key)
if cnt and int(cnt) > 10:
raise forms.ValidationError(self.error_messages['rate_limit'], code='rate_limit')
self.user_cache = self.backend.form_authenticate(self.request, self.cleaned_data)
if self.user_cache is None:
if self.ratelimit_key:
rc.incr(self.ratelimit_key)
rc.expire(self.ratelimit_key, 300)
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login'
-14
View File
@@ -1,5 +1,4 @@
from django import forms
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
@@ -20,7 +19,6 @@ class UserSettingsForm(forms.ModelForm):
"address or password."),
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
}
old_pw = forms.CharField(max_length=255,
@@ -66,18 +64,6 @@ class UserSettingsForm(forms.ModelForm):
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')
if old_pw and settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwchange_%s' % self.user.pk)
rc.expire('pretix_pwchange_%s' % self.user.pk, 300)
if cnt > 10:
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if old_pw and not check_password(old_pw, self.user.password):
raise forms.ValidationError(
self.error_messages['pw_current_wrong'],
-15
View File
@@ -27,21 +27,6 @@ class LazyDate:
return date_format(self.value, "SHORT_DATE_FORMAT")
class LazyExpiresDate:
def __init__(self, expires):
self.value = expires
def __format__(self, format_spec):
return self.__str__()
def __str__(self):
at_end_of_day = self.value.hour == 23 and self.value.minute == 59 and self.value.second >= 59
if at_end_of_day:
return date_format(self.value, "SHORT_DATE_FORMAT")
else:
return date_format(self.value, "SHORT_DATETIME_FORMAT")
class LazyCurrencyNumber:
def __init__(self, value, currency):
self.value = value
@@ -1,23 +0,0 @@
# Generated by Django 3.0.11 on 2020-12-18 18:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0162_remove_seat_name'),
]
operations = [
migrations.AddField(
model_name='cachedfile',
name='session_key',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='cachedfile',
name='web_download',
field=models.BooleanField(default=True),
),
]
-2
View File
@@ -28,8 +28,6 @@ class CachedFile(models.Model):
filename = models.CharField(max_length=255)
type = models.CharField(max_length=255)
file = models.FileField(null=True, blank=True, upload_to=cachedfile_name, max_length=255)
web_download = models.BooleanField(default=True) # allow web download, True for backwards compatibility in plugins
session_key = models.TextField(null=True, blank=True) # only allow download in this session
@receiver(post_delete, sender=CachedFile)
+7 -13
View File
@@ -392,19 +392,13 @@ class Order(LockModel, LoggedModel):
def set_expires(self, now_dt=None, subevents=None):
now_dt = now_dt or now()
tz = pytz.timezone(self.event.settings.timezone)
mode = self.event.settings.get('payment_term_mode')
if mode == 'days':
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if self.event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
elif mode == 'minutes':
exp_by_date = now_dt.astimezone(tz) + timedelta(minutes=self.event.settings.get('payment_term_minutes', as_type=int))
else:
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if self.event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
self.expires = exp_by_date
+1 -1
View File
@@ -159,7 +159,7 @@ class Seat(models.Model):
parts.append(gettext('Seat {number}').format(number=self.seat_number))
if not parts:
return self.seat_guid
return self.name
return ', '.join(parts)
@classmethod
+2 -4
View File
@@ -17,7 +17,7 @@ from pretix.celery_app import app
@app.task(base=ProfiledEventTask)
def export(event: Event, shredders: List[str], session_key=None) -> None:
def export(event: Event, shredders: List[str]) -> None:
known_shredders = event.get_data_shredders()
with NamedTemporaryFile() as rawfile:
@@ -54,9 +54,7 @@ def export(event: Event, shredders: List[str], session_key=None) -> None:
cf = CachedFile()
cf.date = now()
cf.filename = event.slug + '.zip'
cf.type = 'application/zip'
cf.session_key = session_key
cf.web_download = True
cf.type = 'application/pdf'
cf.expires = now() + timedelta(hours=1)
cf.save()
cf.file.save(cachedfile_name(cf, cf.filename), rawfile)
+15 -73
View File
@@ -423,29 +423,6 @@ DEFAULTS = {
"if you want.")
)
},
'payment_term_mode': {
'default': 'days',
'type': str,
'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField,
'serializer_kwargs': dict(
choices=(
('days', _("in days")),
('minutes', _("in minutes"))
),
),
'form_kwargs': dict(
label=_("Set payment term"),
widget=forms.RadioSelect,
required=True,
choices=(
('days', _("in days")),
('minutes', _("in minutes"))
),
help_text=_("If using days, the order will expire at the end of the last day. "
"Using minutes is more exact, but should only be used for real-time payment methods.")
)
},
'payment_term_days': {
'default': '14',
'type': int,
@@ -453,16 +430,11 @@ DEFAULTS = {
'serializer_class': serializers.IntegerField,
'form_kwargs': dict(
label=_('Payment term in days'),
widget=forms.NumberInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_0',
'data-required-if': '#id_payment_term_mode_0'
},
),
help_text=_("The number of days after placing an order the user has to pay to preserve their reservation. If "
"you use slow payment methods like bank transfer, we recommend 14 days. If you only use real-time "
"payment methods, we recommend still setting two or three days to allow people to retry failed "
"payments."),
required=True,
validators=[MinValueValidator(0),
MaxValueValidator(1000000)]
),
@@ -471,6 +443,18 @@ DEFAULTS = {
MaxValueValidator(1000000)]
)
},
'payment_term_last': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField,
'form_kwargs': dict(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the number of "
"days configured above. If you use the event series feature and an order contains tickets for "
"multiple dates, the earliest date will be used."),
)
},
'payment_term_weekdays': {
'default': 'True',
'type': bool,
@@ -480,49 +464,7 @@ DEFAULTS = {
label=_('Only end payment terms on weekdays'),
help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be "
"moved to the next Monday instead. This is required in some countries by civil law. This will "
"not effect the last date of payments configured below."),
widget=forms.CheckboxInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_0',
'data-required-if': '#id_payment_term_mode_0'
},
),
)
},
'payment_term_minutes': {
'default': '30',
'type': int,
'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField,
'form_kwargs': dict(
label=_('Payment term in minutes'),
help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. "
"Only use this if you exclusively offer real-time payment methods. Please note that for technical reasons, "
"the actual time frame might be a few minutes longer before the order is marked as expired."),
validators=[MinValueValidator(0),
MaxValueValidator(1440)],
widget=forms.NumberInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_1',
'data-required-if': '#id_payment_term_mode_1'
},
),
),
'serializer_kwargs': dict(
validators=[MinValueValidator(0),
MaxValueValidator(1440)]
)
},
'payment_term_last': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField,
'form_kwargs': dict(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the terms "
"configured above. If you use the event series feature and an order contains tickets for "
"multiple dates, the earliest date will be used."),
"not effect the last date of payments configured above."),
)
},
'payment_term_expire_automatically': {
@@ -994,7 +936,7 @@ DEFAULTS = {
('week', _('Week calendar')),
('calendar', _('Month calendar')),
),
help_text=_('If your event series has more than 50 dates in the future, only the month or week calendar can be used.')
help_text=_('If your event series has more than 100 dates, only the month or week calendar can be used.')
),
},
'last_order_modification_date': {
+2 -43
View File
@@ -1,4 +1,3 @@
import re
import urllib.parse
import bleach
@@ -72,10 +71,6 @@ EMAIL_RE = build_email_re(tlds=sorted(tld_set, key=len, reverse=True))
def safelink_callback(attrs, new=False):
"""
Makes sure that all links to a different domain are passed through a redirection handler
to ensure there's no passing of referers with secrets inside them.
"""
url = attrs.get((None, 'href'), '/')
if not url_has_allowed_host_and_scheme(url, allowed_hosts=None) and not url.startswith('mailto:') and not url.startswith('tel:'):
signer = signing.Signer(salt='safe-redirect')
@@ -85,42 +80,7 @@ def safelink_callback(attrs, new=False):
return attrs
def truelink_callback(attrs, new=False):
"""
Tries to prevent "phishing" attacks in which a link looks like it points to a safe place but instead
points somewhere else, e.g.
<a href="https://evilsite.com">https://google.com</a>
At the same time, custom texts are still allowed:
<a href="https://maps.google.com">Get to the event</a>
Suffixes are also allowed:
<a href="https://maps.google.com/location/foo">https://maps.google.com</a>
"""
text = re.sub('[^a-zA-Z0-9.-/_]', '', attrs.get('_text')) # clean up link text
if URL_RE.match(text):
# link text looks like a url
if text.startswith('//'):
text = 'https:' + text
elif not text.startswith('http'):
text = 'https://' + text
text_url = urllib.parse.urlparse(text)
href_url = urllib.parse.urlparse(attrs[None, 'href'])
if text_url.netloc != href_url.netloc or not href_url.path.startswith(href_url.path):
# link text contains an URL that has a different base than the actual URL
attrs['_text'] = attrs[None, 'href']
return attrs
def abslink_callback(attrs, new=False):
"""
Makes sure that all links will be absolute links and will be opened in a new page with no
window.opener attribute.
"""
url = attrs.get((None, 'href'), '/')
if not url.startswith('mailto:') and not url.startswith('tel:'):
attrs[None, 'href'] = urllib.parse.urljoin(settings.SITE_URL, url)
@@ -133,7 +93,6 @@ def markdown_compile_email(source):
linker = bleach.Linker(
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
return linker.linkify(bleach.clean(
@@ -186,7 +145,7 @@ def rich_text(text: str, **kwargs):
linker = bleach.Linker(
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
callbacks=DEFAULT_CALLBACKS + ([safelink_callback] if kwargs.get('safelinks', True) else [abslink_callback]),
parse_email=True
)
body_md = linker.linkify(markdown_compile(text))
@@ -202,7 +161,7 @@ def rich_text_snippet(text: str, **kwargs):
linker = bleach.Linker(
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
callbacks=DEFAULT_CALLBACKS + ([safelink_callback] if kwargs.get('safelinks', True) else [abslink_callback]),
parse_email=True
)
body_md = linker.linkify(markdown_compile(text, snippet=True))
+1 -5
View File
@@ -13,11 +13,7 @@ class DownloadView(TemplateView):
@cached_property
def object(self) -> CachedFile:
try:
o = get_object_or_404(CachedFile, id=self.kwargs['id'], web_download=True)
if o.session_key:
if o.session_key != self.request.session.session_key:
raise Http404()
return o
return get_object_or_404(CachedFile, id=self.kwargs['id'])
except ValueError: # Invalid URLs
raise Http404()
+27 -3
View File
@@ -1,3 +1,4 @@
import itertools
import json
from collections import OrderedDict
from decimal import Decimal
@@ -53,7 +54,31 @@ class BaseQuestionsViewMixin:
data=(self.request.POST if self.request.method == 'POST' else None),
files=(self.request.FILES if self.request.method == 'POST' else None))
form.pos = cartpos or orderpos
form.show_copy_answers_to_addon_button = form.pos.addon_to and set(form.pos.addon_to.item.questions.all()) & set(form.pos.item.questions.all())
if form.pos.addon_to_id is not None:
form.copy_answer_from = None
# addons typically do not have the same item as the main position, so we look for overlapping questions
form.show_copy_answers_to_addon_button = bool(
set(form.pos.addon_to.item.questions.values_list("id", flat=True)) &
set(form.pos.item.questions.values_list("id", flat=True)))
else:
form.show_copy_answers_to_addon_button = False
# look for a position we can best copy answers from
form.copy_answer_from = next(
itertools.chain(
( # match a position with the same item
other_form.pos.id for other_form in formlist
if other_form.pos.addon_to_id is None and form.pos.item.id == other_form.pos.item.id
),
( # match a position with questions in common
other_form.pos.id for other_form in formlist
if other_form.pos.addon_to_id is None
and set(form.pos.item.questions.values_list("id", flat=True)) & set(other_form.pos.item.questions.values_list("id", flat=True))
)
),
None # didn't find a position to copy answers from
)
if len(form.fields) > 0:
formlist.append(form)
return formlist
@@ -105,8 +130,7 @@ class BaseQuestionsViewMixin:
if hasattr(field, 'answer'):
# We already have a cached answer object, so we don't
# have to create a new one
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
or (isinstance(v, QuerySet) and not v.exists()):
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) or (isinstance(v, QuerySet) and not v.exists()):
if field.answer.file:
field.answer.file.delete()
field.answer.delete()
+1 -15
View File
@@ -584,11 +584,9 @@ class CancelSettingsForm(SettingsForm):
class PaymentSettingsForm(SettingsForm):
auto_fields = [
'payment_term_mode',
'payment_term_days',
'payment_term_weekdays',
'payment_term_minutes',
'payment_term_last',
'payment_term_weekdays',
'payment_term_expire_automatically',
'payment_term_accept_late',
'payment_explanation',
@@ -601,18 +599,6 @@ class PaymentSettingsForm(SettingsForm):
"will set the tax rate and reverse charge rules, other settings of the tax rule are ignored.")
)
def clean_payment_term_days(self):
value = self.cleaned_data.get('payment_term_days')
if self.cleaned_data.get('payment_term_mode') == 'days' and value is None:
raise ValidationError(_("This field is required."))
return value
def clean_payment_term_minutes(self):
value = self.cleaned_data.get('payment_term_minutes')
if self.cleaned_data.get('payment_term_mode') == 'minutes' and value is None:
raise ValidationError(_("This field is required."))
return value
def clean(self):
data = super().clean()
settings_dict = self.obj.settings.freeze()
@@ -59,11 +59,9 @@
<fieldset>
<legend>{% trans "Deadlines" %}</legend>
{% bootstrap_form_errors form layout="control" %}
{% bootstrap_field form.payment_term_mode layout="control" %}
{% bootstrap_field form.payment_term_days layout="control" %}
{% bootstrap_field form.payment_term_weekdays layout="control" %}
{% bootstrap_field form.payment_term_minutes layout="control" %}
{% bootstrap_field form.payment_term_last layout="control" %}
{% bootstrap_field form.payment_term_weekdays layout="control" %}
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
{% bootstrap_field form.payment_term_accept_late layout="control" %}
</fieldset>
+3 -5
View File
@@ -74,15 +74,13 @@ def login(request):
backend = [b for b in backends if b.visible][0]
if request.user.is_authenticated:
next_url = backend.get_next_url(request) or 'control:index'
if next_url and url_has_allowed_host_and_scheme(next_url, allowed_hosts=None):
return redirect(next_url)
return redirect(reverse('control:index'))
return redirect(next_url)
if request.method == 'POST':
form = LoginForm(backend=backend, data=request.POST, request=request)
form = LoginForm(backend=backend, data=request.POST)
if form.is_valid() and form.user_cache and form.user_cache.auth_backend == backend.identifier:
return process_login(request, form.user_cache, form.cleaned_data.get('keep_logged_in', False))
else:
form = LoginForm(backend=backend, request=request)
form = LoginForm(backend=backend)
ctx['form'] = form
ctx['can_register'] = settings.PRETIX_REGISTRATION
ctx['can_reset'] = settings.PRETIX_PASSWORD_RESET
+2 -2
View File
@@ -1956,9 +1956,9 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
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 = CachedFile()
cf.date = now()
cf.expires = now() + timedelta(hours=24)
cf.expires = now() + timedelta(days=3)
cf.save()
return self.do(self.request.event.id, str(cf.id), self.exporter.identifier, self.exporter.form.cleaned_data)
+2 -2
View File
@@ -1238,9 +1238,9 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, V
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 = CachedFile()
cf.date = now()
cf.expires = now() + timedelta(hours=24)
cf.expires = now() + timedelta(days=3)
cf.save()
return self.do(
organizer=self.request.organizer.id,
+4 -4
View File
@@ -50,9 +50,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
f = self.request.FILES.get('background')
error = False
if f.size > self.maxfilesize:
error = _('The uploaded PDF file is too large.')
error = _('The uploaded PDF file is to large.')
if f.size < self.minfilesize:
error = _('The uploaded PDF file is too small.')
error = _('The uploaded PDF file is to small.')
if mimetypes.guess_type(f.name)[0] not in self.accepted_formats:
error = _('Please only upload PDF files.')
# if there was an error, add error message to response_data and return
@@ -137,7 +137,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
buffer = BytesIO()
p.write(buffer)
buffer.seek(0)
c = CachedFile(web_download=True)
c = CachedFile()
c.expires = now() + timedelta(days=7)
c.date = now()
c.filename = 'background_preview.pdf'
@@ -162,7 +162,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
"status": "error",
"error": error
})
c = CachedFile(web_download=True)
c = CachedFile()
c.expires = now() + timedelta(days=7)
c.date = now()
c.filename = 'background_preview.pdf'
+1 -1
View File
@@ -75,7 +75,7 @@ class ShredExportView(RecentAuthenticationRequiredMixin, EventPermissionRequired
if constr:
return self.error(ShredError(self.get_error_url()))
return self.do(self.request.event.id, request.POST.getlist("shredder"), self.request.session.session_key)
return self.do(self.request.event.id, request.POST.getlist("shredder"))
class ShredDoView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
@@ -1,10 +0,0 @@
from django import template
from pretix.base.i18n import LazyExpiresDate
register = template.Library()
@register.filter
def format_expires(order):
return LazyExpiresDate(order.expires.astimezone(order.event.timezone))
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-07-30 19:00+0000\n"
"Last-Translator: Abdullah <abdullah.gumaijan@gmail.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-05-19 09:19+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-20 11:52+0000\n"
"PO-Revision-Date: 2020-08-25 02:00+0000\n"
"Last-Translator: Dennis Lichtenthäler <lichtenthaeler@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
-2
View File
@@ -121,7 +121,6 @@ Key
Kombitickets
Kompatibilitätsmodus
Konfigurations
Kosovo
landesspezifische
Lead
Leaflet
@@ -184,7 +183,6 @@ Saalplan
SAQ
SCA
Scan
Scans
Scanergebnis
Scanning
schiefgeht
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-20 11:52+0000\n"
"PO-Revision-Date: 2020-08-25 02:00+0000\n"
"Last-Translator: Dennis Lichtenthäler <lichtenthaeler@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
@@ -121,7 +121,6 @@ Key
Kombitickets
Kompatibilitätsmodus
Konfigurations
Kosovo
landesspezifische
Lead
Leaflet
@@ -186,7 +185,6 @@ SCA
Scan
Scanergebnis
Scanning
Scans
schiefgeht
sechsstelligen
Secret
+1095 -1235
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-04-27 20:00+0000\n"
"Last-Translator: Gonzalo Gabriel Perez <zalitoar@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-02-26 03:00+0000\n"
"Last-Translator: David100mark <david.hundertmark@gmx.net>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-01-24 08:00+0000\n"
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-06-12 20:00+0000\n"
"Last-Translator: Frank <webappconcept@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-11-13 06:00+0000\n"
"Last-Translator: Zane Smite <z.smite@riga-jurmala.com>\n"
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-08-05 06:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2020-08-09 00:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-09-24 19:00+0000\n"
"Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-03-19 09:00+0000\n"
"Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-11-07 09:31+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-08-27 08:00+0000\n"
"Last-Translator: Bostjan Marusic <bostjan@brokenbones.si>\n"
"Language-Team: Slovenian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-08-11 00:00+0000\n"
"Last-Translator: Tobias Sundgren <syrgas@gmail.com>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
-1
View File
@@ -44,7 +44,6 @@ iDEAL
integrations
iOS
JSON
Kosovo
Lead
Leaflet
LLC
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-14 12:00+0000\n"
"POT-Creation-Date: 2020-08-25 09:54+0000\n"
"PO-Revision-Date: 2019-03-28 14:00+0000\n"
"Last-Translator: yichengsd <sunzl@jxepub.com>\n"
"Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/"
+1 -1
View File
@@ -235,7 +235,7 @@ class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
def post(self, request, *args, **kwargs):
order = get_object_or_404(self.request.event.orders, code=request.GET.get("code"))
cf = CachedFile(web_download=True, session_key=self.request.session.session_key)
cf = CachedFile()
cf.date = now()
cf.type = 'application/pdf'
cf.expires = now() + timedelta(days=3)
@@ -534,108 +534,3 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
def get_filename(self):
return '{}_checkin'.format(self.event.slug)
class CheckinLogList(ListExporter):
name = "checkinlog"
identifier = 'checkinlog'
verbose_name = gettext_lazy('Check-in log (all successful scans)')
@property
def additional_form_fields(self):
return self._fields
def iterate_list(self, form_data):
yield [
_('Date'),
_('Time'),
_('Check-in list'),
_('Scan type'),
_('Order code'),
_('Position ID'),
_('Secret'),
_('Product'),
_('Name'),
_('Device'),
_('Offline override'),
_('Automatically checked in'),
]
qs = Checkin.objects.filter(
position__order__event=self.event,
)
if form_data.get('list'):
qs = qs.filter(list_id=form_data.get('list'))
if form_data.get('items'):
qs = qs.filter(position__item_id__in=form_data['items'])
yield self.ProgressSetTotal(total=qs.count())
qs = qs.select_related(
'position__item', 'position__order', 'position__order__invoice_address', 'position', 'list', 'device'
).order_by(
'datetime'
)
for ci in qs.iterator():
try:
ia = ci.position.order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = InvoiceAddress()
yield [
date_format(ci.datetime, 'SHORT_DATE_FORMAT'),
date_format(ci.datetime, 'TIME_FORMAT'),
str(ci.list),
ci.get_type_display(),
ci.position.order.code,
ci.position.positionid,
ci.position.secret,
str(ci.position.item),
ci.position.attendee_name or ia.name,
str(ci.device),
_('Yes') if ci.forced else _('No'),
_('Yes') if ci.auto_checked_in else _('No'),
]
def get_filename(self):
return '{}_checkinlog'.format(self.event.slug)
@property
def _fields(self):
d = OrderedDict(
[
('list',
forms.ModelChoiceField(
queryset=self.event.checkin_lists.all(),
label=_('Check-in list'),
widget=forms.RadioSelect(
attrs={'class': 'scrolling-choice'}
),
)),
('items',
forms.ModelMultipleChoiceField(
queryset=self.event.items.all(),
label=_('Limit to products'),
widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'}
),
initial=self.event.items.all()
)),
]
)
d['list'].queryset = self.event.checkin_lists.all()
d['list'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.orders.checkinlists.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': _('All check-in lists')
}
)
d['list'].widget.choices = d['list'].choices
d['list'].required = False
return d
@@ -13,9 +13,3 @@ def register_csv(sender, **kwargs):
def register_pdf(sender, **kwargs):
from .exporters import PDFCheckinList
return PDFCheckinList
@receiver(register_data_exporters, dispatch_uid="export_checkin_list")
def register_log(sender, **kwargs):
from .exporters import CheckinLogList
return CheckinLogList
@@ -60,9 +60,9 @@
{% if pos.variation %}
{{ pos.variation }}
{% endif %}
{% if forloop.counter > 1 %}
{% if forms.0.copy_answer_from is not None %}
<span class="text-right flip">
<button type="button" data-id="{{ forloop.counter0 }}" name="copy" class="js-copy-answers btn btn-default btn-xs">{% trans "Copy answers from above" %}</button>
<button type="button" data-id="{{ forms.0.pos.id }}" data-copy-from="{{ forms.0.copy_answer_from }}" name="copy" class="js-copy-answers btn btn-default btn-xs">{% trans "Copy answers from above" %}</button>
<i class="fa fa-angle-down collapse-indicator"></i>
</span>
{% else %}
@@ -122,13 +122,13 @@
<legend>
{% if form.show_copy_answers_to_addon_button %}
<span class="pull-right flip">
<button type="button" data-id="{{ forloop.parentloop.counter0 }}" data-addonid="{{ forloop.counter0 }}" name="copy" class="js-copy-answers-addon btn btn-default btn-xs">{% trans "Copy answers" %}</button>
<button type="button" data-id="{{ forms.0.pos.id }}" data-addonid="{{ forloop.counter0 }}" name="copy" class="js-copy-answers-addon btn btn-default btn-xs">{% trans "Copy answers" %}</button>
</span>
{% endif %}
+ {{ form.pos.item.name }}{% if form.pos.variation %} {{ form.pos.variation.value }}{% endif %}
</legend>
{% endif %}
<div data-idx="{{ forloop.parentloop.counter0 }}" data-addonidx="{{ forloop.counter0 }}">
<div data-idx="{{ forms.0.pos.id }}" data-addonidx="{{ forloop.counter0 }}">
{% bootstrap_form form layout="checkout" %}
</div>
{% endfor %}
@@ -3,7 +3,6 @@
{% load bootstrap3 %}
{% load eventsignal %}
{% load money %}
{% load expiresformat %}
{% load eventurl %}
{% block title %}{% trans "Order details" %}{% endblock %}
{% block content %}
@@ -67,7 +66,7 @@
<strong>{% blocktrans trimmed with total=pending_sum|money:request.event.currency %}
A payment of {{ total }} is still pending for this order.
{% endblocktrans %}</strong>
<strong>{% blocktrans trimmed with date=order|format_expires %}
<strong>{% blocktrans trimmed with date=order.expires|date:"SHORT_DATE_FORMAT" %}
Please complete your payment before {{ date }}
{% endblocktrans %}</strong>
{% if last_payment %}
+37 -22
View File
@@ -16,9 +16,13 @@ function ngettext(singular, plural, count) {
function interpolate(fmt, object, named) {
if (named) {
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
return fmt.replace(/%\(\w+\)s/g, function (match) {
return String(obj[match.slice(2, -2)])
});
} else {
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
return fmt.replace(/%s/g, function (match) {
return String(obj.shift())
});
}
}
@@ -171,13 +175,13 @@ $(function () {
$(".js-copy-answers").click(function (e) {
e.preventDefault();
e.stopPropagation();
let idx = $(this).data('id');
const addonDivs = $('div[data-idx="' + idx +'"]')
const idx = $(this).data('id');
const copyFromIdx = $(this).data('copy-from')
const addonDivs = $('div[data-idx="' + idx + '"]')
addonDivs.each(function (index) {
const elements = $(this).find('input, select, textarea');
const addonIdx = $(this).attr("data-addonidx");
const answersDiv = $('div[data-idx="0"][data-addonidx="' + addonIdx + '"]');
const addonIdx = $(this).data("addonidx");
const answersDiv = $('div[data-idx="' + copyFromIdx + '"][data-addonidx="' + addonIdx + '"]');
const answers = answersDiv.find('input, select, textarea');
copy_answers(elements, answers);
@@ -189,7 +193,7 @@ $(function () {
e.stopPropagation();
const id = $(this).data('id');
const addonId = $(this).data('addonid');
const addonDiv = $('div[data-idx="' + id +'"][data-addonidx="' + addonId + '"]');
const addonDiv = $('div[data-idx="' + id + '"][data-addonidx="' + addonId + '"]');
const elements = addonDiv.find('input, select, textarea');
const answers = $('*[data-idx="' + id + '"] input, *[data-idx="' + id + '"] select, *[data-idx="' + id + '"] textarea');
copy_answers(elements, answers);
@@ -249,7 +253,7 @@ $(function () {
is_enabled = true;
}
});
$(".input-seat-selection option").each(function() {
$(".input-seat-selection option").each(function () {
if ($(this).val() && $(this).val() !== "" && $(this).prop('selected')) {
is_enabled = true;
}
@@ -257,7 +261,9 @@ $(function () {
}
if (!is_enabled && !$(".has-seating").length) {
$("#btn-add-to-cart").prop("disabled", !is_enabled).popover({
'content': function () { return gettext("Please enter a quantity for one of the ticket types.") },
'content': function () {
return gettext("Please enter a quantity for one of the ticket types.")
},
'placement': 'top',
'trigger': 'hover focus'
});
@@ -353,7 +359,9 @@ $(function () {
if (counter > curCounter) {
return; // Lost race
}
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
dependent.find("option").filter(function (t) {
return !!$(this).attr("value")
}).remove();
if (data.data.length > 0) {
$.each(data.data, function (k, s) {
dependent.append($("<option>").attr("value", s.code).text(s.name));
@@ -400,8 +408,7 @@ $(function () {
true
));
}
var cancel_fee_slider = $('#cancel-fee-slider').slider({
}).on('slide', function () {
var cancel_fee_slider = $('#cancel-fee-slider').slider({}).on('slide', function () {
cancel_fee_slider_update();
}).data('slider');
if (cancel_fee_slider) {
@@ -417,7 +424,7 @@ $(function () {
}
var local_tz = moment.tz.guess()
$("span[data-timezone]").each(function() {
$("span[data-timezone]").each(function () {
var t = moment.tz($(this).attr("data-time"), $(this).attr("data-timezone"))
var tz = moment.tz.zone($(this).attr("data-timezone"))
@@ -445,39 +452,47 @@ $(function () {
});
function copy_answers(elements, answers) {
elements.each(function (index) {
elements.each(function (index) {
var input = $(this),
tagName = input.prop('tagName').toLowerCase(),
attributeType = input.attr('type'),
suffix = input.attr('name').split('-')[1];
let a = false;
switch (tagName) {
case "textarea":
input.val(answers.filter("[name$=" + suffix + "]").val());
a = answers.filter("[name$=" + suffix + "]");
if (a.length) input.val(a.val());
break;
case "select":
input.val(answers.filter("[name$=" + suffix + "]").find(":selected").val()).change();
a = answers.filter("[name$=" + suffix + "]").find(":selected");
if (a.length) input.val(a.val()).change();
break;
case "input":
switch (attributeType) {
case "text":
case "number":
input.val(answers.filter("[name$=" + suffix + "]").val());
a = answers.filter("[name$=" + suffix + "]")
if (a.length) input.val(a.val());
break;
case "checkbox":
case "radio":
if (input.attr('value')) {
input.prop("checked", answers.filter("[name$=" + suffix + "][value=" + input.attr('value') + "]").prop("checked"));
a = answers.filter("[name$=" + suffix + "][value=" + input.attr('value') + "]")
if (a.length) input.prop("checked", a.prop("checked"));
} else {
input.prop("checked", answers.filter("[name$=" + suffix + "]").prop("checked"));
a = answers.filter("[name$=" + suffix + "]")
if (a.length) input.prop("checked", a.prop("checked"));
}
break;
default:
input.val(answers.filter("[name$=" + suffix + "]").val());
a = answers.filter("[name$=" + suffix + "]")
if (a.length) input.val(a.val());
}
break;
default:
input.val(answers.filter("[name$=" + suffix + "]").val());
a = answers.filter("[name$=" + suffix + "]")
if (a.length) input.val(a.val());
}
});
questions_toggle_dependent(true);
-27
View File
@@ -71,30 +71,3 @@ phrases =
Stripe Connect
chunkers = enchant.tokenize.HTMLChunker
filters = PythonFormatFilter,enchant.tokenize.URLFilter,HTMLFilter
[check-manifest]
ignore =
.update-locales
Makefile
manage.py
pretix/icons/*
requirements.txt
requirements/*
tests/*
tests/api/*
tests/base/*
tests/control/*
tests/testdummy/*
tests/templates/*
tests/presale/*
tests/doc/*
tests/helpers/*
tests/media/*
tests/multidomain/*
tests/plugins/*
tests/plugins/badges/*
tests/plugins/banktransfer/*
tests/plugins/paypal/*
tests/plugins/pretixdroid/*
tests/plugins/stripe/*
tests/plugins/ticketoutputpdf/*
-14
View File
@@ -78,20 +78,6 @@ def test_expiry_weekdays(event):
assert order.expires.weekday() == 0
@pytest.mark.django_db
def test_expiry_minutes(event):
today = now()
event.settings.set('payment_term_days', 5)
event.settings.set('payment_term_mode', 'minutes')
event.settings.set('payment_term_minutes', 30)
event.settings.set('payment_term_weekdays', False)
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
assert (order.expires - today).days == 0
assert (order.expires - today).seconds == 30 * 60
@pytest.mark.django_db
def test_expiry_last(event):
today = now()
-33
View File
@@ -1,33 +0,0 @@
import pytest
from pretix.base.templatetags.rich_text import rich_text, rich_text_snippet, markdown_compile_email
@pytest.mark.parametrize("link", [
# Test link detection
("google.com",
'<a href="http://google.com" rel="noopener" target="_blank">google.com</a>'),
# Test abslink_callback
("[Call](tel:+12345)",
'<a href="tel:+12345" rel="nofollow">Call</a>'),
("[Foo](/foo)",
'<a href="http://example.com/foo" rel="noopener" target="_blank">Foo</a>'),
("mail@example.org",
'<a href="mailto:mail@example.org">mailto:mail@example.org</a>'),
# Test truelink_callback
('<a href="https://evilsite.com">Evil Site</a>',
'<a href="https://evilsite.com" rel="noopener" target="_blank">Evil Site</a>'),
('<a href="https://evilsite.com">evilsite.com</a>',
'<a href="https://evilsite.com" rel="noopener" target="_blank">evilsite.com</a>'),
('<a href="https://evilsite.com">goodsite.com</a>',
'<a href="https://evilsite.com" rel="noopener" target="_blank">https://evilsite.com</a>'),
('<a href="https://goodsite.com.evilsite.com">goodsite.com</a>',
'<a href="https://goodsite.com.evilsite.com" rel="noopener" target="_blank">https://goodsite.com.evilsite.com</a>'),
('<a href="https://evilsite.com/deep/path">evilsite.com</a>',
'<a href="https://evilsite.com/deep/path" rel="noopener" target="_blank">evilsite.com</a>'),
])
def test_linkify_abs(link):
input, output = link
assert rich_text_snippet(input, safelinks=False) == output
assert rich_text(input, safelinks=False) == f'<p>{output}</p>'
assert markdown_compile_email(input) == f'<p>{output}</p>'
-4
View File
@@ -90,10 +90,6 @@ class LoginFormTest(TestCase):
self.assertEqual(response.status_code, 302)
self.assertIn('/control/events/', response['Location'])
response = self.client.get('/control/login?next=//evilsite.com')
self.assertEqual(response.status_code, 302)
self.assertIn('/control/', response['Location'])
def test_logout(self):
response = self.client.post('/control/login', {
'email': 'dummy@dummy.dummy',
-2
View File
@@ -375,8 +375,6 @@ class EventsTest(SoupTest):
self.get_doc('/control/event/%s/%s/settings/payment' % (self.orga1.slug, self.event1.slug))
self.post_doc('/control/event/%s/%s/settings/payment' % (self.orga1.slug, self.event1.slug), {
'payment_term_days': '2',
'payment_term_minutes': '30',
'payment_term_mode': 'days',
'tax_rate_default': tr19.pk,
})
self.event1.settings.flush()