mirror of
https://github.com/pretix/pretix.git
synced 2026-07-30 09:05:08 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece1b21298 |
+1
-3
@@ -8,8 +8,6 @@ tests:
|
||||
- XDG_CACHE_HOME=/cache bash .travis.sh tests
|
||||
tags:
|
||||
- python3
|
||||
except:
|
||||
- pypi
|
||||
pypi:
|
||||
stage: release
|
||||
script:
|
||||
@@ -24,7 +22,7 @@ pypi:
|
||||
tags:
|
||||
- python3
|
||||
only:
|
||||
- pypi
|
||||
- release
|
||||
artifacts:
|
||||
paths:
|
||||
- src/dist/
|
||||
|
||||
+18
-18
@@ -12,29 +12,29 @@ services:
|
||||
- postgresql
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.6
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
|
||||
- python: 3.6
|
||||
env: JOB=tests-cov
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.6
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.6
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.6
|
||||
env: JOB=style
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.6
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_mysql.cfg
|
||||
- python: 3.4
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.5
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.6
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.6
|
||||
env: JOB=plugins
|
||||
- python: 3.6
|
||||
env: JOB=tests-cov
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
|
||||
@@ -19,13 +19,13 @@ Order events
|
||||
There are multiple signals that will be sent out in the ordering cycle:
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: validate_cart, order_fee_calculation, order_paid, order_placed, order_fee_type_name, allow_ticket_download
|
||||
:members: validate_cart, fee_calculation_for_cart, order_fee_calculation, order_paid, order_placed, order_fee_type_name, allow_ticket_download
|
||||
|
||||
Frontend
|
||||
--------
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content
|
||||
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content
|
||||
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
|
||||
@@ -20,6 +20,7 @@ Your should install the following on your system:
|
||||
|
||||
* Python 3.4 or newer
|
||||
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
|
||||
* ``pyvenv`` for Python 3 (Debian package: ``python3-venv``)
|
||||
* ``python-dev`` for Python 3 (Debian package: ``python3-dev``)
|
||||
* ``libffi`` (Debian package: ``libffi-dev``)
|
||||
* ``libssl`` (Debian package: ``libssl-dev``)
|
||||
@@ -36,7 +37,7 @@ Please execute ``python -V`` or ``python3 -V`` to make sure you have Python 3.4
|
||||
execute ``pip3 -V`` to check. Then use Python's internal tools to create a virtual
|
||||
environment and activate it for your current session::
|
||||
|
||||
python3 -m venv env
|
||||
pyvenv env
|
||||
source env/bin/activate
|
||||
|
||||
You should now see a ``(env)`` prepended to your shell prompt. You have to do this
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.8.1"
|
||||
__version__ = "1.8.0.dev0"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
||||
|
||||
from pretix.base.models import Event
|
||||
@@ -17,18 +13,6 @@ class EventPermission(BasePermission):
|
||||
return True
|
||||
return False
|
||||
|
||||
if request.user.is_authenticated:
|
||||
# If this logic is updated, make sure to also update the logic in pretix/control/middleware.py
|
||||
if not settings.PRETIX_LONG_SESSIONS or not request.session.get('pretix_auth_long_session', False):
|
||||
last_used = request.session.get('pretix_auth_last_used', time.time())
|
||||
if time.time() - request.session.get('pretix_auth_login_time', time.time()) > settings.PRETIX_SESSION_TIMEOUT_ABSOLUTE:
|
||||
logout(request)
|
||||
request.session['pretix_auth_login_time'] = 0
|
||||
return False
|
||||
if time.time() - last_used > settings.PRETIX_SESSION_TIMEOUT_RELATIVE:
|
||||
return False
|
||||
request.session['pretix_auth_last_used'] = int(time.time())
|
||||
|
||||
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken)
|
||||
else request.user)
|
||||
if 'event' in request.resolver_match.kwargs and 'organizer' in request.resolver_match.kwargs:
|
||||
|
||||
@@ -11,7 +11,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import payment # NOQA
|
||||
from . import exporters # NOQA
|
||||
from . import invoice # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check # NOQA
|
||||
|
||||
try:
|
||||
from .celery_app import app as celery_app # NOQA
|
||||
|
||||
@@ -2,12 +2,15 @@ import logging
|
||||
from smtplib import SMTPRecipientsRefused, SMTPSenderRefused
|
||||
|
||||
from django.core.mail.backends.smtp import EmailBackend
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
|
||||
from pretix.base.validators import PlaceholderValidator
|
||||
|
||||
logger = logging.getLogger('pretix.base.email')
|
||||
|
||||
|
||||
class CustomSMTPBackend(EmailBackend):
|
||||
|
||||
def test(self, from_addr):
|
||||
try:
|
||||
self.open()
|
||||
@@ -24,3 +27,72 @@ class CustomSMTPBackend(EmailBackend):
|
||||
raise SMTPRecipientsRefused(senderrs)
|
||||
finally:
|
||||
self.close()
|
||||
|
||||
|
||||
class MailTemplateRenderer:
|
||||
def __init__(self, placeholders: list):
|
||||
self.placeholders = placeholders
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {
|
||||
'required': False,
|
||||
'widget': I18nTextarea,
|
||||
'validators': [],
|
||||
'help_text': ''
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
if defaults['help_text']:
|
||||
defaults['help_text'] += ' '
|
||||
defaults['help_text'] += _('Available placeholders: {list}').format(
|
||||
list=', '.join(['{' + v + '}' for v in self.placeholders])
|
||||
)
|
||||
defaults['validators'].append(PlaceholderValidator(['{' + v + '}' for v in self.placeholders]))
|
||||
return I18nFormField(**defaults)
|
||||
|
||||
def preview(self, text, **kwargs):
|
||||
return text.format(**kwargs)
|
||||
|
||||
def render(self, text, values):
|
||||
set_placeholders = set(values.keys())
|
||||
expected_palceholders = set(self.placeholders)
|
||||
if set_placeholders != expected_palceholders:
|
||||
raise ValueError('Invalid placeholder set. Unknown placeholders: {}. Missing placeholders: {}'.format(
|
||||
set_placeholders - expected_palceholders, expected_palceholders - set_placeholders
|
||||
))
|
||||
|
||||
return text.format_map(values)
|
||||
|
||||
|
||||
mail_text_order_placed = MailTemplateRenderer(
|
||||
['event', 'total', 'currency', 'date', 'payment_info', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_order_paid = MailTemplateRenderer(
|
||||
['event', 'url', 'invoice_name', 'invoice_company', 'payment_info']
|
||||
)
|
||||
mail_text_order_free = MailTemplateRenderer(
|
||||
['event', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_order_changed = MailTemplateRenderer(
|
||||
['event', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_resend_link = MailTemplateRenderer(
|
||||
['event', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_resend_all_links = MailTemplateRenderer(
|
||||
['event', 'orders']
|
||||
)
|
||||
mail_text_order_expire_warning = MailTemplateRenderer(
|
||||
['event', 'url', 'expire_date', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_waiting_list = MailTemplateRenderer(
|
||||
['event', 'url', 'product', 'hours', 'code']
|
||||
)
|
||||
mail_text_order_canceled = MailTemplateRenderer(
|
||||
['event', 'url', 'code']
|
||||
)
|
||||
mail_text_order_custom_mail = MailTemplateRenderer(
|
||||
['expire_date', 'event', 'code', 'date', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
mail_text_download_reminder = MailTemplateRenderer(
|
||||
['event', 'url']
|
||||
)
|
||||
|
||||
@@ -9,8 +9,6 @@ from hierarkey.forms import HierarkeyForm
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
|
||||
from .validators import PlaceholderValidator # NOQA
|
||||
|
||||
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
|
||||
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import BaseValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
|
||||
class PlaceholderValidator(BaseValidator):
|
||||
"""
|
||||
Takes list of allowed placeholders,
|
||||
validates form field by checking for placeholders,
|
||||
which are not presented in taken list.
|
||||
"""
|
||||
|
||||
def __init__(self, limit_value):
|
||||
super().__init__(limit_value)
|
||||
self.limit_value = limit_value
|
||||
|
||||
def __call__(self, value):
|
||||
if isinstance(value, LazyI18nString):
|
||||
for l, v in value.data.items():
|
||||
self.__call__(v)
|
||||
return
|
||||
|
||||
data_placeholders = list(re.findall(r'({[\w\s]*})', value, re.X))
|
||||
invalid_placeholders = []
|
||||
for placeholder in data_placeholders:
|
||||
if placeholder not in self.limit_value:
|
||||
invalid_placeholders.append(placeholder)
|
||||
if invalid_placeholders:
|
||||
raise ValidationError(
|
||||
_('Invalid placeholder(s): %(value)s'),
|
||||
code='invalid',
|
||||
params={'value': ", ".join(invalid_placeholders,)})
|
||||
|
||||
def clean(self, x):
|
||||
return x
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-10-03 16:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0077_auto_20170829_1126'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_number',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_state',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_time',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventmetaproperty',
|
||||
name='default',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='taxrule',
|
||||
name='eu_reverse_charge',
|
||||
field=models.BooleanField(default=False, help_text='Not recommended. Most events will NOT be qualified for reverse charge since the place of taxation is the location of the event. This option disables charging VAT for all customers outside the EU and for business customers in different EU countries who entered a valid EU VAT ID. Only enable this option after consulting a tax counsel. No warranty given for correct tax calculation. USE AT YOUR OWN RISK.', verbose_name='Use EU reverse charge taxation rules'),
|
||||
),
|
||||
]
|
||||
@@ -38,31 +38,6 @@ class EventMixin:
|
||||
raise ValidationError({'date_to': _('The end of the event has to be later than its start.')})
|
||||
super().clean()
|
||||
|
||||
def get_short_date_from_display(self, tz=None, show_times=True) -> str:
|
||||
"""
|
||||
Returns a shorter formatted string containing the start date of the event with respect
|
||||
to the current locale and to the ``show_times`` setting.
|
||||
"""
|
||||
tz = tz or pytz.timezone(self.settings.timezone)
|
||||
return _date(
|
||||
self.date_from.astimezone(tz),
|
||||
"SHORT_DATETIME_FORMAT" if self.settings.show_times and show_times else "DATE_FORMAT"
|
||||
)
|
||||
|
||||
def get_short_date_to_display(self, tz=None) -> str:
|
||||
"""
|
||||
Returns a shorter formatted string containing the start date of the event with respect
|
||||
to the current locale and to the ``show_times`` setting. Returns an empty string
|
||||
if ``show_date_to`` is ``False``.
|
||||
"""
|
||||
tz = tz or pytz.timezone(self.settings.timezone)
|
||||
if not self.settings.show_date_to or not self.date_to:
|
||||
return ""
|
||||
return _date(
|
||||
self.date_to.astimezone(tz),
|
||||
"SHORT_DATETIME_FORMAT" if self.settings.show_times else "DATE_FORMAT"
|
||||
)
|
||||
|
||||
def get_date_from_display(self, tz=None, show_times=True) -> str:
|
||||
"""
|
||||
Returns a formatted string containing the start date of the event with respect
|
||||
@@ -194,7 +169,7 @@ class Event(EventMixin, LoggedModel):
|
||||
organizer = models.ForeignKey(Organizer, related_name="events", on_delete=models.PROTECT)
|
||||
name = I18nCharField(
|
||||
max_length=200,
|
||||
verbose_name=_("Event name"),
|
||||
verbose_name=_("Name"),
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=50, db_index=True,
|
||||
|
||||
@@ -175,7 +175,6 @@ class Item(LoggedModel):
|
||||
related_name="items",
|
||||
blank=True, null=True,
|
||||
verbose_name=_("Category"),
|
||||
help_text=_("If you have many products, you can optionally sort them into categories to keep things organized.")
|
||||
)
|
||||
name = I18nCharField(
|
||||
max_length=255,
|
||||
@@ -705,9 +704,6 @@ class Quota(LoggedModel):
|
||||
blank=True,
|
||||
verbose_name=_("Variations")
|
||||
)
|
||||
cached_availability_state = models.PositiveIntegerField(null=True, blank=True)
|
||||
cached_availability_number = models.PositiveIntegerField(null=True, blank=True)
|
||||
cached_availability_time = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Quota")
|
||||
@@ -722,24 +718,11 @@ class Quota(LoggedModel):
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
clear_cache = kwargs.pop('clear_cache', True)
|
||||
super().save(*args, **kwargs)
|
||||
if self.event and clear_cache:
|
||||
if self.event:
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def rebuild_cache(self, now_dt=None):
|
||||
self.cached_availability_time = None
|
||||
self.cached_availability_number = None
|
||||
self.cached_availability_state = None
|
||||
self.availability(now_dt=now_dt)
|
||||
|
||||
def cache_is_hot(self, now_dt=None):
|
||||
now_dt = now_dt or now()
|
||||
return self.cached_availability_time and (now_dt - self.cached_availability_time).total_seconds() < 120
|
||||
|
||||
def availability(
|
||||
self, now_dt: datetime=None, count_waitinglist=True, _cache=None, allow_cache=False
|
||||
) -> Tuple[int, int]:
|
||||
def availability(self, now_dt: datetime=None, count_waitinglist=True, _cache=None) -> Tuple[int, int]:
|
||||
"""
|
||||
This method is used to determine whether Items or ItemVariations belonging
|
||||
to this quota should currently be available for sale.
|
||||
@@ -747,26 +730,12 @@ class Quota(LoggedModel):
|
||||
:returns: a tuple where the first entry is one of the ``Quota.AVAILABILITY_`` constants
|
||||
and the second is the number of available tickets.
|
||||
"""
|
||||
if allow_cache and self.cache_is_hot() and count_waitinglist:
|
||||
return self.cached_availability_state, self.cached_availability_number
|
||||
|
||||
if _cache and count_waitinglist is not _cache.get('_count_waitinglist', True):
|
||||
_cache.clear()
|
||||
|
||||
if _cache is not None and self.pk in _cache:
|
||||
return _cache[self.pk]
|
||||
now_dt = now_dt or now()
|
||||
res = self._availability(now_dt, count_waitinglist)
|
||||
|
||||
if count_waitinglist and not self.cache_is_hot(now_dt):
|
||||
self.cached_availability_state = res[0]
|
||||
self.cached_availability_number = res[1]
|
||||
self.cached_availability_time = now_dt
|
||||
self.save(
|
||||
update_fields=['cached_availability_state', 'cached_availability_number', 'cached_availability_time'],
|
||||
clear_cache=False
|
||||
)
|
||||
|
||||
if _cache is not None:
|
||||
_cache[self.pk] = res
|
||||
_cache['_count_waitinglist'] = count_waitinglist
|
||||
|
||||
@@ -21,7 +21,6 @@ from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
|
||||
from pretix.base.settings import SettingsSandbox
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.presale.views import get_cart_total
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -176,7 +175,6 @@ class BasePaymentProvider:
|
||||
help_text=_('Will be printed just below the payment figures and above the closing text on invoices.'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {'rows': '2'}}
|
||||
)),
|
||||
])
|
||||
|
||||
@@ -277,7 +275,7 @@ class BasePaymentProvider:
|
||||
|
||||
The default implementation checks for the _availability_date setting to be either unset or in the future.
|
||||
"""
|
||||
return self._is_still_available(cart_id=get_or_create_cart_id(request))
|
||||
return self._is_still_available(cart_id=request.session.session_key)
|
||||
|
||||
def payment_form_render(self, request: HttpRequest) -> str:
|
||||
"""
|
||||
|
||||
@@ -82,7 +82,6 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
|
||||
'invoice_company': ''
|
||||
})
|
||||
body, body_md = render_mail(template, context)
|
||||
subject = str(subject).format_map(context)
|
||||
sender = sender or (event.settings.get('mail_from') if event else settings.MAIL_FROM)
|
||||
|
||||
subject = str(subject)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
from django.db import models
|
||||
from django.db.models import F, Max, OuterRef, Q, Subquery
|
||||
from django.dispatch import receiver
|
||||
|
||||
from pretix.base.models import LogEntry, Quota
|
||||
from pretix.celery_app import app
|
||||
|
||||
from ..signals import periodic_task
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
def build_all_quota_caches(sender, **kwargs):
|
||||
refresh_quota_cashes.apply_async()
|
||||
|
||||
|
||||
@app.task
|
||||
def refresh_quota_cashes():
|
||||
last_activity = LogEntry.objects.filter(
|
||||
event=OuterRef('event_id'),
|
||||
).order_by().values('event').annotate(
|
||||
m=Max('datetime')
|
||||
).values(
|
||||
'm'
|
||||
)
|
||||
quotas = Quota.objects.annotate(
|
||||
last_activity=Subquery(last_activity, output_field=models.DateTimeField())
|
||||
).filter(
|
||||
Q(cached_availability_time__isnull=True) |
|
||||
Q(cached_availability_time__lt=F('last_activity'))
|
||||
)
|
||||
for q in quotas:
|
||||
q.availability()
|
||||
@@ -211,7 +211,7 @@ DEFAULTS = {
|
||||
},
|
||||
'confirm_text': {
|
||||
'default': None,
|
||||
'type': LazyI18nString
|
||||
'type': str
|
||||
},
|
||||
'mail_prefix': {
|
||||
'default': None,
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import BaseValidator
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
|
||||
class PlaceholderValidator(BaseValidator):
|
||||
"""
|
||||
Takes list of allowed placeholders,
|
||||
validates form field by checking for placeholders,
|
||||
which are not presented in taken list.
|
||||
"""
|
||||
|
||||
def __init__(self, limit_value):
|
||||
super().__init__(limit_value)
|
||||
self.limit_value = limit_value
|
||||
|
||||
def __call__(self, value):
|
||||
if isinstance(value, LazyI18nString):
|
||||
for l, v in value.data.items():
|
||||
self.__call__(v)
|
||||
return
|
||||
|
||||
data_placeholders = list(re.findall(r'({[\w\s]*})', value, re.X))
|
||||
invalid_placeholders = []
|
||||
for placeholder in data_placeholders:
|
||||
if placeholder not in self.limit_value:
|
||||
invalid_placeholders.append(placeholder)
|
||||
if invalid_placeholders:
|
||||
raise ValidationError(
|
||||
_('Invalid placeholder(s): %(value)s'),
|
||||
code='invalid',
|
||||
params={'value': ", ".join(invalid_placeholders,)})
|
||||
|
||||
def clean(self, x):
|
||||
return x
|
||||
|
||||
|
||||
class BlacklistValidator:
|
||||
|
||||
@@ -29,14 +29,14 @@ def contextprocessor(request):
|
||||
'DEBUG': settings.DEBUG,
|
||||
}
|
||||
_html_head = []
|
||||
if hasattr(request, 'event') and request.user.is_authenticated:
|
||||
if hasattr(request, 'event'):
|
||||
for receiver, response in html_head.send(request.event, request=request):
|
||||
_html_head.append(response)
|
||||
ctx['html_head'] = "".join(_html_head)
|
||||
|
||||
_js_payment_weekdays_disabled = '[]'
|
||||
_nav_event = []
|
||||
if getattr(request, 'event', None) and hasattr(request, 'organizer') and request.user.is_authenticated:
|
||||
if getattr(request, 'event', None) and hasattr(request, 'organizer'):
|
||||
for receiver, response in nav_event.send(request.event, request=request):
|
||||
_nav_event += response
|
||||
if request.event.settings.get('payment_term_weekdays'):
|
||||
@@ -61,16 +61,15 @@ def contextprocessor(request):
|
||||
ctx['js_payment_weekdays_disabled'] = _js_payment_weekdays_disabled
|
||||
|
||||
_nav_global = []
|
||||
if not hasattr(request, 'event') and request.user.is_authenticated:
|
||||
if not hasattr(request, 'event'):
|
||||
for receiver, response in nav_global.send(request, request=request):
|
||||
_nav_global += response
|
||||
|
||||
ctx['nav_global'] = sorted(_nav_global, key=lambda n: n['label'])
|
||||
|
||||
_nav_topbar = []
|
||||
if request.user.is_authenticated:
|
||||
for receiver, response in nav_topbar.send(request, request=request):
|
||||
_nav_topbar += response
|
||||
for receiver, response in nav_topbar.send(request, request=request):
|
||||
_nav_topbar += response
|
||||
ctx['nav_topbar'] = sorted(_nav_topbar, key=lambda n: n['label'])
|
||||
|
||||
ctx['js_datetime_format'] = get_javascript_format('DATETIME_INPUT_FORMATS')
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import os
|
||||
|
||||
from django import forms
|
||||
from django.utils.formats import get_format
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ...base.forms import I18nModelForm
|
||||
@@ -100,34 +98,3 @@ class SlugWidget(forms.TextInput):
|
||||
ctx = super().get_context(name, value, attrs)
|
||||
ctx['pre'] = self.prefix
|
||||
return ctx
|
||||
|
||||
|
||||
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
|
||||
|
||||
def __init__(self, attrs=None, date_format=None, time_format=None):
|
||||
attrs = attrs or {}
|
||||
if 'placeholder' in attrs:
|
||||
del attrs['placeholder']
|
||||
date_attrs = dict(attrs)
|
||||
time_attrs = dict(attrs)
|
||||
date_attrs.setdefault('class', 'form-control splitdatetimepart')
|
||||
time_attrs.setdefault('class', 'form-control splitdatetimepart')
|
||||
date_attrs['class'] += ' datepickerfield'
|
||||
time_attrs['class'] += ' timepickerfield'
|
||||
time_attrs['class'] += ' timepickerfield'
|
||||
|
||||
df = date_format or get_format('DATE_INPUT_FORMATS')[0]
|
||||
date_attrs['placeholder'] = now().replace(
|
||||
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
||||
).strftime(df)
|
||||
tf = time_format or get_format('TIME_INPUT_FORMATS')[0]
|
||||
time_attrs['placeholder'] = now().replace(
|
||||
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
||||
).strftime(tf)
|
||||
|
||||
widgets = (
|
||||
forms.DateInput(attrs=date_attrs, format=date_format),
|
||||
forms.TimeInput(attrs=time_attrs, format=time_format),
|
||||
)
|
||||
# Skip one hierarchy level
|
||||
forms.MultiWidget.__init__(self, widgets, attrs)
|
||||
|
||||
@@ -8,15 +8,15 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
from pytz import common_timezones, timezone
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.forms import I18nModelForm, SettingsForm
|
||||
from pretix.base.models import Event, Organizer, TaxRule
|
||||
from pretix.base.models.event import EventMetaValue
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.control.forms import (
|
||||
ExtFileField, SlugWidget, SplitDateTimePickerWidget,
|
||||
)
|
||||
from pretix.base.validators import PlaceholderValidator
|
||||
from pretix.control.forms import ExtFileField, SlugWidget
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.style import get_fonts
|
||||
from pretix.base import email
|
||||
|
||||
|
||||
class EventWizardFoundationForm(forms.Form):
|
||||
@@ -48,8 +48,6 @@ class EventWizardFoundationForm(forms.Form):
|
||||
empty_label=None,
|
||||
required=True
|
||||
)
|
||||
if len(self.fields['organizer'].choices) == 1:
|
||||
self.fields['organizer'].initial = self.fields['organizer'].queryset.first()
|
||||
|
||||
|
||||
class EventWizardBasicsForm(I18nModelForm):
|
||||
@@ -58,7 +56,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
}
|
||||
timezone = forms.ChoiceField(
|
||||
choices=((a, a) for a in common_timezones),
|
||||
label=_("Event timezone"),
|
||||
label=_("Default timezone"),
|
||||
)
|
||||
locale = forms.ChoiceField(
|
||||
choices=settings.LANGUAGES,
|
||||
@@ -84,18 +82,14 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
'presale_end',
|
||||
'location',
|
||||
]
|
||||
field_classes = {
|
||||
'date_from': forms.SplitDateTimeField,
|
||||
'date_to': forms.SplitDateTimeField,
|
||||
'presale_start': forms.SplitDateTimeField,
|
||||
'presale_end': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'date_from': SplitDateTimePickerWidget(),
|
||||
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-date_from_0'}),
|
||||
'presale_start': SplitDateTimePickerWidget(),
|
||||
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-presale_start_0'}),
|
||||
'slug': SlugWidget,
|
||||
'date_from': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'date_to': forms.DateTimeInput(attrs={'class': 'datetimepicker',
|
||||
'data-date-after': '#id_basics-date_from'}),
|
||||
'presale_start': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'presale_end': forms.DateTimeInput(attrs={'class': 'datetimepicker',
|
||||
'data-date-after': '#id_basics-presale_start'}),
|
||||
'slug': SlugWidget
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -107,9 +101,6 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
self.initial['timezone'] = get_current_timezone_name()
|
||||
self.fields['locale'].choices = [(a, b) for a, b in settings.LANGUAGES if a in self.locales]
|
||||
self.fields['location'].widget.attrs['rows'] = '3'
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
self.fields['slug'].widget.prefix = build_absolute_uri(self.organizer, 'presale:organizer.index')
|
||||
if self.has_subevents:
|
||||
del self.fields['presale_start']
|
||||
@@ -199,9 +190,6 @@ class EventUpdateForm(I18nModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['slug'].widget.attrs['readonly'] = 'readonly'
|
||||
self.fields['location'].widget.attrs['rows'] = '3'
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@@ -218,19 +206,14 @@ class EventUpdateForm(I18nModelForm):
|
||||
'presale_end',
|
||||
'location',
|
||||
]
|
||||
field_classes = {
|
||||
'date_from': forms.SplitDateTimeField,
|
||||
'date_to': forms.SplitDateTimeField,
|
||||
'date_admission': forms.SplitDateTimeField,
|
||||
'presale_start': forms.SplitDateTimeField,
|
||||
'presale_end': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'date_from': SplitDateTimePickerWidget(),
|
||||
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}),
|
||||
'date_admission': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}),
|
||||
'presale_start': SplitDateTimePickerWidget(),
|
||||
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}),
|
||||
'date_from': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'date_to': forms.DateTimeInput(attrs={'class': 'datetimepicker', 'data-date-after': '#id_date_from'}),
|
||||
'date_admission': forms.DateTimeInput(attrs={'class': 'datetimepicker',
|
||||
'data-date-default': '#id_date_from'}),
|
||||
'presale_start': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'presale_end': forms.DateTimeInput(attrs={'class': 'datetimepicker',
|
||||
'data-date-after': '#id_presale_start'}),
|
||||
}
|
||||
|
||||
|
||||
@@ -358,13 +341,13 @@ class EventSettingsForm(SettingsForm):
|
||||
label=_("Imprint URL"),
|
||||
required=False,
|
||||
)
|
||||
confirm_text = I18nFormField(
|
||||
confirm_text = forms.CharField(
|
||||
label=_('Confirmation text'),
|
||||
help_text=_('This text needs to be confirmed by the user before a purchase is possible. You could for example '
|
||||
'link your terms of service here. If you use the Pages feature to publish your terms of service, '
|
||||
'you don\'t need this setting since you can configure it there.'),
|
||||
required=False,
|
||||
widget=I18nTextarea
|
||||
widget=forms.Textarea(attrs={"rows": 3})
|
||||
)
|
||||
contact_mail = forms.EmailField(
|
||||
label=_("Contact address"),
|
||||
@@ -393,14 +376,6 @@ class EventSettingsForm(SettingsForm):
|
||||
})
|
||||
return data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['confirm_text'].widget.attrs['rows'] = '3'
|
||||
self.fields['confirm_text'].widget.attrs['placeholder'] = _(
|
||||
'e.g. I hereby confirm that I have read and agree with the event organizer\'s terms of service '
|
||||
'and agree with them.'
|
||||
)
|
||||
|
||||
|
||||
class PaymentSettingsForm(SettingsForm):
|
||||
payment_term_days = forms.IntegerField(
|
||||
@@ -552,51 +527,25 @@ class InvoiceSettingsForm(SettingsForm):
|
||||
choices=[]
|
||||
)
|
||||
invoice_address_from = forms.CharField(
|
||||
widget=forms.Textarea(attrs={
|
||||
'rows': 5,
|
||||
'placeholder': _(
|
||||
'Sample Event Company\n'
|
||||
'Albert Einstein Road 52\n'
|
||||
'12345 Samplecity'
|
||||
)
|
||||
}),
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={'rows': 5}), required=False,
|
||||
label=_("Your address"),
|
||||
help_text=_("Will be printed as the sender on invoices. Be sure to include relevant details required in "
|
||||
"your jurisdiction.")
|
||||
"your jurisdiction (e.g. your VAT ID).")
|
||||
)
|
||||
invoice_introductory_text = I18nFormField(
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': 3,
|
||||
'placeholder': _(
|
||||
'e.g. With this document, we sent you the invoice for your ticket order.'
|
||||
)
|
||||
}},
|
||||
required=False,
|
||||
label=_("Introductory text"),
|
||||
help_text=_("Will be printed on every invoice above the invoice rows.")
|
||||
)
|
||||
invoice_additional_text = I18nFormField(
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': 3,
|
||||
'placeholder': _(
|
||||
'e.g. Thank you for your purchase! You can find more information on the event at ...'
|
||||
)
|
||||
}},
|
||||
required=False,
|
||||
label=_("Additional text"),
|
||||
help_text=_("Will be printed on every invoice below the invoice total.")
|
||||
)
|
||||
invoice_footer_text = I18nFormField(
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': 5,
|
||||
'placeholder': _(
|
||||
'e.g. your bank details, legal details like your VAT ID, registration numbers, etc.'
|
||||
)
|
||||
}},
|
||||
required=False,
|
||||
label=_("Footer"),
|
||||
help_text=_("Will be printed centered and in a smaller font at the end of every invoice page.")
|
||||
@@ -633,64 +582,36 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Sender address"),
|
||||
help_text=_("Sender address for outgoing emails")
|
||||
)
|
||||
|
||||
mail_text_signature = I18nFormField(
|
||||
label=_("Signature"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("This will be attached to every email. Available placeholders: {event}"),
|
||||
validators=[PlaceholderValidator(['{event}'])],
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': '4',
|
||||
'placeholder': _(
|
||||
'e.g. your contact details'
|
||||
)
|
||||
}}
|
||||
validators=[PlaceholderValidator(['{event}'])]
|
||||
)
|
||||
|
||||
mail_text_order_placed = I18nFormField(
|
||||
mail_text_order_placed = email.mail_text_order_placed.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {payment_info}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total}', '{currency}', '{date}', '{payment_info}',
|
||||
'{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_order_paid = I18nFormField(
|
||||
mail_text_order_paid = email.mail_text_order_paid.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}, {payment_info}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}', '{payment_info}'])]
|
||||
)
|
||||
mail_text_order_free = I18nFormField(
|
||||
mail_text_order_free = email.mail_text_order_free.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_order_changed = I18nFormField(
|
||||
mail_text_order_changed = email.mail_text_order_changed.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_resend_link = I18nFormField(
|
||||
mail_text_resend_link = email.mail_text_resend_link.formfield(
|
||||
label=_("Text (sent by admin)"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_resend_all_links = I18nFormField(
|
||||
mail_text_resend_all_links = email.mail_text_resend_all_links.formfield(
|
||||
label=_("Text (requested by user)"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {orders}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{orders}'])]
|
||||
)
|
||||
mail_days_order_expire_warning = forms.IntegerField(
|
||||
label=_("Number of days"),
|
||||
@@ -699,42 +620,25 @@ class MailSettingsForm(SettingsForm):
|
||||
help_text=_("This email will be sent out this many days before the order expires. If the "
|
||||
"value is 0, the mail will never be sent.")
|
||||
)
|
||||
mail_text_order_expire_warning = I18nFormField(
|
||||
mail_text_order_expire_warning = email.mail_text_order_expire_warning.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {expire_date}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{expire_date}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_waiting_list = I18nFormField(
|
||||
mail_text_waiting_list = email.mail_text_waiting_list.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {product}, {hours}, {code}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{product}', '{hours}', '{code}'])]
|
||||
)
|
||||
mail_text_order_canceled = I18nFormField(
|
||||
mail_text_order_canceled = email.mail_text_order_canceled.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {code}, {url}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{code}', '{url}'])]
|
||||
)
|
||||
mail_text_order_custom_mail = I18nFormField(
|
||||
mail_text_order_custom_mail = email.mail_text_order_custom_mail.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_download_reminder = I18nFormField(
|
||||
mail_text_download_reminder = email.mail_text_download_reminder.formfield(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}'])]
|
||||
)
|
||||
mail_days_download_reminder = forms.IntegerField(
|
||||
label=_("Number of days"),
|
||||
@@ -750,17 +654,14 @@ class MailSettingsForm(SettingsForm):
|
||||
)
|
||||
smtp_host = forms.CharField(
|
||||
label=_("Hostname"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'mail.example.org'})
|
||||
required=False
|
||||
)
|
||||
smtp_port = forms.IntegerField(
|
||||
label=_("Port"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'e.g. 587, 465, 25, ...'})
|
||||
required=False
|
||||
)
|
||||
smtp_username = forms.CharField(
|
||||
label=_("Username"),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'myuser@example.org'}),
|
||||
required=False
|
||||
)
|
||||
smtp_password = forms.CharField(
|
||||
|
||||
@@ -197,8 +197,7 @@ class OrderSearchFilterForm(OrderFilterForm):
|
||||
class SubEventFilterForm(FilterForm):
|
||||
orders = {
|
||||
'date_from': 'date_from',
|
||||
'active': 'active',
|
||||
'sum_quota_available': 'sum_quota_available'
|
||||
'active': 'active'
|
||||
}
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
@@ -259,8 +258,7 @@ class EventFilterForm(FilterForm):
|
||||
'organizer': 'organizer__name',
|
||||
'date_from': 'order_from',
|
||||
'date_to': 'order_to',
|
||||
'live': 'live',
|
||||
'sum_quota_available': 'sum_quota_available'
|
||||
'live': 'live'
|
||||
}
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
|
||||
@@ -12,7 +12,6 @@ from pretix.base.models import (
|
||||
Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.models.items import ItemAddOn
|
||||
from pretix.control.forms import SplitDateTimePickerWidget
|
||||
|
||||
|
||||
class CategoryForm(I18nModelForm):
|
||||
@@ -150,18 +149,14 @@ class ItemCreateForm(I18nModelForm):
|
||||
)
|
||||
|
||||
if not self.event.has_subevents:
|
||||
choices = [
|
||||
(self.NONE, _("Do not add to a quota now")),
|
||||
(self.EXISTING, _("Add product to an existing quota")),
|
||||
(self.NEW, _("Create a new quota for this product"))
|
||||
]
|
||||
if not self.event.quotas.exists():
|
||||
choices.remove(choices[1])
|
||||
|
||||
self.fields['quota_option'] = forms.ChoiceField(
|
||||
label=_("Quota options"),
|
||||
widget=forms.RadioSelect,
|
||||
choices=choices,
|
||||
choices=(
|
||||
(self.NONE, _("Do not add to a quota now")),
|
||||
(self.EXISTING, _("Add product to an existing quota")),
|
||||
(self.NEW, _("Create a new quota for this product"))
|
||||
),
|
||||
initial=self.NONE,
|
||||
required=False
|
||||
)
|
||||
@@ -183,7 +178,7 @@ class ItemCreateForm(I18nModelForm):
|
||||
self.fields['quota_add_new_size'] = forms.IntegerField(
|
||||
min_value=0,
|
||||
label=_("Size"),
|
||||
widget=forms.TextInput(attrs={'placeholder': _("Number of tickets")}),
|
||||
widget=forms.TextInput(attrs={'placeholder': _("New quota size")}),
|
||||
help_text=_("Leave empty for an unlimited number of tickets."),
|
||||
required=False
|
||||
)
|
||||
@@ -268,11 +263,6 @@ class ItemUpdateForm(I18nModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['category'].queryset = self.instance.event.categories.all()
|
||||
self.fields['tax_rule'].queryset = self.instance.event.tax_rules.all()
|
||||
self.fields['description'].widget.attrs['placeholder'] = _(
|
||||
'e.g. This reduced price is available for full-time students, jobless and people '
|
||||
'over 65. This ticket includes access to all parts of the event, except the VIP '
|
||||
'area.'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
@@ -296,13 +286,9 @@ class ItemUpdateForm(I18nModelForm):
|
||||
'min_per_order',
|
||||
'checkin_attention'
|
||||
]
|
||||
field_classes = {
|
||||
'available_from': forms.SplitDateTimeField,
|
||||
'available_until': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'available_from': SplitDateTimePickerWidget(),
|
||||
'available_until': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_available_from_0'}),
|
||||
'available_from': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'available_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ from django.utils.formats import localize
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator
|
||||
from pretix.base.email import mail_text_order_custom_mail
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models import (
|
||||
InvoiceAddress, Item, ItemAddOn, Order, OrderPosition,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.validators import PlaceholderValidator
|
||||
|
||||
|
||||
class ExtendForm(I18nModelForm):
|
||||
@@ -269,13 +271,9 @@ class OrderMailForm(forms.Form):
|
||||
initial=order.email
|
||||
)
|
||||
self.fields['sendto'].widget.attrs['readonly'] = 'readonly'
|
||||
self.fields['message'] = forms.CharField(
|
||||
self.fields['message'] = mail_text_order_custom_mail.formfield(
|
||||
label=_("Message"),
|
||||
required=True,
|
||||
widget=forms.Textarea,
|
||||
initial=order.event.settings.mail_text_order_custom_mail.localize(order.locale),
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
|
||||
@@ -9,7 +8,6 @@ from pretix.base.forms import I18nModelForm, SettingsForm
|
||||
from pretix.base.models import Organizer, Team
|
||||
from pretix.control.forms import ExtFileField
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
|
||||
class OrganizerForm(I18nModelForm):
|
||||
@@ -115,6 +113,20 @@ class TeamForm(forms.ModelForm):
|
||||
|
||||
class OrganizerSettingsForm(SettingsForm):
|
||||
|
||||
locales = forms.MultipleChoiceField(
|
||||
choices=settings.LANGUAGES,
|
||||
label=_("Use languages"),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=_('Choose all languages that your organizer homepage should be available in.')
|
||||
)
|
||||
|
||||
organizer_homepage_text = I18nFormField(
|
||||
label=_('Homepage text'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_('This will be displayed on the organizer homepage.')
|
||||
)
|
||||
|
||||
organizer_info_text = I18nFormField(
|
||||
label=_('Info text'),
|
||||
required=False,
|
||||
@@ -122,23 +134,6 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
help_text=_('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.')
|
||||
)
|
||||
|
||||
|
||||
class OrganizerDisplaySettingsForm(SettingsForm):
|
||||
primary_color = forms.CharField(
|
||||
label=_("Primary color"),
|
||||
required=False,
|
||||
validators=[
|
||||
RegexValidator(regex='^#[0-9a-fA-F]{6}$',
|
||||
message=_('Please enter the hexadecimal code of a color, e.g. #990000.'))
|
||||
],
|
||||
widget=forms.TextInput(attrs={'class': 'colorpickerfield'})
|
||||
)
|
||||
organizer_homepage_text = I18nFormField(
|
||||
label=_('Homepage text'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_('This will be displayed on the organizer homepage.')
|
||||
)
|
||||
organizer_logo_image = ExtFileField(
|
||||
label=_('Logo image'),
|
||||
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
|
||||
@@ -146,6 +141,7 @@ class OrganizerDisplaySettingsForm(SettingsForm):
|
||||
help_text=_('If you provide a logo image, we will by default not show your organization name '
|
||||
'in the page header. We will show your logo with a maximal height of 120 pixels.')
|
||||
)
|
||||
|
||||
event_list_type = forms.ChoiceField(
|
||||
label=_('Default overview style'),
|
||||
choices=(
|
||||
@@ -153,22 +149,3 @@ class OrganizerDisplaySettingsForm(SettingsForm):
|
||||
('calendar', _('Calendar'))
|
||||
)
|
||||
)
|
||||
locales = forms.MultipleChoiceField(
|
||||
choices=settings.LANGUAGES,
|
||||
label=_("Use languages"),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=_('Choose all languages that your organizer homepage should be available in.')
|
||||
)
|
||||
primary_font = forms.ChoiceField(
|
||||
label=_('Font'),
|
||||
choices=[
|
||||
('Open Sans', 'Open Sans')
|
||||
],
|
||||
help_text=_('Only respected by modern browsers.')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['primary_font'].choices += [
|
||||
(a, a) for a in get_fonts()
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from i18nfield.forms import I18nInlineFormSet
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.control.forms import SplitDateTimePickerWidget
|
||||
|
||||
|
||||
class SubEventForm(I18nModelForm):
|
||||
@@ -28,19 +27,13 @@ class SubEventForm(I18nModelForm):
|
||||
'location',
|
||||
'frontpage_text'
|
||||
]
|
||||
field_classes = {
|
||||
'date_from': forms.SplitDateTimeField,
|
||||
'date_to': forms.SplitDateTimeField,
|
||||
'date_admission': forms.SplitDateTimeField,
|
||||
'presale_start': forms.SplitDateTimeField,
|
||||
'presale_end': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'date_from': SplitDateTimePickerWidget(),
|
||||
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}),
|
||||
'date_admission': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}),
|
||||
'presale_start': SplitDateTimePickerWidget(),
|
||||
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}),
|
||||
'date_from': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'date_to': forms.DateTimeInput(attrs={'class': 'datetimepicker', 'data-date-after': '#id_date_from'}),
|
||||
'date_admission': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'presale_start': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
'presale_end': forms.DateTimeInput(attrs={'class': 'datetimepicker',
|
||||
'data-date-after': '#id_presale_start'}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models import Item, ItemVariation, Quota, Voucher
|
||||
from pretix.control.forms import SplitDateTimePickerWidget
|
||||
from pretix.control.signals import voucher_form_validation
|
||||
|
||||
|
||||
@@ -28,11 +27,8 @@ class VoucherForm(I18nModelForm):
|
||||
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
|
||||
'comment', 'max_usages', 'price_mode', 'subevent'
|
||||
]
|
||||
field_classes = {
|
||||
'valid_until': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'valid_until': SplitDateTimePickerWidget(),
|
||||
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -222,11 +218,8 @@ class VoucherBulkForm(VoucherForm):
|
||||
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
|
||||
'max_usages', 'price_mode', 'subevent'
|
||||
]
|
||||
field_classes = {
|
||||
'valid_until': forms.SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'valid_until': SplitDateTimePickerWidget(),
|
||||
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
|
||||
}
|
||||
labels = {
|
||||
'max_usages': _('Maximum usages per voucher')
|
||||
|
||||
@@ -64,17 +64,15 @@ class PermissionMiddleware(MiddlewareMixin):
|
||||
return self._login_redirect(request)
|
||||
|
||||
if not settings.PRETIX_LONG_SESSIONS or not request.session.get('pretix_auth_long_session', False):
|
||||
# If this logic is updated, make sure to also update the logic in pretix/api/auth/permission.py
|
||||
last_used = request.session.get('pretix_auth_last_used', time.time())
|
||||
if time.time() - request.session.get('pretix_auth_login_time', time.time()) > settings.PRETIX_SESSION_TIMEOUT_ABSOLUTE:
|
||||
logout(request)
|
||||
request.session['pretix_auth_login_time'] = 0
|
||||
return self._login_redirect(request)
|
||||
if url_name != 'user.reauth':
|
||||
if time.time() - last_used > settings.PRETIX_SESSION_TIMEOUT_RELATIVE:
|
||||
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
if time.time() - last_used > settings.PRETIX_SESSION_TIMEOUT_RELATIVE and url_name != 'user.reauth':
|
||||
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
|
||||
request.session['pretix_auth_last_used'] = int(time.time())
|
||||
request.session['pretix_auth_last_used'] = int(time.time())
|
||||
|
||||
if 'event' in url.kwargs and 'organizer' in url.kwargs:
|
||||
request.event = Event.objects.filter(
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<a href="{% url 'control:event.index' organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
{% if url_name == "event.index" %}class="active"{% endif %}>
|
||||
<i class="fa fa-dashboard fa-fw"></i>
|
||||
{% trans "Event dashboard" %}
|
||||
{% trans "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if 'can_change_event_settings' in request.eventpermset %}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
<p>{{ text }}</p>
|
||||
{% endif %}
|
||||
{% if button_text %}
|
||||
<p><a href="{{ button_url }}" class="btn btn-primary btn-lg">{{ button_text }}</a></p>
|
||||
<p><a href="{{ button_url }}" class="btn btn-default btn-lg">{{ button_text }}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1,25 +1,18 @@
|
||||
{% extends "pretixcontrol/event/settings_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load hierarkey_form %}
|
||||
{% block inside %}
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Event page" %}</legend>
|
||||
<legend>{% trans "Display settings" %}</legend>
|
||||
{% bootstrap_field form.primary_color layout="horizontal" %}
|
||||
{% bootstrap_field form.primary_font layout="horizontal" %}
|
||||
{% bootstrap_field form.logo_image layout="horizontal" %}
|
||||
{% bootstrap_field form.frontpage_text layout="horizontal" %}
|
||||
{% bootstrap_field form.show_variations_expanded layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Shop design" %}</legend>
|
||||
{% url "control:organizer.display" organizer=request.organizer.slug as org_url %}
|
||||
{% propagated request.event org_url "primary_color" "primary_font" %}
|
||||
{% bootstrap_field form.primary_color layout="horizontal" %}
|
||||
{% bootstrap_field form.primary_font layout="horizontal" %}
|
||||
{% endpropagated %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.slug layout="horizontal" %}
|
||||
{% bootstrap_field form.date_from layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_from layout="horizontal" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" %}
|
||||
{% bootstrap_field form.location layout="horizontal" %}
|
||||
{% bootstrap_field form.date_admission layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_admission layout="horizontal" %}
|
||||
{% bootstrap_field form.currency layout="horizontal" %}
|
||||
{% bootstrap_field form.is_public layout="horizontal" %}
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
{% bootstrap_field form.presale_start layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_start layout="horizontal" %}
|
||||
{% bootstrap_field sform.presale_start_show_date layout="horizontal" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" %}
|
||||
{% bootstrap_field sform.show_items_outside_presale_period layout="horizontal" %}
|
||||
{% bootstrap_field sform.last_order_modification_date layout="horizontal" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -3,30 +3,6 @@
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{{ request.event.name }}{% endblock %}
|
||||
{% block content %}
|
||||
{% if "congratulations" in request.GET %}
|
||||
<div class="thank-you">
|
||||
<span class="fa fa-check-circle"></span>
|
||||
|
||||
<h2>{% trans "Congratulations!" %}</h2>
|
||||
<p>
|
||||
<strong>{% trans "You just created an event!" %}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can now scroll down and modify the settings in more detail, if you want, or you can create your
|
||||
first product to start selling tickets right away!
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url "control:event.items.add" organizer=request.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default">
|
||||
{% trans "Create a first product" %}
|
||||
</a>
|
||||
</p>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h1>{% trans "Settings" %}</h1>
|
||||
<ul class="nav nav-pills">
|
||||
{% if 'can_change_event_settings' in request.eventpermset %}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.rate addon_after="%" layout="horizontal" %}
|
||||
{% bootstrap_field form.rate layout="horizontal" %}
|
||||
<legend>{% trans "Advanced settings" %}</legend>
|
||||
<div class="alert alert-warning">
|
||||
<span class="fa fa-w fa-legal fa-4x pull-left"></span>
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
{% block form %}
|
||||
{% endblock %}
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save pull-right">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}"
|
||||
class="btn btn-default btn-lg pull-left">
|
||||
{% trans "Back" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field form.date_from layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_from layout="horizontal" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" %}
|
||||
{% bootstrap_field form.location layout="horizontal" %}
|
||||
{% bootstrap_field form.currency layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate addon_after="%" layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Display settings" %}</legend>
|
||||
@@ -43,8 +43,8 @@
|
||||
{% if form.presale_start %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
{% bootstrap_field form.presale_start layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_start layout="horizontal" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
<th>
|
||||
{% trans "Event name" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Short form" %}
|
||||
<a href="?{% url_replace request 'ordering' '-slug' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'slug' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
{% if not hide_orga %}
|
||||
<th>
|
||||
{% trans "Organizer" %}
|
||||
@@ -62,16 +67,12 @@
|
||||
{% trans "Start date" %}
|
||||
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||
/
|
||||
</th>
|
||||
<th>
|
||||
{% trans "End date" %}
|
||||
<a href="?{% url_replace request 'ordering' '-date_to' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Quota available" %}
|
||||
<a href="?{% url_replace request 'ordering' '-sum_quota_available' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'sum_quota_available' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
{% trans "Status" %}
|
||||
<a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a>
|
||||
@@ -82,39 +83,26 @@
|
||||
<tbody>
|
||||
{% for e in events %}
|
||||
<tr>
|
||||
<td class="event-name-col">
|
||||
<td>
|
||||
<strong><a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
|
||||
<br><small>{{ e.slug }}</small>
|
||||
{% if e.has_subevents %}
|
||||
<span class="label label-default">{% trans "Series" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ e.slug }}</td>
|
||||
{% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %}
|
||||
<td class="event-date-col">
|
||||
<td>
|
||||
{% if e.has_subevents %}
|
||||
{{ e.min_from|default_if_none:""|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{{ e.min_from|default_if_none:"" }}
|
||||
{% else %}
|
||||
{{ e.get_short_date_from_display }}
|
||||
{% endif %}
|
||||
{% if e.has_subevents %}
|
||||
<span class="label label-default">{% trans "Series" %}</span>
|
||||
{% endif %}
|
||||
{% if e.settings.show_date_to and e.date_to %}
|
||||
<br> –
|
||||
{% if e.has_subevents %}
|
||||
{{ e.max_fromto|default_if_none:e.max_from|default_if_none:e.max_to|default_if_none:""|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{% else %}
|
||||
{{ e.get_short_date_to_display }}
|
||||
{% endif %}
|
||||
{{ e.get_date_from_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for q in e.first_quotas|slice:":3" %}
|
||||
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
|
||||
{% endfor %}
|
||||
{% if e.first_quotas|length > 3 %}
|
||||
<a href="{% url "control:event.items.quotas" organizer=e.organizer.slug event=e.slug %}"
|
||||
class="quotabox-more" data-toggle="tooltip" title="{% trans "More quotas" %}"
|
||||
data-placement="top">
|
||||
···
|
||||
</a>
|
||||
{% if e.has_subevents %}
|
||||
{{ e.max_fromto|default_if_none:e.max_from|default_if_none:e.max_to|default_if_none:"" }}
|
||||
{% else %}
|
||||
{{ e.get_date_from_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
<div class="quotabox" data-toggle="tooltip_html" data-placement="top"
|
||||
title="{% trans "Quota:" %} {{ q.name }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
|
||||
{% if q.size|default_if_none:"NONE" == "NONE" %}
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-success progress-bar-100">
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-{% if q.cached_avail.0 <= 10 or q.cached_avail.0 >= 100 %}danger{% else %}warning{% endif %} progress-bar-{{ q.inv_percent }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="numbers">
|
||||
{{ q.cached_avail.1|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,14 +17,14 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Price settings" %}</legend>
|
||||
{% bootstrap_field form.default_price addon_after=request.event.currency layout="horizontal" %}
|
||||
{% bootstrap_field form.default_price layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rule layout="horizontal" %}
|
||||
{% bootstrap_field form.free_price layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Availability" %}</legend>
|
||||
{% bootstrap_field form.available_from layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.available_until layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.available_from layout="horizontal" %}
|
||||
{% bootstrap_field form.available_until layout="horizontal" %}
|
||||
{% bootstrap_field form.max_per_order layout="horizontal" %}
|
||||
{% bootstrap_field form.min_per_order layout="horizontal" %}
|
||||
{% bootstrap_field form.require_voucher layout="horizontal" %}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.active layout='horizontal' %}
|
||||
{% bootstrap_field form.default_price addon_after=request.event.currency layout='horizontal' %}
|
||||
{% bootstrap_field form.default_price layout='horizontal' %}
|
||||
{% bootstrap_field form.description layout='horizontal' %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
{% bootstrap_field formset.empty_form.active layout='horizontal' %}
|
||||
{% bootstrap_field formset.empty_form.default_price addon_after=request.event.currency layout='horizontal' %}
|
||||
{% bootstrap_field formset.empty_form.default_price layout='horizontal' %}
|
||||
{% bootstrap_field formset.empty_form.description layout='horizontal' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<input name="{{ position.form.prefix }}-operation" type="radio" value="price"
|
||||
{% if position.form.operation.value == "price" %}checked="checked"{% endif %}>
|
||||
{% trans "Change price to" %}
|
||||
{% bootstrap_field position.form.price addon_after=request.event.currency layout='inline' %}
|
||||
{% bootstrap_field position.form.price layout='inline' %}
|
||||
{% if position.apply_tax %}
|
||||
{% if position.item.tax_rule and not position.item.tax_rule.price_includes_tax %}
|
||||
{% blocktrans trimmed with rate=position.item.tax_rule.rate name=position.item.tax_rule.name %}
|
||||
@@ -150,7 +150,7 @@
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.do layout='horizontal' %}
|
||||
{% bootstrap_field add_form.itemvar layout='horizontal' %}
|
||||
{% bootstrap_field add_form.price addon_after=request.event.currency layout='horizontal' %}
|
||||
{% bootstrap_field add_form.price layout='horizontal' %}
|
||||
{% if add_form.addon_to %}
|
||||
{% bootstrap_field add_form.addon_to layout='horizontal' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -26,13 +26,6 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'can_change_organizer_settings' in request.orgapermset %}
|
||||
<li {% if "organizer.display" in url_name %}class="active"{% endif %}>
|
||||
<a href="{% url "control:organizer.display" organizer=organizer.slug %}">
|
||||
{% trans "Display" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for nav in nav_organizer %}
|
||||
<li {% if nav.active %}class="active"{% endif %}>
|
||||
<a href="{{ nav.url }}">
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Organizer page" %}</legend>
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.locales layout="horizontal" %}
|
||||
{% bootstrap_field form.organizer_logo_image layout="horizontal" %}
|
||||
{% bootstrap_field form.organizer_homepage_text layout="horizontal" %}
|
||||
{% bootstrap_field form.event_list_type layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Shop design" %}</legend>
|
||||
<p class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
These settings will be used for the organizer page as well as for the default settings
|
||||
for all events in this account that do not have their own design settings.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field form.primary_color layout="horizontal" %}
|
||||
{% bootstrap_field form.primary_font layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -16,6 +16,14 @@
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Display settings" %}</legend>
|
||||
{% bootstrap_form_errors sform %}
|
||||
{% bootstrap_field sform.locales layout="horizontal" %}
|
||||
{% bootstrap_field sform.organizer_logo_image layout="horizontal" %}
|
||||
{% bootstrap_field sform.organizer_homepage_text layout="horizontal" %}
|
||||
{% bootstrap_field sform.event_list_type layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Other" %}</legend>
|
||||
{% bootstrap_form_errors sform %}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.active layout="horizontal" %}
|
||||
{% bootstrap_field form.date_from layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_from layout="horizontal" %}
|
||||
{% bootstrap_field form.date_to layout="horizontal" %}
|
||||
{% bootstrap_field form.location layout="horizontal" %}
|
||||
{% bootstrap_field form.date_admission layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.date_admission layout="horizontal" %}
|
||||
{% bootstrap_field form.frontpage_text layout="horizontal" %}
|
||||
{% if meta_forms %}
|
||||
<div class="form-group metadata-group">
|
||||
@@ -49,8 +49,8 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
{% bootstrap_field form.presale_start layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.presale_start layout="horizontal" %}
|
||||
{% bootstrap_field form.presale_end layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Quotas" %}</legend>
|
||||
|
||||
@@ -51,11 +51,6 @@
|
||||
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Quota available" %}
|
||||
<a href="?{% url_replace request 'ordering' '-sum_quota_available' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'sum_quota_available' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Status" %}
|
||||
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
||||
@@ -72,18 +67,6 @@
|
||||
{{ s.name }}</a></strong>
|
||||
</td>
|
||||
<td>{{ s.get_date_from_display }}</td>
|
||||
<td>
|
||||
{% for q in s.first_quotas|slice:":3" %}
|
||||
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
|
||||
{% endfor %}
|
||||
{% if s.first_quotas|length > 3 %}
|
||||
<a href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}"
|
||||
class="quotabox-more" data-toggle="tooltip" title="{% trans "More quotas" %}"
|
||||
data-placement="top">
|
||||
···
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not s.active %}
|
||||
<span class="label label-danger">{% trans "Disabled" %}</span>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Voucher details" %}</legend>
|
||||
{% bootstrap_field form.valid_until layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.valid_until layout="horizontal" %}
|
||||
{% bootstrap_field form.block_quota layout="horizontal" %}
|
||||
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<legend>{% trans "Voucher details" %}</legend>
|
||||
{% bootstrap_field form.code layout="horizontal" %}
|
||||
{% bootstrap_field form.max_usages layout="horizontal" %}
|
||||
{% bootstrap_field form.valid_until layout="horizontal" horizontal_field_class="col-md-9 splitdatetimerow" %}
|
||||
{% bootstrap_field form.valid_until layout="horizontal" %}
|
||||
{% bootstrap_field form.block_quota layout="horizontal" %}
|
||||
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
from django import template
|
||||
from django.template import Node
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class PropagatedNode(Node):
|
||||
def __init__(self, nodelist, event, field_names, url):
|
||||
self.nodelist = nodelist
|
||||
self.event = template.Variable(event)
|
||||
self.field_names = field_names
|
||||
self.url = template.Variable(url)
|
||||
|
||||
def render(self, context):
|
||||
event = self.event.resolve(context)
|
||||
url = self.url.resolve(context)
|
||||
body = self.nodelist.render(context)
|
||||
|
||||
if all([fn not in event.settings._cache() for fn in self.field_names]):
|
||||
body = """
|
||||
<div class="propagated-settings-box">
|
||||
<input type="hidden" name="_settings_ignore" value="{fnames}">
|
||||
<div class="propagated-settings-form blurred">
|
||||
{body}
|
||||
</div>
|
||||
<div class="propagated-settings-overlay">
|
||||
<h4><span class="fa fa-link"></span> {text_inh}</h4>
|
||||
<p>
|
||||
{text_expl}
|
||||
</p>
|
||||
<button class="btn btn-default" name="decouple" value="{fnames}" data-action="unlink">
|
||||
<span class="fa fa-unlink"></span> {text_unlink}
|
||||
</button>
|
||||
<a class="btn btn-default" href="{url}" target="_blank">
|
||||
<span class="fa fa-group"></span> {text_orga}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
""".format(
|
||||
body=body,
|
||||
text_inh=_("Organizer-level settings"),
|
||||
fnames=','.join(self.field_names),
|
||||
text_expl=_(
|
||||
'These settings are currently set on organizer level. This way, you can easily change them for '
|
||||
'all of your events at the same time. You can either go to the organizer settings to change them '
|
||||
'or decouple them from the organizer account to change them for this event individually.'
|
||||
),
|
||||
text_unlink=_('Change only for this event'),
|
||||
text_orga=_('Change for all events'),
|
||||
url=url
|
||||
)
|
||||
|
||||
return body
|
||||
|
||||
|
||||
@register.tag
|
||||
def propagated(parser, token):
|
||||
try:
|
||||
tag, event, url, *args = token.split_contents()
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError(
|
||||
"%r tag requires at least three arguments" % token.contents.split()[0]
|
||||
)
|
||||
|
||||
nodelist = parser.parse(('endpropagated',))
|
||||
parser.delete_first_token()
|
||||
return PropagatedNode(nodelist, event, [f[1:-1] for f in args], url)
|
||||
@@ -35,8 +35,6 @@ urlpatterns = [
|
||||
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(),
|
||||
name='organizer.display'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
|
||||
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
|
||||
|
||||
@@ -152,7 +152,7 @@ def quota_widgets(sender, subevent=None, **kwargs):
|
||||
widgets = []
|
||||
|
||||
for q in sender.quotas.filter(subevent=subevent):
|
||||
status, left = q.availability(allow_cache=True)
|
||||
status, left = q.availability()
|
||||
widgets.append({
|
||||
'content': NUM_WIDGET.format(num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
||||
text=_('{quota} left').format(quota=escape(q.name))),
|
||||
|
||||
@@ -328,23 +328,11 @@ class EventSettingsFormView(EventPermissionRequiredMixin, FormView):
|
||||
kwargs['obj'] = self.request.event
|
||||
return kwargs
|
||||
|
||||
def _save_decoupled(self, form):
|
||||
# Save fields that are currently only set via the organizer but should be decoupled
|
||||
fields = set()
|
||||
for f in self.request.POST.getlist("decouple"):
|
||||
fields |= set(f.split(","))
|
||||
for f in fields:
|
||||
if f not in form.fields:
|
||||
continue
|
||||
if f not in self.request.event.settings._cache():
|
||||
self.request.event.settings.set(f, self.request.event.settings.get(f))
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
self._save_decoupled(form)
|
||||
if form.has_changed():
|
||||
self.request.event.log_action(
|
||||
'pretix.event.settings', user=self.request.user, data={
|
||||
@@ -406,7 +394,6 @@ class DisplaySettings(EventSettingsViewMixin, EventSettingsFormView):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
self._save_decoupled(form)
|
||||
if form.has_changed():
|
||||
self.request.event.log_action(
|
||||
'pretix.event.settings', user=self.request.user, data={
|
||||
@@ -516,8 +503,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
'mail_text_waiting_list': ['event', 'url', 'product', 'hours', 'code'],
|
||||
'mail_text_order_canceled': ['code', 'event', 'url'],
|
||||
'mail_text_order_custom_mail': ['expire_date', 'event', 'code', 'date', 'url',
|
||||
'invoice_name', 'invoice_company'],
|
||||
'mail_text_download_reminder': ['event', 'url']
|
||||
'invoice_name', 'invoice_company']
|
||||
}
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -698,7 +698,6 @@ class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
'id': form.instance.pk
|
||||
}
|
||||
)
|
||||
form.instance.rebuild_cache()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import (
|
||||
F, IntegerField, Max, Min, OuterRef, Prefetch, Subquery, Sum,
|
||||
)
|
||||
from django.db.models import Max, Min
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
@@ -15,7 +14,7 @@ from django.views.generic import ListView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.models import Event, Organizer, Quota, Team
|
||||
from pretix.base.models import Event, Organizer, Team
|
||||
from pretix.control.forms.event import (
|
||||
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
|
||||
)
|
||||
@@ -44,22 +43,6 @@ class EventList(ListView):
|
||||
order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to'),
|
||||
)
|
||||
|
||||
sum_quota_available = Quota.objects.filter(
|
||||
event=OuterRef('pk'), subevent__isnull=True
|
||||
).order_by().values('event').annotate(
|
||||
s=Sum('cached_availability_number')
|
||||
).values(
|
||||
's'
|
||||
)
|
||||
|
||||
qs = qs.annotate(
|
||||
sum_quota_available=Subquery(sum_quota_available, output_field=IntegerField())
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
queryset=Quota.objects.filter(subevent__isnull=True).annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||
to_attr='first_quotas')
|
||||
)
|
||||
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
return qs
|
||||
@@ -71,18 +54,6 @@ class EventList(ListView):
|
||||
pk__in=self.request.user.teams.values_list('organizer', flat=True)
|
||||
).count()
|
||||
ctx['hide_orga'] = orga_c <= 1
|
||||
|
||||
for s in ctx['events']:
|
||||
s.first_quotas = s.first_quotas[:4]
|
||||
for q in s.first_quotas:
|
||||
q.cached_avail = (
|
||||
(q.cached_availability_state, q.cached_availability_number)
|
||||
if q.cached_availability_time is not None
|
||||
else q.availability(allow_cache=True)
|
||||
)
|
||||
if q.cached_avail[1] is not None:
|
||||
q.percent = round(q.cached_avail[1] / q.size * 100) if q.size > 0 else 0
|
||||
q.inv_percent = 100 - q.percent
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -186,10 +157,12 @@ class EventWizard(SessionWizardView):
|
||||
event.settings.set('locale', basics_data['locale'])
|
||||
event.settings.set('locales', foundation_data['locales'])
|
||||
|
||||
messages.success(self.request, _('The new event has been created. You can now adjust the event settings in '
|
||||
'detail.'))
|
||||
return redirect(reverse('control:event.settings', kwargs={
|
||||
'organizer': event.organizer.slug,
|
||||
'event': event.slug,
|
||||
}) + '?congratulations=1')
|
||||
}))
|
||||
|
||||
|
||||
class SlugRNG(OrganizerPermissionRequiredMixin, View):
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import (
|
||||
CreateView, DeleteView, DetailView, FormView, ListView, UpdateView,
|
||||
CreateView, DeleteView, DetailView, ListView, UpdateView,
|
||||
)
|
||||
|
||||
from pretix.base.models import Organizer, Team, TeamInvite, User
|
||||
@@ -18,13 +18,12 @@ from pretix.base.models.event import EventMetaProperty
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.control.forms.organizer import (
|
||||
EventMetaPropertyForm, OrganizerDisplaySettingsForm, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm,
|
||||
EventMetaPropertyForm, OrganizerForm, OrganizerSettingsForm,
|
||||
OrganizerUpdateForm, TeamForm,
|
||||
)
|
||||
from pretix.control.permissions import OrganizerPermissionRequiredMixin
|
||||
from pretix.control.signals import nav_organizer
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.presale.style import regenerate_organizer_css
|
||||
|
||||
|
||||
class OrganizerList(ListView):
|
||||
@@ -86,71 +85,6 @@ class OrganizerTeamView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
|
||||
context_object_name = 'organizer'
|
||||
|
||||
|
||||
class OrganizerSettingsFormView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
|
||||
model = Organizer
|
||||
permission = 'can_change_organizer_settings'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['obj'] = self.request.organizer
|
||||
return kwargs
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if form.has_changed():
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.settings', user=self.request.user, data={
|
||||
k: (form.cleaned_data.get(k).name
|
||||
if isinstance(form.cleaned_data.get(k), File)
|
||||
else form.cleaned_data.get(k))
|
||||
for k in form.changed_data
|
||||
}
|
||||
)
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return self.get(request)
|
||||
|
||||
|
||||
class OrganizerDisplaySettings(OrganizerSettingsFormView):
|
||||
model = Organizer
|
||||
form_class = OrganizerDisplaySettingsForm
|
||||
template_name = 'pretixcontrol/organizers/display.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:organizer.display', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if form.has_changed():
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.settings', user=self.request.user, data={
|
||||
k: (form.cleaned_data.get(k).name
|
||||
if isinstance(form.cleaned_data.get(k), File)
|
||||
else form.cleaned_data.get(k))
|
||||
for k in form.changed_data
|
||||
}
|
||||
)
|
||||
regenerate_organizer_css.apply_async(args=(self.request.organizer.pk,))
|
||||
messages.success(self.request, _('Your changes have been saved. Please note that it can '
|
||||
'take a short period of time until your changes become '
|
||||
'active.'))
|
||||
return redirect(self.get_success_url())
|
||||
else:
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return self.get(request)
|
||||
|
||||
|
||||
class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
model = Organizer
|
||||
form_class = OrganizerUpdateForm
|
||||
|
||||
@@ -3,8 +3,6 @@ import copy
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.forms import inlineformset_factory
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.utils.functional import cached_property
|
||||
@@ -31,21 +29,7 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
|
||||
permission = 'can_change_settings'
|
||||
|
||||
def get_queryset(self):
|
||||
sum_quota_available = Quota.objects.filter(
|
||||
subevent=OuterRef('pk')
|
||||
).order_by().values('subevent').annotate(
|
||||
s=Sum('cached_availability_number')
|
||||
).values(
|
||||
's'
|
||||
)
|
||||
|
||||
qs = self.request.event.subevents.annotate(
|
||||
sum_quota_available=Subquery(sum_quota_available, output_field=IntegerField())
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||
to_attr='first_quotas')
|
||||
)
|
||||
qs = self.request.event.subevents.all()
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
return qs
|
||||
@@ -53,17 +37,6 @@ class SubEventList(EventPermissionRequiredMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['filter_form'] = self.filter_form
|
||||
for s in ctx['subevents']:
|
||||
s.first_quotas = s.first_quotas[:4]
|
||||
for q in s.first_quotas:
|
||||
q.cached_avail = (
|
||||
(q.cached_availability_state, q.cached_availability_number)
|
||||
if q.cached_availability_time is not None
|
||||
else q.availability(allow_cache=True)
|
||||
)
|
||||
if q.cached_avail[1] is not None:
|
||||
q.percent = round(q.cached_avail[1] / q.size * 100) if q.size > 0 else 0
|
||||
q.inv_percent = 100 - q.percent
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
|
||||
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: 2017-10-07 16:25+0000\n"
|
||||
"POT-Creation-Date: 2017-09-27 11:21+0000\n"
|
||||
"PO-Revision-Date: 2017-08-27 09:35+0200\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
|
||||
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: 2017-10-07 16:25+0000\n"
|
||||
"POT-Creation-Date: 2017-09-27 11:21+0000\n"
|
||||
"PO-Revision-Date: 2017-08-27 09:35+0200\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
|
||||
@@ -19,18 +19,6 @@ class BankTransfer(BasePaymentProvider):
|
||||
form_field = I18nFormField(
|
||||
label=_('Bank account details'),
|
||||
widget=I18nTextarea,
|
||||
help_text=_('Include everything that your customers need to send you a bank transfer payment. Within SEPA '
|
||||
'countries, IBAN, BIC and account owner should suffice. If you have lots of international '
|
||||
'customers, they might also need your full address and your bank\'s full address.'),
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': '4',
|
||||
'placeholder': _(
|
||||
'e.g. IBAN: DE12 1234 5678 8765 4321\n'
|
||||
'BIC: GENEXAMPLE1\n'
|
||||
'Account owner: John Doe\n'
|
||||
'Name of Bank: Professional Banking Institute Ltd., London'
|
||||
)
|
||||
}}
|
||||
)
|
||||
return OrderedDict(
|
||||
list(super().settings_form_fields.items()) + [('bank_details', form_field)]
|
||||
|
||||
@@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
||||
name='AppConfiguration',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, max_length=190)),
|
||||
('key', models.CharField(db_index=True, max_length=190, unique=True)),
|
||||
('all_items', models.BooleanField(default=True)),
|
||||
('allow_search', models.BooleanField(default=True)),
|
||||
('show_info', models.BooleanField(default=True)),
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
class AppConfiguration(models.Model):
|
||||
event = models.ForeignKey('pretixbase.Event')
|
||||
key = models.CharField(max_length=190, db_index=True)
|
||||
key = models.CharField(max_length=190, unique=True, db_index=True)
|
||||
all_items = models.BooleanField(default=True, verbose_name=_('Can scan all products'))
|
||||
items = models.ManyToManyField('pretixbase.Item', blank=True, verbose_name=_('Can scan these products'))
|
||||
subevent = models.ForeignKey('pretixbase.SubEvent', null=True, blank=True,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from pretix.base.email import MailTemplateRenderer
|
||||
|
||||
mail_text_sendmail = MailTemplateRenderer(
|
||||
['expire_date', 'event', 'code', 'date', 'url', 'invoice_name', 'invoice_company']
|
||||
)
|
||||
|
||||
@@ -2,9 +2,9 @@ from django import forms
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||
|
||||
from pretix.base.forms import PlaceholderValidator
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.plugins.sendmail.email import mail_text_sendmail
|
||||
|
||||
|
||||
class MailForm(forms.Form):
|
||||
@@ -23,19 +23,11 @@ class MailForm(forms.Form):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['subject'] = I18nFormField(
|
||||
widget=I18nTextInput, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
locales=event.settings.get('locales')
|
||||
)
|
||||
self.fields['message'] = I18nFormField(
|
||||
self.fields['message'] = mail_text_sendmail.formfield(
|
||||
widget=I18nTextarea, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
choices = list(Order.STATUS_CHOICE)
|
||||
if not event.settings.get('payment_term_expire_automatically', as_type=bool):
|
||||
@@ -43,8 +35,7 @@ class MailForm(forms.Form):
|
||||
('overdue', _('pending with payment overdue'))
|
||||
)
|
||||
self.fields['sendto'] = forms.MultipleChoiceField(
|
||||
label=_("Send to customers with order status"),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
label=_("Send to"), widget=forms.CheckboxSelectMultiple,
|
||||
choices=choices
|
||||
)
|
||||
if event.has_subevents:
|
||||
|
||||
@@ -17,6 +17,7 @@ from pretix.base.models.event import SubEvent
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.plugins.sendmail.email import mail_text_sendmail
|
||||
|
||||
from . import forms
|
||||
|
||||
@@ -78,33 +79,24 @@ class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
|
||||
if self.request.POST.get("action") == "preview":
|
||||
for l in self.request.event.settings.locales:
|
||||
|
||||
with language(l):
|
||||
|
||||
context_dict = {
|
||||
'code': 'ORDER1234',
|
||||
'event': self.request.event.name,
|
||||
'date': date_format(now(), 'SHORT_DATE_FORMAT'),
|
||||
'expire_date': date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
|
||||
'url': build_absolute_uri(self.request.event, 'presale:event.order', kwargs={
|
||||
self.output[l] = []
|
||||
self.output[l].append(
|
||||
_('Subject: {subject}').format(subject=form.cleaned_data['subject'].localize(l)))
|
||||
preview_text = mail_text_sendmail.preview(
|
||||
text=form.cleaned_data['message'].localize(l),
|
||||
code='ORDER1234',
|
||||
event=self.request.event.name,
|
||||
date=date_format(now(), 'SHORT_DATE_FORMAT'),
|
||||
expire_date=date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
|
||||
url=build_absolute_uri(self.request.event, 'presale:event.order', kwargs={
|
||||
'order': 'ORDER1234',
|
||||
'secret': 'longrandomsecretabcdef123456'
|
||||
}),
|
||||
'invoice_name': _('John Doe'),
|
||||
'invoice_company': _('Sample Company LLC')
|
||||
}
|
||||
|
||||
self.output[l] = []
|
||||
|
||||
subject = form.cleaned_data['subject'].localize(l)
|
||||
preview_subject = subject.format_map(context_dict)
|
||||
self.output[l].append(
|
||||
_('Subject: {subject}').format(subject=preview_subject))
|
||||
|
||||
message = form.cleaned_data['message'].localize(l)
|
||||
preview_text = message.format_map(context_dict)
|
||||
invoice_name=_('John Doe'),
|
||||
invoice_company=_('Sample Company LLC'),
|
||||
)
|
||||
self.output[l].append(preview_text)
|
||||
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
for o in orders:
|
||||
|
||||
@@ -25,9 +25,6 @@ from pretix.presale.signals import (
|
||||
)
|
||||
from pretix.presale.views import CartMixin, get_cart, get_cart_total
|
||||
from pretix.presale.views.async import AsyncAction
|
||||
from pretix.presale.views.cart import (
|
||||
cart_session, create_empty_cart_id, get_or_create_cart_id,
|
||||
)
|
||||
from pretix.presale.views.questions import QuestionsViewMixin
|
||||
|
||||
|
||||
@@ -85,13 +82,9 @@ class BaseCheckoutFlowStep:
|
||||
if n:
|
||||
return n.get_step_url()
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
return cart_session(self.request)
|
||||
|
||||
@cached_property
|
||||
def invoice_address(self):
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
if not iapk:
|
||||
return InvoiceAddress()
|
||||
|
||||
@@ -264,7 +257,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
if not is_valid:
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
return self.do(self.request.event.id, data, get_or_create_cart_id(self.request),
|
||||
return self.do(self.request.event.id, data, self.request.session.session_key,
|
||||
invoice_address=self.invoice_address.pk)
|
||||
|
||||
|
||||
@@ -279,9 +272,9 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
@cached_property
|
||||
def contact_form(self):
|
||||
initial = {
|
||||
'email': self.cart_session.get('email', '')
|
||||
'email': self.request.session.get('email', '')
|
||||
}
|
||||
initial.update(self.cart_session.get('contact_form_data', {}))
|
||||
initial.update(self.request.session.get('contact_form_data', {}))
|
||||
return ContactForm(data=self.request.POST if self.request.method == "POST" else None,
|
||||
event=self.request.event,
|
||||
initial=initial)
|
||||
@@ -308,15 +301,15 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
messages.error(request,
|
||||
_("We had difficulties processing your input. Please review the errors below."))
|
||||
return self.render()
|
||||
self.cart_session['email'] = self.contact_form.cleaned_data['email']
|
||||
request.session['email'] = self.contact_form.cleaned_data['email']
|
||||
if request.event.settings.invoice_address_asked:
|
||||
addr = self.invoice_form.save()
|
||||
self.cart_session['invoice_address'] = addr.pk
|
||||
self.cart_session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
request.session['invoice_address_{}'.format(request.event.pk)] = addr.pk
|
||||
request.session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
|
||||
update_tax_rates(
|
||||
event=request.event,
|
||||
cart_id=get_or_create_cart_id(request),
|
||||
cart_id=request.session.session_key,
|
||||
invoice_address=self.invoice_form.instance
|
||||
)
|
||||
|
||||
@@ -326,11 +319,11 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
self.request = request
|
||||
try:
|
||||
emailval = EmailValidator()
|
||||
if 'email' not in self.cart_session:
|
||||
if 'email' not in request.session:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
return False
|
||||
emailval(self.cart_session.get('email'))
|
||||
emailval(request.session.get('email'))
|
||||
except ValidationError:
|
||||
if warn:
|
||||
messages.warning(request, _('Please enter a valid email address.'))
|
||||
@@ -411,7 +404,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
self.request = request
|
||||
for p in self.provider_forms:
|
||||
if p['provider'].identifier == request.POST.get('payment', ''):
|
||||
self.cart_session['payment'] = p['provider'].identifier
|
||||
request.session['payment'] = p['provider'].identifier
|
||||
resp = p['provider'].checkout_prepare(
|
||||
request,
|
||||
self.get_cart()
|
||||
@@ -429,18 +422,16 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['providers'] = self.provider_forms
|
||||
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
|
||||
ctx['selected'] = self.request.POST.get('payment', self.cart_session.get('payment', ''))
|
||||
if len(self.provider_forms) == 1:
|
||||
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
||||
ctx['selected'] = self.request.POST.get('payment', self.request.session.get('payment', ''))
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def payment_provider(self):
|
||||
return self.request.event.get_payment_providers().get(self.cart_session['payment'])
|
||||
return self.request.event.get_payment_providers().get(self.request.session['payment'])
|
||||
|
||||
def is_completed(self, request, warn=False):
|
||||
self.request = request
|
||||
if 'payment' not in self.cart_session or not self.payment_provider:
|
||||
if 'payment' not in request.session or not self.payment_provider:
|
||||
if warn:
|
||||
messages.error(request, _('The payment information you entered was incomplete.'))
|
||||
return False
|
||||
@@ -455,7 +446,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
def is_applicable(self, request):
|
||||
self.request = request
|
||||
if self._total_order_value == 0:
|
||||
self.cart_session['payment'] = 'free'
|
||||
request.session['payment'] = 'free'
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -485,7 +476,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
responses = contact_form_fields.send(self.event)
|
||||
for r, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
for key, value in response.items():
|
||||
v = self.cart_session.get('contact_form_data', {}).get(key)
|
||||
v = self.request.session.get('contact_form_data', {}).get(key)
|
||||
if v is True:
|
||||
v = _('Yes')
|
||||
elif v is False:
|
||||
@@ -504,7 +495,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
|
||||
@cached_property
|
||||
def payment_provider(self):
|
||||
return self.request.event.get_payment_providers().get(self.cart_session['payment'])
|
||||
return self.request.event.get_payment_providers().get(self.request.session['payment'])
|
||||
|
||||
def get(self, request):
|
||||
self.request = request
|
||||
@@ -529,17 +520,16 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
return redirect(self.get_error_url())
|
||||
|
||||
meta_info = {
|
||||
'contact_form_data': self.cart_session.get('contact_form_data', {})
|
||||
'contact_form_data': self.request.session.get('contact_form_data', {})
|
||||
}
|
||||
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
|
||||
meta_info.update(response)
|
||||
|
||||
return self.do(self.request.event.id, self.payment_provider.identifier,
|
||||
[p.id for p in self.positions], self.cart_session.get('email'),
|
||||
[p.id for p in self.positions], request.session.get('email'),
|
||||
translation.get_language(), self.invoice_address.pk, meta_info)
|
||||
|
||||
def get_success_message(self, value):
|
||||
create_empty_cart_id(self.request)
|
||||
return None
|
||||
|
||||
def get_success_url(self, value):
|
||||
|
||||
@@ -57,8 +57,6 @@ def contextprocessor(request):
|
||||
ctx['languages'] = [get_language_info(code) for code in request.event.settings.locales]
|
||||
|
||||
if hasattr(request, 'organizer'):
|
||||
if request.organizer.settings.presale_css_file and not hasattr(request, 'event'):
|
||||
ctx['css_file'] = default_storage.url(request.organizer.settings.presale_css_file)
|
||||
ctx['organizer_logo'] = request.organizer.settings.get('organizer_logo_image', as_type=str, default='')[7:]
|
||||
ctx['organizer_homepage_text'] = request.organizer.settings.get('organizer_homepage_text', as_type=LazyI18nString)
|
||||
ctx['organizer'] = request.organizer
|
||||
|
||||
+11
-41
@@ -11,22 +11,23 @@ from django.core.files.storage import default_storage
|
||||
from django.dispatch import Signal
|
||||
from django.templatetags.static import static as _static
|
||||
|
||||
from pretix.base.models import Event, Event_SettingsStore, Organizer
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.services.async import ProfiledTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
|
||||
logger = logging.getLogger('pretix.presale.style')
|
||||
affected_keys = ['primary_font', 'primary_color']
|
||||
|
||||
|
||||
def compile_scss(object):
|
||||
@app.task(base=ProfiledTask)
|
||||
def regenerate_css(event_id: int):
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
sassdir = os.path.join(settings.STATIC_ROOT, 'pretixpresale/scss')
|
||||
|
||||
def static(path):
|
||||
sp = _static(path)
|
||||
if not settings.MEDIA_URL.startswith("/") and sp.startswith("/"):
|
||||
domain = get_domain(object.organizer if isinstance(object, Event) else object)
|
||||
domain = get_domain(event.organizer)
|
||||
if domain:
|
||||
siteurlsplit = urlsplit(settings.SITE_URL)
|
||||
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
||||
@@ -37,16 +38,15 @@ def compile_scss(object):
|
||||
return '"{}"'.format(sp)
|
||||
|
||||
sassrules = []
|
||||
if object.settings.get('primary_color'):
|
||||
sassrules.append('$brand-primary: {};'.format(object.settings.get('primary_color')))
|
||||
if event.settings.get('primary_color'):
|
||||
sassrules.append('$brand-primary: {};'.format(event.settings.get('primary_color')))
|
||||
|
||||
font = object.settings.get('primary_font')
|
||||
font = event.settings.get('primary_font')
|
||||
if font != 'Open Sans':
|
||||
sassrules.append(get_font_stylesheet(font))
|
||||
sassrules.append(
|
||||
'$font-family-sans-serif: "{}", "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif !default'.format(
|
||||
font
|
||||
))
|
||||
sassrules.append('$font-family-sans-serif: "{}", "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif !default'.format(
|
||||
font
|
||||
))
|
||||
|
||||
sassrules.append('@import "main.scss";')
|
||||
|
||||
@@ -58,14 +58,6 @@ def compile_scss(object):
|
||||
custom_functions=cf
|
||||
)
|
||||
checksum = hashlib.sha1(css.encode('utf-8')).hexdigest()
|
||||
return css, checksum
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def regenerate_css(event_id: int):
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
css, checksum = compile_scss(event)
|
||||
|
||||
fname = '{}/{}/presale.{}.css'.format(
|
||||
event.organizer.slug, event.slug, checksum[:16]
|
||||
)
|
||||
@@ -76,28 +68,6 @@ def regenerate_css(event_id: int):
|
||||
event.settings.set('presale_css_checksum', checksum)
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def regenerate_organizer_css(organizer_id: int):
|
||||
organizer = Organizer.objects.get(pk=organizer_id)
|
||||
css, checksum = compile_scss(organizer)
|
||||
|
||||
fname = '{}/presale.{}.css'.format(
|
||||
organizer.slug, checksum[:16]
|
||||
)
|
||||
|
||||
if organizer.settings.get('presale_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
organizer.settings.set('presale_css_file', newname)
|
||||
organizer.settings.set('presale_css_checksum', checksum)
|
||||
|
||||
non_inherited_events = set(Event_SettingsStore.objects.filter(
|
||||
object__organizer=organizer, key__in=affected_keys
|
||||
).values_list('object_id', flat=True))
|
||||
for event in organizer.events.all():
|
||||
if event.pk not in non_inherited_events:
|
||||
regenerate_css.apply_async(args=(event.pk,))
|
||||
|
||||
|
||||
register_fonts = Signal()
|
||||
"""
|
||||
Return a dictionaries of the following structure. Paths should be relative to static root.
|
||||
|
||||
@@ -19,11 +19,6 @@ class CartMixin:
|
||||
"""
|
||||
return list(get_cart(self.request))
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
from pretix.presale.views.cart import cart_session
|
||||
return cart_session(self.request)
|
||||
|
||||
def get_cart(self, answers=False, queryset=None, order=None, downloads=False):
|
||||
if queryset:
|
||||
prefetch = []
|
||||
@@ -107,7 +102,7 @@ class CartMixin:
|
||||
if order:
|
||||
fees = order.fees.all()
|
||||
else:
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
ia = None
|
||||
if iapk:
|
||||
try:
|
||||
@@ -115,7 +110,7 @@ class CartMixin:
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
fees = get_fees(self.request.event, self.request, total, ia, self.cart_session.get('payment'))
|
||||
fees = get_fees(self.request.event, self.request, total, ia, self.request.session.get('payment'))
|
||||
|
||||
total += sum([f.value for f in fees])
|
||||
net_total += sum([f.net_value for f in fees])
|
||||
@@ -142,11 +137,9 @@ class CartMixin:
|
||||
|
||||
|
||||
def get_cart(request):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not hasattr(request, '_cart_cache'):
|
||||
request._cart_cache = CartPosition.objects.filter(
|
||||
cart_id=get_or_create_cart_id(request), event=request.event
|
||||
cart_id=request.session.session_key, event=request.event
|
||||
).order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
@@ -159,14 +152,12 @@ def get_cart(request):
|
||||
|
||||
|
||||
def get_cart_total(request):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not hasattr(request, '_cart_total_cache'):
|
||||
if hasattr(request, '_cart_cache'):
|
||||
request._cart_total_cache = sum(i.price for i in request._cart_cache)
|
||||
else:
|
||||
request._cart_total_cache = CartPosition.objects.filter(
|
||||
cart_id=get_or_create_cart_id(request), event=request.event
|
||||
cart_id=request.session.session_key, event=request.event
|
||||
).aggregate(sum=Sum('price'))['sum'] or 0
|
||||
return request._cart_total_cache
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db.models import Count, Prefetch, Q
|
||||
from django.http import FileResponse, Http404, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils import translation
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -40,13 +39,9 @@ class CartActionMixin:
|
||||
def get_error_url(self):
|
||||
return self.get_next_url()
|
||||
|
||||
@cached_property
|
||||
def cart_session(self):
|
||||
return cart_session(self.request)
|
||||
|
||||
@cached_property
|
||||
def invoice_address(self):
|
||||
iapk = self.cart_session.get('invoice_address')
|
||||
iapk = self.request.session.get('invoice_address_{}'.format(self.request.event.pk))
|
||||
if not iapk:
|
||||
return InvoiceAddress()
|
||||
|
||||
@@ -129,65 +124,19 @@ class CartActionMixin:
|
||||
return items
|
||||
|
||||
|
||||
def create_empty_cart_id(request):
|
||||
current_id = request.session.get('current_cart_event_{}'.format(request.event.pk))
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
del request.session['carts'][current_id]
|
||||
del request.session['current_cart_event_{}'.format(request.event.pk)]
|
||||
return get_or_create_cart_id(request)
|
||||
|
||||
|
||||
def get_or_create_cart_id(request):
|
||||
current_id = request.session.get('current_cart_event_{}'.format(request.event.pk))
|
||||
if current_id and current_id in request.session.get('carts', {}):
|
||||
return current_id
|
||||
else:
|
||||
cart_data = {}
|
||||
|
||||
while True:
|
||||
new_id = get_random_string(length=32)
|
||||
if not CartPosition.objects.filter(cart_id=new_id).exists():
|
||||
break
|
||||
|
||||
# Migrate legacy data
|
||||
legacy_pos = CartPosition.objects.filter(cart_id=request.session.session_key, event=request.event)
|
||||
if legacy_pos.exists():
|
||||
legacy_pos.update(cart_id=new_id)
|
||||
if 'invoice_address_{}'.format(request.event.pk) in request.session:
|
||||
cart_data['invoice_address'] = request.session['invoice_address_{}'.format(request.event.pk)]
|
||||
if 'email' in request.session:
|
||||
cart_data['email'] = request.session['email']
|
||||
if 'contact_form_data' in request.session:
|
||||
cart_data['contact_form_data'] = request.session['contact_form_data']
|
||||
if 'payment' in request.session:
|
||||
cart_data['payment'] = request.session['payment']
|
||||
|
||||
if 'carts' not in request.session:
|
||||
request.session['carts'] = {}
|
||||
request.session['carts'][new_id] = cart_data
|
||||
request.session['current_cart_event_{}'.format(request.event.pk)] = new_id
|
||||
return new_id
|
||||
|
||||
|
||||
def cart_session(request):
|
||||
request.session.modified = True
|
||||
cart_id = get_or_create_cart_id(request)
|
||||
return request.session['carts'][cart_id]
|
||||
|
||||
|
||||
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
task = remove_cart_position
|
||||
known_errortypes = ['CartError']
|
||||
|
||||
def get_success_message(self, value):
|
||||
if CartPosition.objects.filter(cart_id=get_or_create_cart_id(self.request)).exists():
|
||||
if CartPosition.objects.filter(cart_id=self.request.session.session_key).exists():
|
||||
return _('Your cart has been updated.')
|
||||
else:
|
||||
return _('Your cart is now empty.')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if 'id' in request.POST:
|
||||
return self.do(self.request.event.id, request.POST.get('id'), get_or_create_cart_id(self.request), translation.get_language())
|
||||
return self.do(self.request.event.id, request.POST.get('id'), self.request.session.session_key, translation.get_language())
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
return JsonResponse({
|
||||
@@ -205,7 +154,7 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
return _('Your cart is now empty.')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language())
|
||||
return self.do(self.request.event.id, self.request.session.session_key, translation.get_language())
|
||||
|
||||
|
||||
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
@@ -218,7 +167,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
items = self._items_from_post_data()
|
||||
if items:
|
||||
return self.do(self.request.event.id, items, get_or_create_cart_id(self.request), translation.get_language(),
|
||||
return self.do(self.request.event.id, items, self.request.session.session_key, translation.get_language(),
|
||||
self.invoice_address.pk)
|
||||
else:
|
||||
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||
@@ -350,7 +299,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
|
||||
redeemed_in_carts = CartPosition.objects.filter(
|
||||
Q(voucher=self.voucher) & Q(event=request.event) &
|
||||
(Q(expires__gte=now()) | Q(cart_id=get_or_create_cart_id(request)))
|
||||
(Q(expires__gte=now()) | Q(cart_id=request.session.session_key))
|
||||
)
|
||||
v_avail = self.voucher.max_usages - self.voucher.redeemed - redeemed_in_carts.count()
|
||||
if v_avail < 1:
|
||||
@@ -388,7 +337,7 @@ class AnswerDownload(EventViewMixin, View):
|
||||
answid = kwargs.get('answer')
|
||||
answer = get_object_or_404(
|
||||
QuestionAnswer,
|
||||
cartposition__cart_id=get_or_create_cart_id(self.request),
|
||||
cartposition__cart_id=self.request.session.session_key,
|
||||
id=answid
|
||||
)
|
||||
if not answer.file:
|
||||
|
||||
@@ -9,15 +9,13 @@ from pretix.base.services.cart import CartError
|
||||
from pretix.base.signals import validate_cart
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.checkoutflow import get_checkout_flow
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
|
||||
class CheckoutView(View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
||||
self.request = request
|
||||
cart_pos = CartPosition.objects.filter(
|
||||
cart_id=get_or_create_cart_id(request), event=self.request.event
|
||||
cart_id=self.request.session.session_key, event=self.request.event
|
||||
)
|
||||
|
||||
if not cart_pos.exists() and "async_id" not in request.GET:
|
||||
|
||||
@@ -9,9 +9,6 @@ $(document).ready(function () {
|
||||
$("#existing-quota-group").children()[fn]();
|
||||
} else if (v === "new") {
|
||||
hideAll(animate);
|
||||
if ($("#id_quota_add_new_name").val() === "") {
|
||||
$("#id_quota_add_new_name").val($("input[name^=name_]").first().val());
|
||||
}
|
||||
$("#new-quota-group").children()[fn]();
|
||||
} else {
|
||||
hideAll(animate);
|
||||
|
||||
@@ -88,9 +88,6 @@ $(function () {
|
||||
});
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="tooltip_html"]').tooltip({
|
||||
'html': true
|
||||
});
|
||||
|
||||
var url = document.location.toString();
|
||||
if (url.match('#')) {
|
||||
@@ -179,22 +176,11 @@ $(function () {
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
},
|
||||
}
|
||||
};
|
||||
if ($(this).is('[data-is-payment-date]'))
|
||||
opts["daysOfWeekDisabled"] = JSON.parse($("body").attr("data-payment-weekdays-disabled"));
|
||||
$(this).datetimepicker(opts);
|
||||
if ($(this).parent().is('.splitdatetimerow')) {
|
||||
$(this).on("dp.change", function (ev) {
|
||||
var $timepicker = $(this).closest(".splitdatetimerow").find(".timepickerfield");
|
||||
var date = $(this).data('DateTimePicker').date();
|
||||
if (date === null) {
|
||||
return;
|
||||
}
|
||||
date.set({'hour': 0, 'minute': 0, 'second': 0});
|
||||
$timepicker.data('DateTimePicker').date(date);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(".timepickerfield").each(function() {
|
||||
@@ -312,13 +298,4 @@ $(function () {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(".propagated-settings-box button[data-action=unlink]").click(function(ev) {
|
||||
var $box = $(this).closest(".propagated-settings-box");
|
||||
$box.find(".propagated-settings-overlay").fadeOut();
|
||||
$box.find("input[name=_settings_ignore]").attr("name", "decouple");
|
||||
$box.find(".propagated-settings-form").removeClass("blurred");
|
||||
ev.preventDefault();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -190,53 +190,3 @@ pre.mail-preview {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.splitdatetimerow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.help-block {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.splitdatetimepart {
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
|
||||
&.datepickerfield {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
&.timepickerfield {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.propagated-settings-box {
|
||||
position: relative;
|
||||
|
||||
.propagated-settings-overlay {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.propagated-settings-form.blurred {
|
||||
-webkit-filter: blur(2px);
|
||||
-moz-filter: blur(2px);
|
||||
-ms-filter: blur(2px);
|
||||
-o-filter: blur(2px);
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
@media (max-width: $screen-sm-max) {
|
||||
.propagated-settings-box {
|
||||
min-height: 250px;
|
||||
}
|
||||
}
|
||||
@@ -399,79 +399,3 @@ body.loading #wrapper {
|
||||
.fa.disabled {
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.event-name-col {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.quotabox {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 50px;
|
||||
.progress {
|
||||
height: 7px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.numbers {
|
||||
font-size: 10px;
|
||||
color: $text-muted;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
.progress-bar-success {
|
||||
background: lighten($brand-success, 20%);
|
||||
}
|
||||
}
|
||||
.quotabox-more {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
line-height: 20px;
|
||||
margin-top: -6px;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.thank-you {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.fa {
|
||||
font-size: 150px;
|
||||
line-height: 170px;
|
||||
display: block;
|
||||
color: $brand-success;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $brand-success;
|
||||
}
|
||||
}
|
||||
@media (min-width: $screen-md-min) {
|
||||
.thank-you {
|
||||
min-height: 170px;
|
||||
width: 70%;
|
||||
margin: auto;
|
||||
|
||||
.fa {
|
||||
float: left;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 158px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: $screen-sm-max) {
|
||||
.thank-you {
|
||||
text-align: center;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
|
||||
def add_cart_session(client, event, data):
|
||||
new_id = get_random_string(length=32)
|
||||
session = client.session
|
||||
session['current_cart_event_{}'.format(event.pk)] = new_id
|
||||
if 'carts' not in session:
|
||||
session['carts'] = {}
|
||||
session['carts'][new_id] = data
|
||||
session.save()
|
||||
return new_id
|
||||
|
||||
|
||||
def get_cart_session_key(client, event):
|
||||
cart_id = client.session.get('current_cart_event_{}'.format(event.pk))
|
||||
if cart_id:
|
||||
return cart_id
|
||||
else:
|
||||
return add_cart_session(client, event, {})
|
||||
@@ -43,17 +43,6 @@ def user():
|
||||
return User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_client(client, team, user):
|
||||
team.can_view_orders = True
|
||||
team.can_view_vouchers = True
|
||||
team.all_events = True
|
||||
team.save()
|
||||
team.members.add(user)
|
||||
client.force_authenticate(user=user)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def token_client(client, team):
|
||||
team.can_view_orders = True
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from django.test import override_settings
|
||||
|
||||
from pretix.base.models import Organizer
|
||||
|
||||
@@ -110,99 +107,3 @@ def test_token_event_permission_not_allowed(token_client, team, organizer, event
|
||||
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/{}/{}'.format(
|
||||
organizer.slug, event.slug, urlset[2]))
|
||||
assert resp.status_code in (404, 403)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_log_out_after_absolute_timeout(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dont_logout_before_absolute_timeout(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 + 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(PRETIX_LONG_SESSIONS=False)
|
||||
def test_ignore_long_session_if_disabled_in_config(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dont_logout_in_long_session(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_log_out_after_relative_timeout(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6
|
||||
session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dont_logout_before_relative_timeout(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6
|
||||
session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 + 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dont_logout_by_relative_in_long_session(user_client, team, organizer, event):
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 5
|
||||
session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_session_activity(user_client, team, organizer, event):
|
||||
t1 = int(time.time()) - 5
|
||||
session = user_client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 5
|
||||
session['pretix_auth_last_used'] = t1
|
||||
session.save()
|
||||
|
||||
response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug))
|
||||
assert response.status_code == 200
|
||||
|
||||
assert user_client.session['pretix_auth_last_used'] > t1
|
||||
|
||||
@@ -89,14 +89,3 @@ def test_send_mail_with_user_locale(env):
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].subject == 'Benutzer'
|
||||
assert 'The language code used for rendering this e-mail is de.' in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_placeholder(env):
|
||||
djmail.outbox = []
|
||||
event, user, organizer = env
|
||||
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', {"event": event}, event)
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [user.email]
|
||||
assert djmail.outbox[0].subject == 'Dummy Test subject'
|
||||
|
||||
@@ -566,26 +566,6 @@ class SessionTimeOutTest(TestCase):
|
||||
response = self.client.get('/control/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_log_out_after_relative_timeout_really_enforced(self):
|
||||
# Regression test added after a security problem in 1.9.1
|
||||
# The problem was that, once the relative timeout happened, the user was redirected
|
||||
# to /control/reauth/, but loading /control/reauth/ was already considered to be
|
||||
# "session activitiy". Therefore, after loding /control/reauth/, the session was no longer
|
||||
# in the timeout state and the user was able to access pages again without re-entering the
|
||||
# password.
|
||||
session = self.client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6
|
||||
session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60
|
||||
session.save()
|
||||
|
||||
response = self.client.get('/control/')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, '/control/reauth/?next=/control/')
|
||||
self.client.get('/control/reauth/?next=/control/')
|
||||
response = self.client.get('/control/')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_update_session_activity(self):
|
||||
t1 = int(time.time()) - 5
|
||||
session = self.client.session
|
||||
|
||||
@@ -50,21 +50,18 @@ class EventsTest(SoupTest):
|
||||
|
||||
def test_settings(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/settings/' % (self.orga1.slug, self.event1.slug))
|
||||
doc.select("[name=date_to_0]")[0]['value'] = "2013-12-30"
|
||||
doc.select("[name=date_to_1]")[0]['value'] = "17:00:00"
|
||||
doc.select("[name=date_to]")[0]['value'] = "2013-12-30 17:00:00"
|
||||
doc.select("[name=settings-max_items_per_order]")[0]['value'] = "12"
|
||||
|
||||
doc = self.post_doc('/control/event/%s/%s/settings/' % (self.orga1.slug, self.event1.slug),
|
||||
extract_form_fields(doc.select('.container-fluid form')[0]))
|
||||
assert len(doc.select(".alert-success")) > 0
|
||||
assert doc.select("[name=date_to_0]")[0]['value'] == "2013-12-30"
|
||||
assert doc.select("[name=date_to_1]")[0]['value'] == "17:00:00"
|
||||
assert doc.select("[name=date_to]")[0]['value'] == "2013-12-30 17:00:00"
|
||||
assert doc.select("[name=settings-max_items_per_order]")[0]['value'] == "12"
|
||||
|
||||
def test_settings_timezone(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/settings/' % (self.orga1.slug, self.event1.slug))
|
||||
doc.select("[name=date_to_0]")[0]['value'] = "2013-12-30"
|
||||
doc.select("[name=date_to_1]")[0]['value'] = "17:00:00"
|
||||
doc.select("[name=date_to]")[0]['value'] = "2013-12-30 17:00:00"
|
||||
doc.select("[name=settings-max_items_per_order]")[0]['value'] = "12"
|
||||
doc.select("[name=settings-timezone]")[0]['value'] = "Asia/Tokyo"
|
||||
doc.find('option', {"value": "Asia/Tokyo"})['selected'] = 'selected'
|
||||
@@ -74,8 +71,7 @@ class EventsTest(SoupTest):
|
||||
extract_form_fields(doc.select('.container-fluid form')[0]))
|
||||
assert len(doc.select(".alert-success")) > 0
|
||||
# date_to should not be changed even though the timezone is changed
|
||||
assert doc.select("[name=date_to_0]")[0]['value'] == "2013-12-30"
|
||||
assert doc.select("[name=date_to_1]")[0]['value'] == "17:00:00"
|
||||
assert doc.select("[name=date_to]")[0]['value'] == "2013-12-30 17:00:00"
|
||||
assert 'selected' in doc.find('option', {"value": "Asia/Tokyo"}).attrs
|
||||
assert doc.select("[name=settings-max_items_per_order]")[0]['value'] == "12"
|
||||
|
||||
@@ -229,37 +225,6 @@ class EventsTest(SoupTest):
|
||||
assert self.event1.settings.get('primary_color') == '#FF0000'
|
||||
mocked.assert_any_call(args=(self.event1.pk,))
|
||||
|
||||
def test_display_settings_do_not_override_parent(self):
|
||||
self.orga1.settings.primary_color = '#ff00ff'
|
||||
with mocker_context() as mocker:
|
||||
mocked = mocker.patch('pretix.presale.style.regenerate_css.apply_async')
|
||||
|
||||
doc = self.get_doc('/control/event/%s/%s/settings/display' % (self.orga1.slug, self.event1.slug))
|
||||
data = extract_form_fields(doc.select("form")[0])
|
||||
doc = self.post_doc('/control/event/%s/%s/settings/display' % (self.orga1.slug, self.event1.slug),
|
||||
data, follow=True)
|
||||
assert doc.select('.alert-success')
|
||||
self.event1.settings.flush()
|
||||
assert 'primary_color' not in self.event1.settings._cache()
|
||||
assert self.event1.settings.primary_color == self.orga1.settings.primary_color
|
||||
mocked.assert_any_call(args=(self.event1.pk,))
|
||||
|
||||
def test_display_settings_explicitly_override_parent(self):
|
||||
self.orga1.settings.primary_color = '#ff00ff'
|
||||
with mocker_context() as mocker:
|
||||
mocked = mocker.patch('pretix.presale.style.regenerate_css.apply_async')
|
||||
|
||||
doc = self.get_doc('/control/event/%s/%s/settings/display' % (self.orga1.slug, self.event1.slug))
|
||||
data = extract_form_fields(doc.select("form")[0])
|
||||
data['decouple'] = 'primary_color'
|
||||
doc = self.post_doc('/control/event/%s/%s/settings/display' % (self.orga1.slug, self.event1.slug),
|
||||
data, follow=True)
|
||||
assert doc.select('.alert-success')
|
||||
self.event1.settings.flush()
|
||||
assert 'primary_color' in self.event1.settings._cache()
|
||||
assert self.event1.settings.primary_color == self.orga1.settings.primary_color
|
||||
mocked.assert_any_call(args=(self.event1.pk,))
|
||||
|
||||
def test_email_settings(self):
|
||||
with mocker_context() as mocker:
|
||||
mocked = mocker.patch('pretix.base.email.CustomSMTPBackend.test')
|
||||
@@ -328,20 +293,16 @@ class EventsTest(SoupTest):
|
||||
'basics-name_0': '33C3',
|
||||
'basics-name_1': '33C3',
|
||||
'basics-slug': '31c3',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-location_1': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
@@ -364,20 +325,16 @@ class EventsTest(SoupTest):
|
||||
'basics-name_0': '33C3',
|
||||
'basics-name_1': '33C3',
|
||||
'basics-slug': '33c3',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-location_1': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-tax_rate': '19.00',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
|
||||
assert doc.select("#id_copy-copy_from_event_1")
|
||||
@@ -422,20 +379,16 @@ class EventsTest(SoupTest):
|
||||
'basics-name_0': '33C3',
|
||||
'basics-name_1': '33C3',
|
||||
'basics-slug': '33c3',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-location_1': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
self.post_doc('/control/events/add', {
|
||||
'event_wizard-current_step': 'copy',
|
||||
@@ -456,19 +409,15 @@ class EventsTest(SoupTest):
|
||||
'event_wizard-current_step': 'basics',
|
||||
'basics-name_0': '33C3',
|
||||
'basics-slug': '33c3',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '',
|
||||
'basics-date_to_1': '',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'UTC',
|
||||
'basics-presale_start_0': '',
|
||||
'basics-presale_start_1': '',
|
||||
'basics-presale_end_0': '',
|
||||
'basics-presale_end_1': '',
|
||||
'basics-presale_start': '',
|
||||
'basics-presale_end': '',
|
||||
})
|
||||
self.post_doc('/control/events/add', {
|
||||
'event_wizard-current_step': 'copy',
|
||||
@@ -500,19 +449,15 @@ class EventsTest(SoupTest):
|
||||
'event_wizard-current_step': 'basics',
|
||||
'basics-name_0': '33C3',
|
||||
'basics-slug': '33c3',
|
||||
'basics-date_from_0': '',
|
||||
'basics-date_from_1': '',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-20 11:00:00',
|
||||
'basics-presale_end': '2016-11-24 18:00:00',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
@@ -527,19 +472,15 @@ class EventsTest(SoupTest):
|
||||
'event_wizard-current_step': 'basics',
|
||||
'basics-name_0': '33C3',
|
||||
'basics-slug': '31c4',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-currency': '$',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
@@ -554,19 +495,15 @@ class EventsTest(SoupTest):
|
||||
'event_wizard-current_step': 'basics',
|
||||
'basics-name_0': '33C3',
|
||||
'basics-slug': '31c5',
|
||||
'basics-date_from_0': '2016-12-27',
|
||||
'basics-date_from_1': '10:00:00',
|
||||
'basics-date_to_0': '2016-12-30',
|
||||
'basics-date_to_1': '19:00:00',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-currency': 'ASD',
|
||||
'basics-tax_rate': '',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start_0': '2016-11-01',
|
||||
'basics-presale_start_1': '10:00:00',
|
||||
'basics-presale_end_0': '2016-11-30',
|
||||
'basics-presale_end_1': '18:00:00',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
@@ -606,13 +543,10 @@ class SubEventsTest(SoupTest):
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/add', {
|
||||
'name_0': 'SE2',
|
||||
'active': 'on',
|
||||
'date_from_0': '2017-07-01',
|
||||
'date_from_1': '10:00:00',
|
||||
'date_to_0': '2017-07-01',
|
||||
'date_to_1': '12:00:00',
|
||||
'date_from': '2017-07-01 10:00:00',
|
||||
'date_to': '2017-07-01 12:00:00',
|
||||
'location_0': 'Hamburg',
|
||||
'presale_start_0': '2017-06-20',
|
||||
'presale_start_1': '10:00:00',
|
||||
'presale_start': '2017-06-20 10:00:00',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
@@ -645,13 +579,10 @@ class SubEventsTest(SoupTest):
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk, {
|
||||
'name_0': 'SE2',
|
||||
'active': 'on',
|
||||
'date_from_0': '2017-07-01',
|
||||
'date_from_1': '10:00:00',
|
||||
'date_to_0': '2017-07-01',
|
||||
'date_to_1': '12:00:00',
|
||||
'date_from': '2017-07-01 10:00:00',
|
||||
'date_to': '2017-07-01 12:00:00',
|
||||
'location_0': 'Hamburg',
|
||||
'presale_start_0': '2017-06-20',
|
||||
'presale_start_1': '10:00:00',
|
||||
'presale_start': '2017-06-20 10:00:00',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import datetime
|
||||
|
||||
from tests.base import SoupTest, extract_form_fields
|
||||
|
||||
from pretix.base.models import Event, Organizer, Team, User
|
||||
|
||||
|
||||
class OrganizerTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
|
||||
self.orga2 = Organizer.objects.create(name='MRM', slug='mrm')
|
||||
self.event1 = Event.objects.create(
|
||||
organizer=self.orga1, name='30C3', slug='30c3',
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
plugins='pretix.plugins.banktransfer,tests.testdummy'
|
||||
)
|
||||
|
||||
t = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
|
||||
can_change_items=True, can_change_organizer_settings=True)
|
||||
t.members.add(self.user)
|
||||
t.limit_events.add(self.event1)
|
||||
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
def test_organizer_list(self):
|
||||
doc = self.get_doc('/control/organizers/')
|
||||
tabletext = doc.select("#page-wrapper .table")[0].text
|
||||
self.assertIn("CCC", tabletext)
|
||||
self.assertNotIn("MRM", tabletext)
|
||||
|
||||
def test_organizer_detail(self):
|
||||
doc = self.get_doc('/control/organizer/ccc/')
|
||||
tabletext = doc.select("#page-wrapper .table")[0].text
|
||||
self.assertIn("30C3", tabletext)
|
||||
|
||||
def test_organizer_settings(self):
|
||||
doc = self.get_doc('/control/organizer/%s/edit' % (self.orga1.slug,))
|
||||
doc.select("[name=name]")[0]['value'] = "CCC e.V."
|
||||
|
||||
doc = self.post_doc('/control/organizer/%s/edit' % (self.orga1.slug,),
|
||||
extract_form_fields(doc.select('.container-fluid form')[0]))
|
||||
assert len(doc.select(".alert-success")) > 0
|
||||
assert doc.select("[name=name]")[0]['value'] == "CCC e.V."
|
||||
self.orga1.refresh_from_db()
|
||||
assert self.orga1.name == "CCC e.V."
|
||||
|
||||
def test_organizer_display_settings(self):
|
||||
assert not self.orga1.settings.presale_css_checksum
|
||||
doc = self.get_doc('/control/organizer/%s/settings/display' % (self.orga1.slug,))
|
||||
doc.select("[name=primary_color]")[0]['value'] = "#33c33c"
|
||||
|
||||
doc = self.post_doc('/control/organizer/%s/settings/display' % (self.orga1.slug,),
|
||||
extract_form_fields(doc.select('.container-fluid form')[0]))
|
||||
assert len(doc.select(".alert-success")) > 0
|
||||
assert doc.select("[name=primary_color]")[0]['value'] == "#33c33c"
|
||||
self.orga1.settings.flush()
|
||||
assert self.orga1.settings.primary_color == "#33c33c"
|
||||
assert self.orga1.settings.presale_css_checksum
|
||||
@@ -96,7 +96,6 @@ event_urls = [
|
||||
organizer_urls = [
|
||||
'organizer/abc/edit',
|
||||
'organizer/abc/',
|
||||
'organizer/abc/settings/display',
|
||||
'organizer/abc/teams',
|
||||
'organizer/abc/team/1/',
|
||||
'organizer/abc/team/1/edit',
|
||||
@@ -317,7 +316,6 @@ organizer_permission_urls = [
|
||||
("can_change_teams", "organizer/dummy/team/1/edit", 200),
|
||||
("can_change_teams", "organizer/dummy/team/1/delete", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/edit", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/settings/display", 200),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -194,8 +194,7 @@ class VoucherFormTest(SoupTest):
|
||||
self._create_voucher({
|
||||
'itemvar': '%d-%d' % (self.shirt.pk, self.shirt_red.pk),
|
||||
'block_quota': 'on',
|
||||
'valid_until_0': (now() - datetime.timedelta(days=3)).strftime('%Y-%m-%d'),
|
||||
'valid_until_1': (now() - datetime.timedelta(days=3)).strftime('%H:%M:%S')
|
||||
'valid_until': (now() - datetime.timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
def test_create_blocking_variation_voucher_quota_free(self):
|
||||
@@ -277,8 +276,7 @@ class VoucherFormTest(SoupTest):
|
||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
||||
block_quota=True)
|
||||
self._change_voucher(v, {
|
||||
'valid_until_0': (now() + datetime.timedelta(days=3)).strftime('%Y-%m-%d'),
|
||||
'valid_until_1': (now() + datetime.timedelta(days=3)).strftime('%H:%M:%S')
|
||||
'valid_until': (now() + datetime.timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
}, expected_failure=True)
|
||||
v.refresh_from_db()
|
||||
assert v.valid_until < now()
|
||||
@@ -287,8 +285,7 @@ class VoucherFormTest(SoupTest):
|
||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
||||
block_quota=True)
|
||||
self._change_voucher(v, {
|
||||
'valid_until_0': (now() + datetime.timedelta(days=3)).strftime('%Y-%m-%d'),
|
||||
'valid_until_1': (now() + datetime.timedelta(days=3)).strftime('%H:%M:%S')
|
||||
'valid_until': (now() + datetime.timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
v.refresh_from_db()
|
||||
assert v.valid_until > now()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, Item, ItemCategory, Organizer, Quota,
|
||||
)
|
||||
from pretix.testutils.sessions import add_cart_session, get_cart_session_key
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -29,7 +29,7 @@ def env(client):
|
||||
event.settings.set('payment_paypal_endpoint', 'sandbox')
|
||||
event.settings.set('payment_paypal_client_id', '12345')
|
||||
event.settings.set('payment_paypal_secret', '12345')
|
||||
add_cart_session(client, event, {'email': 'admin@localhost'})
|
||||
client.session.email = 'admin@localhost'
|
||||
return client, ticket
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_payment(env, monkeypatch):
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal._create_payment", create_payment)
|
||||
|
||||
client, ticket = env
|
||||
session_key = get_cart_session_key(client, ticket.event)
|
||||
session_key = client.cookies.get(settings.SESSION_COOKIE_NAME).value
|
||||
CartPosition.objects.create(
|
||||
event=ticket.event, cart_id=session_key, item=ticket,
|
||||
price=23, expires=now() + datetime.timedelta(minutes=10)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, Item, ItemCategory, Organizer, Quota,
|
||||
)
|
||||
from pretix.testutils.sessions import add_cart_session, get_cart_session_key
|
||||
|
||||
|
||||
class MockedCharge():
|
||||
@@ -35,7 +35,7 @@ def env(client):
|
||||
quota_tickets.items.add(ticket)
|
||||
event.settings.set('attendee_names_asked', False)
|
||||
event.settings.set('payment_stripe__enabled', True)
|
||||
add_cart_session(client, event, {'email': 'admin@localhost'})
|
||||
client.session.email = 'admin@localhost'
|
||||
return client, ticket
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_payment(env, monkeypatch):
|
||||
monkeypatch.setattr("stripe.Charge.create", charge_create)
|
||||
|
||||
client, ticket = env
|
||||
session_key = get_cart_session_key(client, ticket.event)
|
||||
session_key = client.cookies.get(settings.SESSION_COOKIE_NAME).value
|
||||
CartPosition.objects.create(
|
||||
event=ticket.event, cart_id=session_key, item=ticket,
|
||||
price=13.37, expires=now() + datetime.timedelta(minutes=10)
|
||||
|
||||
@@ -213,20 +213,3 @@ def test_sendmail_subevents(logged_in_client, sendmail_url, event, order):
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'Subevent FOO' in response.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_placeholder(logged_in_client, sendmail_url, event, order):
|
||||
djmail.outbox = []
|
||||
response = logged_in_client.post(sendmail_url,
|
||||
{'sendto': 'n',
|
||||
'subject_0': '{code} Test subject',
|
||||
'message_0': 'This is a test file for sending mails.',
|
||||
'action': 'preview'
|
||||
},
|
||||
follow=True)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'ORDER1234' in response.rendered_content
|
||||
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
@@ -16,7 +17,6 @@ from pretix.base.models.items import (
|
||||
ItemAddOn, SubEventItem, SubEventItemVariation,
|
||||
)
|
||||
from pretix.base.services.cart import CartError, CartManager
|
||||
from pretix.testutils.sessions import get_cart_session_key
|
||||
|
||||
|
||||
class CartTestMixin:
|
||||
@@ -51,7 +51,7 @@ class CartTestMixin:
|
||||
self.quota_all.variations.add(self.shirt_red)
|
||||
|
||||
self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
||||
self.session_key = get_cart_session_key(self.client, self.event)
|
||||
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
|
||||
|
||||
|
||||
class CartTest(CartTestMixin, TestCase):
|
||||
@@ -97,7 +97,7 @@ class CartTest(CartTestMixin, TestCase):
|
||||
|
||||
def _set_session(self, key, value):
|
||||
session = self.client.session
|
||||
session['carts'][get_cart_session_key(self.client, self.event)][key] = value
|
||||
session[key] = value
|
||||
session.save()
|
||||
|
||||
def _enable_reverse_charge(self):
|
||||
@@ -108,7 +108,7 @@ class CartTest(CartTestMixin, TestCase):
|
||||
is_business=True, vat_id='ATU1234567', vat_id_validated=True,
|
||||
country=Country('AT')
|
||||
)
|
||||
self._set_session('invoice_address', ia.pk)
|
||||
self._set_session('invoice_address_{}'.format(self.event.pk), ia.pk)
|
||||
return ia
|
||||
|
||||
def test_reverse_charge(self):
|
||||
|
||||
@@ -17,7 +17,6 @@ from pretix.base.models import (
|
||||
OrderPosition, Organizer, Question, Quota, Voucher,
|
||||
)
|
||||
from pretix.base.models.items import ItemAddOn, ItemVariation, SubEventItem
|
||||
from pretix.testutils.sessions import get_cart_session_key
|
||||
|
||||
|
||||
class CheckoutTestCase(TestCase):
|
||||
@@ -41,7 +40,7 @@ class CheckoutTestCase(TestCase):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
|
||||
self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
||||
self.session_key = get_cart_session_key(self.client, self.event)
|
||||
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
|
||||
self._set_session('email', 'admin@localhost')
|
||||
|
||||
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
|
||||
@@ -65,7 +64,7 @@ class CheckoutTestCase(TestCase):
|
||||
is_business=True, vat_id='ATU1234567', vat_id_validated=True,
|
||||
country=Country('AT')
|
||||
)
|
||||
self._set_session('invoice_address', ia.pk)
|
||||
self._set_session('invoice_address_{}'.format(self.event.pk), ia.pk)
|
||||
return ia
|
||||
|
||||
def test_empty_cart(self):
|
||||
@@ -157,7 +156,7 @@ class CheckoutTestCase(TestCase):
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == round_decimal(Decimal('23.00') / Decimal('1.19'))
|
||||
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
|
||||
assert ia.vat_id_validated
|
||||
|
||||
def test_reverse_charge_enable_then_disable(self):
|
||||
@@ -179,7 +178,7 @@ class CheckoutTestCase(TestCase):
|
||||
cr = CartPosition.objects.get(cart_id=self.session_key)
|
||||
assert cr.price == Decimal('23.00')
|
||||
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
|
||||
assert not ia.vat_id_validated
|
||||
|
||||
def test_reverse_charge_invalid_vatid(self):
|
||||
@@ -243,7 +242,7 @@ class CheckoutTestCase(TestCase):
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == round_decimal(Decimal('23.00') / Decimal('1.19'))
|
||||
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
|
||||
assert not ia.vat_id_validated
|
||||
|
||||
def test_reverse_charge_vatid_same_country(self):
|
||||
@@ -274,7 +273,7 @@ class CheckoutTestCase(TestCase):
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == Decimal('23.00')
|
||||
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
|
||||
assert ia.vat_id_validated
|
||||
|
||||
def test_reverse_charge_vatid_check_invalid_country(self):
|
||||
@@ -338,7 +337,7 @@ class CheckoutTestCase(TestCase):
|
||||
cr1.refresh_from_db()
|
||||
assert cr1.price == Decimal('23.00')
|
||||
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
|
||||
ia = InvoiceAddress.objects.get(pk=self.client.session.get('invoice_address_{}'.format(self.event.pk)))
|
||||
assert not ia.vat_id_validated
|
||||
|
||||
def test_question_file_upload(self):
|
||||
@@ -526,7 +525,7 @@ class CheckoutTestCase(TestCase):
|
||||
|
||||
def _set_session(self, key, value):
|
||||
session = self.client.session
|
||||
session['carts'][get_cart_session_key(self.client, self.event)][key] = value
|
||||
session[key] = value
|
||||
session.save()
|
||||
|
||||
def test_subevent(self):
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import datetime
|
||||
import os.path
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.presale.style import regenerate_css, regenerate_organizer_css
|
||||
|
||||
|
||||
class StyleTest(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.orga = Organizer.objects.create(name='CCC', slug='ccc')
|
||||
self.event = Event.objects.create(
|
||||
organizer=self.orga, name='30C3', slug='30c3',
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
live=True
|
||||
)
|
||||
|
||||
def test_organizer_generate_css_for_inherited_events(self):
|
||||
self.orga.settings.primary_color = "#33c33c"
|
||||
regenerate_organizer_css.apply(args=(self.orga.pk,))
|
||||
self.orga.settings.flush()
|
||||
assert self.orga.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.orga.settings.presale_css_file), 'r') as c:
|
||||
assert '#33c33c' in c.read()
|
||||
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert '#33c33c' in c.read()
|
||||
|
||||
def test_organizer_generate_css_only_for_inherited_events(self):
|
||||
self.orga.settings.primary_color = "#33c33c"
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_organizer_css.apply(args=(self.orga.pk,))
|
||||
self.orga.settings.flush()
|
||||
assert self.orga.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.orga.settings.presale_css_file), 'r') as c:
|
||||
assert '#33c33c' in c.read()
|
||||
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert '#34c34c' not in c.read()
|
||||
assert '#33c33c' not in c.read()
|
||||
|
||||
def test_event_generate_css_individually(self):
|
||||
self.orga.settings.primary_color = "#33c33c"
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert '#34c34c' in c.read()
|
||||
assert '#33c33c' not in c.read()
|
||||
|
||||
regenerate_organizer_css.apply(args=(self.orga.pk,))
|
||||
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert '#34c34c' in c.read()
|
||||
assert '#33c33c' not in c.read()
|
||||
|
||||
def test_event_generate_css_new_file(self):
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
|
||||
self.event.settings.flush()
|
||||
fname = self.event.settings.presale_css_file
|
||||
|
||||
self.event.settings.primary_color = "#ff00ff"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file != fname
|
||||
|
||||
def test_event_generate_css_cache_file(self):
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
|
||||
self.event.settings.flush()
|
||||
fname = self.event.settings.presale_css_file
|
||||
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
self.event.settings.flush()
|
||||
assert self.event.settings.presale_css_file == fname
|
||||
|
||||
@override_settings(
|
||||
MEDIA_URL="https://usercontent.pretix.space/media/",
|
||||
SITE_URL="https://pretix.eu"
|
||||
)
|
||||
def test_seperate_media_domain(self):
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
self.event.settings.flush()
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert 'https://pretix.eu/static/' in c.read()
|
||||
|
||||
@override_settings(
|
||||
MEDIA_URL="https://usercontent.pretix.space/media/",
|
||||
SITE_URL="https://pretix.eu"
|
||||
)
|
||||
def test_seperate_media_domain_and_organizer_domain(self):
|
||||
KnownDomain.objects.create(domainname="test.pretix.eu", organizer=self.orga)
|
||||
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
self.event.settings.flush()
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert 'https://test.pretix.eu/static/' in c.read()
|
||||
|
||||
@override_settings(
|
||||
STATIC_URL="https://static.pretix.files/static/",
|
||||
MEDIA_URL="https://usercontent.pretix.space/media/",
|
||||
SITE_URL="https://pretix.eu"
|
||||
)
|
||||
def test_seperate_media_domain_and_static_domain(self):
|
||||
KnownDomain.objects.create(domainname="test.pretix.eu", organizer=self.orga)
|
||||
|
||||
self.event.settings.primary_color = "#34c34c"
|
||||
regenerate_css.apply(args=(self.event.pk,))
|
||||
self.event.settings.flush()
|
||||
with open(os.path.join(settings.MEDIA_ROOT, self.event.settings.presale_css_file), 'r') as c:
|
||||
assert 'https://static.pretix.files/static/' in c.read()
|
||||
assert 'https://test.pretix.eu/static/' not in c.read()
|
||||
Reference in New Issue
Block a user