mirror of
https://github.com/pretix/pretix.git
synced 2026-08-08 10:27:49 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa372b3df0 |
@@ -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
|
||||
|
||||
@@ -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 @@
|
||||
__version__ = "3.11.1"
|
||||
__version__ = "3.11.0.dev0"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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': {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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))
|
||||
+1101
-1299
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-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/"
|
||||
|
||||
+1102
-1288
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"
|
||||
|
||||
+1095
-1239
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"
|
||||
|
||||
+1097
-1285
File diff suppressed because it is too large
Load Diff
@@ -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/"
|
||||
|
||||
+1108
-1269
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 <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
@@ -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
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"
|
||||
|
||||
+1104
-1304
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-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/"
|
||||
|
||||
+1104
-1303
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-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-"
|
||||
|
||||
+1104
-1303
File diff suppressed because it is too large
Load Diff
@@ -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/"
|
||||
|
||||
+1095
-1241
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-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-"
|
||||
|
||||
+1095
-1254
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-06-12 20:00+0000\n"
|
||||
"Last-Translator: Frank <webappconcept@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
+1095
-1252
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-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"
|
||||
|
||||
+1104
-1303
File diff suppressed because it is too large
Load Diff
@@ -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/"
|
||||
|
||||
+1095
-1253
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-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/"
|
||||
|
||||
+1095
-1235
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"
|
||||
|
||||
+1097
-1254
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-11-07 09:31+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
+1095
-1253
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-08-27 08:00+0000\n"
|
||||
"Last-Translator: Bostjan Marusic <bostjan@brokenbones.si>\n"
|
||||
"Language-Team: Slovenian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
+1095
-1244
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-08-11 00:00+0000\n"
|
||||
"Last-Translator: Tobias Sundgren <syrgas@gmail.com>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
+1104
-1303
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: 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-"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>'
|
||||
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user