diff --git a/src/pretix/api/__init__.py b/src/pretix/api/__init__.py index 3ebe57f21..9fd5bdc50 100644 --- a/src/pretix/api/__init__.py +++ b/src/pretix/api/__init__.py @@ -19,15 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig - - -class PretixApiConfig(AppConfig): - name = 'pretix.api' - label = 'pretixapi' - - def ready(self): - from . import signals, webhooks # noqa - - -default_app_config = 'pretix.api.PretixApiConfig' diff --git a/src/pretix/api/apps.py b/src/pretix/api/apps.py new file mode 100644 index 000000000..2af7c6175 --- /dev/null +++ b/src/pretix/api/apps.py @@ -0,0 +1,30 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig + + +class PretixApiConfig(AppConfig): + name = 'pretix.api' + label = 'pretixapi' + + def ready(self): + from . import signals, webhooks # noqa diff --git a/src/pretix/api/middleware.py b/src/pretix/api/middleware.py index ba81e2a29..81c235472 100644 --- a/src/pretix/api/middleware.py +++ b/src/pretix/api/middleware.py @@ -89,7 +89,7 @@ class IdempotencyMiddleware: call.response_body = json.dumps(resp.data) else: call.response_body = repr(resp).encode() - call.response_headers = json.dumps(resp._headers) + call.response_headers = json.dumps(resp.headers._store) call.locked = None call.save(update_fields=['locked', 'response_code', 'response_headers', 'response_body']) diff --git a/src/pretix/api/serializers/cart.py b/src/pretix/api/serializers/cart.py index aa3f250c5..822b06d90 100644 --- a/src/pretix/api/serializers/cart.py +++ b/src/pretix/api/serializers/cart.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # +import os from datetime import timedelta from django.core.files import File @@ -125,9 +126,10 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer): if isinstance(answ_data['answer'], File): an = answ_data.pop('answer') answ = cp.answers.create(**answ_data, answer='') - answ.file.save(an.name, an, save=False) + answ.file.save(os.path.basename(an.name), an, save=False) answ.answer = 'file://' + answ.file.name answ.save() + an.close() else: answ = cp.answers.create(**answ_data) answ.options.add(*options) diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 806e19b87..e59eb8e77 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -21,6 +21,7 @@ # import json import logging +import os from collections import Counter, defaultdict from decimal import Decimal @@ -467,7 +468,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer): if isinstance(answ_data['answer'], File): an = answ_data.pop('answer') a = instance.answers.create(**answ_data, answer='') - a.file.save(an.name, an, save=False) + a.file.save(os.path.basename(an.name), an, save=False) a.answer = 'file://' + a.file.name a.save() else: @@ -1274,7 +1275,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): if isinstance(answ_data['answer'], File): an = answ_data.pop('answer') answ = pos.answers.create(**answ_data, answer='') - answ.file.save(an.name, an, save=False) + answ.file.save(os.path.basename(an.name), an, save=False) answ.answer = 'file://' + answ.file.name answ.save() else: diff --git a/src/pretix/api/signals.py b/src/pretix/api/signals.py index 4dfd59e9f..a44119419 100644 --- a/src/pretix/api/signals.py +++ b/src/pretix/api/signals.py @@ -29,9 +29,7 @@ from pretix.api.models import ApiCall, WebHookCall from pretix.base.signals import periodic_task from pretix.helpers.periodic import minimum_interval -register_webhook_events = Signal( - providing_args=[] -) +register_webhook_events = Signal() """ This signal is sent out to get all known webhook events. Receivers should return an instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such diff --git a/src/pretix/api/urls.py b/src/pretix/api/urls.py index 622955562..aab009420 100644 --- a/src/pretix/api/urls.py +++ b/src/pretix/api/urls.py @@ -35,7 +35,7 @@ import importlib from django.apps import apps -from django.conf.urls import include, url +from django.conf.urls import include, re_path from rest_framework import routers from pretix.api.views import cart @@ -109,30 +109,30 @@ for app in apps.get_app_configs(): importlib.import_module(app.name + '.urls') urlpatterns = [ - url(r'^', include(router.urls)), - url(r'^organizers/(?P[^/]+)/', include(orga_router.urls)), - url(r'^organizers/(?P[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(), - name="organizer.settings"), - url(r'^organizers/(?P[^/]+)/giftcards/(?P[^/]+)/', include(giftcard_router.urls)), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/settings/$', event.EventSettingsView.as_view(), - name="event.settings"), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/', include(event_router.urls)), - url(r'^organizers/(?P[^/]+)/teams/(?P[^/]+)/', include(team_router.urls)), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/items/(?P[^/]+)/', include(item_router.urls)), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/questions/(?P[^/]+)/', - include(question_router.urls)), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/checkinlists/(?P[^/]+)/', - include(checkinlist_router.urls)), - url(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/orders/(?P[^/]+)/', include(order_router.urls)), - url(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"), - url(r"^oauth/token$", oauth.TokenView.as_view(), name="token"), - url(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"), - url(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"), - url(r"^device/update$", device.UpdateView.as_view(), name="device.update"), - url(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"), - url(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"), - url(r"^device/eventselection$", device.EventSelectionView.as_view(), name="device.eventselection"), - url(r"^upload$", upload.UploadView.as_view(), name="upload"), - url(r"^me$", user.MeView.as_view(), name="user.me"), - url(r"^version$", version.VersionView.as_view(), name="version"), + re_path(r'^', include(router.urls)), + re_path(r'^organizers/(?P[^/]+)/', include(orga_router.urls)), + re_path(r'^organizers/(?P[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(), + name="organizer.settings"), + re_path(r'^organizers/(?P[^/]+)/giftcards/(?P[^/]+)/', include(giftcard_router.urls)), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/settings/$', event.EventSettingsView.as_view(), + name="event.settings"), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/', include(event_router.urls)), + re_path(r'^organizers/(?P[^/]+)/teams/(?P[^/]+)/', include(team_router.urls)), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/items/(?P[^/]+)/', include(item_router.urls)), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/questions/(?P[^/]+)/', + include(question_router.urls)), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/checkinlists/(?P[^/]+)/', + include(checkinlist_router.urls)), + re_path(r'^organizers/(?P[^/]+)/events/(?P[^/]+)/orders/(?P[^/]+)/', include(order_router.urls)), + re_path(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"), + re_path(r"^oauth/token$", oauth.TokenView.as_view(), name="token"), + re_path(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"), + re_path(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"), + re_path(r"^device/update$", device.UpdateView.as_view(), name="device.update"), + re_path(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"), + re_path(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"), + re_path(r"^device/eventselection$", device.EventSelectionView.as_view(), name="device.eventselection"), + re_path(r"^upload$", upload.UploadView.as_view(), name="upload"), + re_path(r"^me$", user.MeView.as_view(), name="user.me"), + re_path(r"^version$", version.VersionView.as_view(), name="version"), ] diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index c2750a1be..45da59a2a 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -22,7 +22,7 @@ import django_filters from django.core.exceptions import ValidationError from django.db.models import ( - Count, Exists, F, Max, OuterRef, Prefetch, Q, Subquery, + Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery, ) from django.db.models.functions import Coalesce from django.http import Http404 @@ -48,7 +48,6 @@ from pretix.base.models import ( from pretix.base.services.checkin import ( CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin, ) -from pretix.helpers.database import FixedOrderBy with scopes_disabled(): class CheckinListFilter(FilterSet): @@ -239,10 +238,10 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): 'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached') }, 'last_checked_in': { - '_order': FixedOrderBy(F('last_checked_in'), nulls_first=True), + '_order': OrderBy(F('last_checked_in'), nulls_first=True), }, '-last_checked_in': { - '_order': FixedOrderBy(F('last_checked_in'), nulls_last=True, descending=True), + '_order': OrderBy(F('last_checked_in'), nulls_last=True, descending=True), }, } diff --git a/src/pretix/base/__init__.py b/src/pretix/base/__init__.py index 2acf38a1d..9fd5bdc50 100644 --- a/src/pretix/base/__init__.py +++ b/src/pretix/base/__init__.py @@ -19,48 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # - -# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of -# the Apache License 2.0 can be obtained at . -# -# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A -# full history of changes and contributors is available at . -# -# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze -# -# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig - - -class PretixBaseConfig(AppConfig): - name = 'pretix.base' - label = 'pretixbase' - - def ready(self): - from . import exporter # NOQA - from . import payment # NOQA - from . import exporters # NOQA - from . import invoice # NOQA - from . import notifications # NOQA - from . import email # NOQA - from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA - from django.conf import settings - - try: - from .celery_app import app as celery_app # NOQA - except ImportError: - pass - - if hasattr(settings, 'RAVEN_CONFIG'): - from ..sentry import initialize - initialize() - - -default_app_config = 'pretix.base.PretixBaseConfig' -try: - import pretix.celery_app as celery # NOQA -except ImportError: - pass diff --git a/src/pretix/base/apps.py b/src/pretix/base/apps.py new file mode 100644 index 000000000..c053e71a7 --- /dev/null +++ b/src/pretix/base/apps.py @@ -0,0 +1,65 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig + + +class PretixBaseConfig(AppConfig): + name = 'pretix.base' + label = 'pretixbase' + + def ready(self): + from . import exporter # NOQA + from . import payment # NOQA + from . import exporters # NOQA + from . import invoice # NOQA + from . import notifications # NOQA + from . import email # NOQA + from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA + from django.conf import settings + + try: + from .celery_app import app as celery_app # NOQA + except ImportError: + pass + + if hasattr(settings, 'RAVEN_CONFIG'): + from ..sentry import initialize + initialize() + + +try: + import pretix.celery_app as celery # NOQA +except ImportError: + pass diff --git a/src/pretix/base/i18n.py b/src/pretix/base/i18n.py index 476a1e36e..ba7186ba0 100644 --- a/src/pretix/base/i18n.py +++ b/src/pretix/base/i18n.py @@ -107,10 +107,11 @@ ALLOWED_LANGUAGES = dict(settings.LANGUAGES) def get_babel_locale(): babel_locale = 'en' # Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal - if localedata.exists(translation.get_language()): - babel_locale = translation.get_language() - elif localedata.exists(translation.get_language()[:2]): - babel_locale = translation.get_language()[:2] + if translation.get_language(): + if localedata.exists(translation.get_language()): + babel_locale = translation.get_language() + elif localedata.exists(translation.get_language()[:2]): + babel_locale = translation.get_language()[:2] return babel_locale diff --git a/src/pretix/base/migrations/0001_squashed_0028_auto_20160816_1242.py b/src/pretix/base/migrations/0001_squashed_0028_auto_20160816_1242.py index 591f884c7..181de4a92 100644 --- a/src/pretix/base/migrations/0001_squashed_0028_auto_20160816_1242.py +++ b/src/pretix/base/migrations/0001_squashed_0028_auto_20160816_1242.py @@ -75,7 +75,7 @@ class Migration(migrations.Migration): ('date', models.DateTimeField(blank=True, null=True)), ('filename', models.CharField(max_length=255)), ('type', models.CharField(max_length=255)), - ('file', models.FileField(blank=True, null=True, upload_to=pretix.base.models.base.cachedfile_name)), + ('file', models.FileField(blank=True, null=True, upload_to=pretix.base.models.base._cachedfile_name)), ], ), migrations.CreateModel( diff --git a/src/pretix/base/migrations/0002_auto_20160209_0940.py b/src/pretix/base/migrations/0002_auto_20160209_0940.py index 5bd5d2ebf..fd885933b 100644 --- a/src/pretix/base/migrations/0002_auto_20160209_0940.py +++ b/src/pretix/base/migrations/0002_auto_20160209_0940.py @@ -32,7 +32,7 @@ class Migration(migrations.Migration): ('date', models.DateTimeField(blank=True, null=True)), ('filename', models.CharField(max_length=255)), ('type', models.CharField(max_length=255)), - ('file', models.FileField(blank=True, null=True, upload_to=pretix.base.models.base.cachedfile_name)), + ('file', models.FileField(blank=True, null=True, upload_to=pretix.base.models.base._cachedfile_name)), ], ), migrations.CreateModel( diff --git a/src/pretix/base/migrations/0102_auto_20181017_0024.py b/src/pretix/base/migrations/0102_auto_20181017_0024.py index 945ef0feb..d99394a77 100644 --- a/src/pretix/base/migrations/0102_auto_20181017_0024.py +++ b/src/pretix/base/migrations/0102_auto_20181017_0024.py @@ -1,8 +1,8 @@ # Generated by Django 2.1 on 2018-10-17 00:24 -import jsonfallback.fields + from django.core.exceptions import ImproperlyConfigured -from django.db import migrations +from django.db import migrations, models from django_mysql.checks import mysql_connections from django_mysql.utils import connection_is_mariadb @@ -77,19 +77,19 @@ class Migration(migrations.Migration): migrations.AddField( model_name='cartposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(null=False, default=dict), + field=models.JSONField(null=False, default=dict), preserve_default=False, ), migrations.AddField( model_name='orderposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(null=False, default=dict), + field=models.JSONField(null=False, default=dict), preserve_default=False, ), migrations.AddField( model_name='invoiceaddress', name='name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), preserve_default=False, ), migrations.RunPython(set_attendee_name_parts, migrations.RunPython.noop) diff --git a/src/pretix/base/migrations/0103_auto_20181121_1224.py b/src/pretix/base/migrations/0103_auto_20181121_1224.py index 5c6b3b78e..de770832c 100644 --- a/src/pretix/base/migrations/0103_auto_20181121_1224.py +++ b/src/pretix/base/migrations/0103_auto_20181121_1224.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.1 on 2018-11-21 12:24 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0104_auto_20181114_1526.py b/src/pretix/base/migrations/0104_auto_20181114_1526.py index 31e1119b0..52241a19c 100644 --- a/src/pretix/base/migrations/0104_auto_20181114_1526.py +++ b/src/pretix/base/migrations/0104_auto_20181114_1526.py @@ -2,7 +2,6 @@ import django.db.models.deletion import django.db.models.manager -import jsonfallback.fields from django.db import migrations, models diff --git a/src/pretix/base/migrations/0105_auto_20190112_1512.py b/src/pretix/base/migrations/0105_auto_20190112_1512.py index aa5bb9010..79060a0ac 100644 --- a/src/pretix/base/migrations/0105_auto_20190112_1512.py +++ b/src/pretix/base/migrations/0105_auto_20190112_1512.py @@ -1,7 +1,6 @@ # Generated by Django 2.1 on 2019-01-12 15:12 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0107_auto_20190129_1337.py b/src/pretix/base/migrations/0107_auto_20190129_1337.py index 187fcc7f9..2ddd14660 100644 --- a/src/pretix/base/migrations/0107_auto_20190129_1337.py +++ b/src/pretix/base/migrations/0107_auto_20190129_1337.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-01-29 13:37 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0108_auto_20190201_1527.py b/src/pretix/base/migrations/0108_auto_20190201_1527.py index 30dd42e7d..01fb65a6b 100644 --- a/src/pretix/base/migrations/0108_auto_20190201_1527.py +++ b/src/pretix/base/migrations/0108_auto_20190201_1527.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-02-01 15:27 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields @@ -17,6 +16,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='generate_tickets', - field=models.NullBooleanField(verbose_name='Allow ticket download'), + field=models.BooleanField(verbose_name='Allow ticket download', null=True, blank=True), ), ] diff --git a/src/pretix/base/migrations/0108_auto_20190201_1527_squashed_0141_seat_sorting_rank.py b/src/pretix/base/migrations/0108_auto_20190201_1527_squashed_0141_seat_sorting_rank.py index 280f9e1e0..93d0ee8e2 100644 --- a/src/pretix/base/migrations/0108_auto_20190201_1527_squashed_0141_seat_sorting_rank.py +++ b/src/pretix/base/migrations/0108_auto_20190201_1527_squashed_0141_seat_sorting_rank.py @@ -3,7 +3,6 @@ from decimal import Decimal import django.db.models.deletion -import jsonfallback.fields from django.conf import settings from django.core.cache import cache from django.db import migrations, models @@ -71,7 +70,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='generate_tickets', - field=models.NullBooleanField(verbose_name='Allow ticket download'), + field=models.BooleanField(verbose_name='Allow ticket download', null=True, blank=True), ), migrations.AddField( model_name='invoiceline', @@ -190,7 +189,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='cartposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='cartposition', @@ -210,7 +209,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='invoiceaddress', name='name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='item', @@ -225,7 +224,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='orderposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='orderposition', @@ -338,7 +337,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='show_quota_left', - field=models.NullBooleanField(), + field=models.BooleanField(null=True, blank=True), ), migrations.RenameField( model_name='question', diff --git a/src/pretix/base/migrations/0109_auto_20190208_1432.py b/src/pretix/base/migrations/0109_auto_20190208_1432.py index c7a491115..b72a4c961 100644 --- a/src/pretix/base/migrations/0109_auto_20190208_1432.py +++ b/src/pretix/base/migrations/0109_auto_20190208_1432.py @@ -1,7 +1,6 @@ # Generated by Django 2.1 on 2019-02-08 14:32 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0110_auto_20190219_1245.py b/src/pretix/base/migrations/0110_auto_20190219_1245.py index 92da90819..76cfd55b7 100644 --- a/src/pretix/base/migrations/0110_auto_20190219_1245.py +++ b/src/pretix/base/migrations/0110_auto_20190219_1245.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-02-19 12:45 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0111_auto_20190219_0949.py b/src/pretix/base/migrations/0111_auto_20190219_0949.py index 75bea99da..ad296f456 100644 --- a/src/pretix/base/migrations/0111_auto_20190219_0949.py +++ b/src/pretix/base/migrations/0111_auto_20190219_0949.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-02-19 09:49 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0113_auto_20190312_0942.py b/src/pretix/base/migrations/0113_auto_20190312_0942.py index 308c69685..4c2f5320f 100644 --- a/src/pretix/base/migrations/0113_auto_20190312_0942.py +++ b/src/pretix/base/migrations/0113_auto_20190312_0942.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-03-12 09:42 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0114_auto_20190316_1014.py b/src/pretix/base/migrations/0114_auto_20190316_1014.py index b74854a62..2d81043c9 100644 --- a/src/pretix/base/migrations/0114_auto_20190316_1014.py +++ b/src/pretix/base/migrations/0114_auto_20190316_1014.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.7 on 2019-03-16 10:14 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0115_auto_20190323_2238.py b/src/pretix/base/migrations/0115_auto_20190323_2238.py index 6559ab246..72820d062 100644 --- a/src/pretix/base/migrations/0115_auto_20190323_2238.py +++ b/src/pretix/base/migrations/0115_auto_20190323_2238.py @@ -3,7 +3,6 @@ from decimal import Decimal import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0116_auto_20190402_0722.py b/src/pretix/base/migrations/0116_auto_20190402_0722.py index 7d1c5d62a..fc88c0e06 100644 --- a/src/pretix/base/migrations/0116_auto_20190402_0722.py +++ b/src/pretix/base/migrations/0116_auto_20190402_0722.py @@ -1,7 +1,6 @@ # Generated by Django 2.1.5 on 2019-04-02 07:22 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0118_auto_20190423_0839.py b/src/pretix/base/migrations/0118_auto_20190423_0839.py index 94396b97b..839a16fa0 100644 --- a/src/pretix/base/migrations/0118_auto_20190423_0839.py +++ b/src/pretix/base/migrations/0118_auto_20190423_0839.py @@ -1,7 +1,6 @@ # Generated by Django 2.2 on 2019-04-23 08:39 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0119_auto_20190509_0654.py b/src/pretix/base/migrations/0119_auto_20190509_0654.py index e16ba93f1..04246fc8e 100644 --- a/src/pretix/base/migrations/0119_auto_20190509_0654.py +++ b/src/pretix/base/migrations/0119_auto_20190509_0654.py @@ -1,7 +1,6 @@ # Generated by Django 2.2 on 2019-05-09 06:54 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields diff --git a/src/pretix/base/migrations/0120_auto_20190509_0736.py b/src/pretix/base/migrations/0120_auto_20190509_0736.py index c0904e832..17ae129ac 100644 --- a/src/pretix/base/migrations/0120_auto_20190509_0736.py +++ b/src/pretix/base/migrations/0120_auto_20190509_0736.py @@ -1,7 +1,6 @@ # Generated by Django 2.2 on 2019-05-09 07:36 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.fields @@ -17,7 +16,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='cartposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='cartposition', @@ -37,7 +36,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='invoiceaddress', name='name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='item', @@ -52,7 +51,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='orderposition', name='attendee_name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterField( model_name='orderposition', diff --git a/src/pretix/base/migrations/0126_item_show_quota_left.py b/src/pretix/base/migrations/0126_item_show_quota_left.py index cef78f179..92c0ebc37 100644 --- a/src/pretix/base/migrations/0126_item_show_quota_left.py +++ b/src/pretix/base/migrations/0126_item_show_quota_left.py @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='show_quota_left', - field=models.NullBooleanField(), + field=models.BooleanField(null=True, blank=True), ), ] diff --git a/src/pretix/base/migrations/0152_auto_20200511_1504.py b/src/pretix/base/migrations/0152_auto_20200511_1504.py index ee514a291..36f2e171c 100644 --- a/src/pretix/base/migrations/0152_auto_20200511_1504.py +++ b/src/pretix/base/migrations/0152_auto_20200511_1504.py @@ -2,7 +2,6 @@ import django.db.models.deletion import django_countries.fields -import jsonfallback.fields from django.db import migrations, models import pretix.helpers.countries @@ -43,7 +42,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='checkinlist', name='rules', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AlterUniqueTogether( name='checkin', diff --git a/src/pretix/base/migrations/0158_auto_20200724_0754.py b/src/pretix/base/migrations/0158_auto_20200724_0754.py index 8fbb41b22..3ee8f48db 100644 --- a/src/pretix/base/migrations/0158_auto_20200724_0754.py +++ b/src/pretix/base/migrations/0158_auto_20200724_0754.py @@ -16,7 +16,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='cachedfile', name='file', - field=models.FileField(max_length=255, null=True, upload_to=pretix.base.models.base.cachedfile_name), + field=models.FileField(max_length=255, null=True, upload_to=pretix.base.models.base._cachedfile_name), ), migrations.AlterField( model_name='cartposition', diff --git a/src/pretix/base/migrations/0177_auto_20210301_1510.py b/src/pretix/base/migrations/0177_auto_20210301_1510.py index 7a2795ed2..f7f4e4220 100644 --- a/src/pretix/base/migrations/0177_auto_20210301_1510.py +++ b/src/pretix/base/migrations/0177_auto_20210301_1510.py @@ -1,6 +1,6 @@ # Generated by Django 3.0.10 on 2021-03-01 15:10 -import jsonfallback.fields + import phonenumber_field.modelfields from django.db import migrations, models @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='waitinglistentry', name='name_parts', - field=jsonfallback.fields.FallbackJSONField(default=dict), + field=models.JSONField(default=dict), ), migrations.AddField( model_name='waitinglistentry', diff --git a/src/pretix/base/migrations/0184_customer.py b/src/pretix/base/migrations/0184_customer.py index ad62e0c15..29b27e8bb 100644 --- a/src/pretix/base/migrations/0184_customer.py +++ b/src/pretix/base/migrations/0184_customer.py @@ -1,7 +1,6 @@ # Generated by Django 3.0.13 on 2021-04-06 07:25 import django.db.models.deletion -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.base @@ -28,7 +27,7 @@ class Migration(migrations.Migration): ('email', models.EmailField(db_index=True, max_length=190, null=True)), ('password', models.CharField(max_length=128)), ('name_cached', models.CharField(max_length=255)), - ('name_parts', jsonfallback.fields.FallbackJSONField(default=dict)), + ('name_parts', models.JSONField(default=dict)), ('is_active', models.BooleanField(default=True)), ('is_verified', models.BooleanField(default=True)), ('last_login', models.DateTimeField(blank=True, null=True)), diff --git a/src/pretix/base/migrations/0185_memberships.py b/src/pretix/base/migrations/0185_memberships.py index 70d1cab47..fa3774d3b 100644 --- a/src/pretix/base/migrations/0185_memberships.py +++ b/src/pretix/base/migrations/0185_memberships.py @@ -2,7 +2,6 @@ import django.db.models.deletion import i18nfield.fields -import jsonfallback.fields from django.db import migrations, models import pretix.base.models.base @@ -61,7 +60,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(primary_key=True, serialize=False)), ('date_start', models.DateTimeField()), ('date_end', models.DateTimeField()), - ('attendee_name_parts', jsonfallback.fields.FallbackJSONField(default=dict, null=True)), + ('attendee_name_parts', models.JSONField(default=dict, null=True)), ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='pretixbase.Customer')), ('granted_in', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='granted_memberships', to='pretixbase.OrderPosition', null=True)), ('membership_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='pretixbase.MembershipType')), diff --git a/src/pretix/base/models/base.py b/src/pretix/base/models/base.py index fd55cb82f..cb4b0a5a7 100644 --- a/src/pretix/base/models/base.py +++ b/src/pretix/base/models/base.py @@ -36,7 +36,13 @@ from pretix.helpers.json import CustomJSONEncoder def cachedfile_name(instance, filename: str) -> str: secret = get_random_string(length=12) - return 'cachedfiles/%s.%s.%s' % (instance.id, secret, filename.split('.')[-1]) + return '%s.%s.%s' % (instance.id, secret, filename.split('.')[-1]) + + +def _cachedfile_name(instance, filename: str) -> str: + # This was previously combined with cachedfile_name in one function, but a security patch for Django introduced + # additional file name validation in May 2021, and this was the best way to fix it without breaking plugins. + return 'cachedfiles/' + cachedfile_name(instance, filename) class CachedFile(models.Model): @@ -48,7 +54,7 @@ class CachedFile(models.Model): date = models.DateTimeField(null=True, blank=True) filename = models.CharField(max_length=255) type = models.CharField(max_length=255) - file = models.FileField(null=True, blank=True, upload_to=cachedfile_name, max_length=255) + file = models.FileField(null=True, blank=True, upload_to=_cachedfile_name, max_length=255) web_download = models.BooleanField(default=True) # allow web download, True for backwards compatibility in plugins session_key = models.TextField(null=True, blank=True) # only allow download in this session diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py index e2f71a05d..4e2f487e7 100644 --- a/src/pretix/base/models/checkin.py +++ b/src/pretix/base/models/checkin.py @@ -39,7 +39,6 @@ from django.db.models import Exists, F, Max, OuterRef, Q, Subquery from django.utils.timezone import now from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_scopes import ScopedManager, scopes_disabled -from jsonfallback.fields import FallbackJSONField from pretix.base.models import LoggedModel from pretix.base.models.fields import MultiStringField @@ -82,7 +81,7 @@ class CheckinList(LoggedModel): 'any of the selected sales channels. This option can be useful when tickets sold at the box office ' 'are not checked again before entry and should be considered validated directly upon purchase.') ) - rules = FallbackJSONField(default=dict, blank=True) + rules = models.JSONField(default=dict, blank=True) objects = ScopedManager(organizer='event__organizer') diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 1c5a2dd12..ce9d6a586 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -27,7 +27,6 @@ from django.db import models from django.utils.crypto import get_random_string, salted_hmac from django.utils.translation import gettext_lazy as _ from django_scopes import ScopedManager, scopes_disabled -from jsonfallback.fields import FallbackJSONField from pretix.base.banlist import banned from pretix.base.models.base import LoggedModel @@ -45,7 +44,7 @@ class Customer(LoggedModel): email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190) password = models.CharField(verbose_name=_('Password'), max_length=128) name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) - name_parts = FallbackJSONField(default=dict) + name_parts = models.JSONField(default=dict) is_active = models.BooleanField(default=True, verbose_name=_('Account active')) is_verified = models.BooleanField(default=True, verbose_name=_('Verified email address')) last_login = models.DateTimeField(verbose_name=_('Last login'), blank=True, null=True) @@ -60,6 +59,10 @@ class Customer(LoggedModel): class Meta: unique_together = [['organizer', 'email']] + ordering = ('email',) + + def get_email_field_name(self): + return 'email' def save(self, **kwargs): if self.email: diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index ca260bd9f..e552a351f 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -1270,7 +1270,8 @@ class SubEvent(EventMixin, LoggedModel): ).order_by().values('subevent').annotate(items=GroupConcat('item_id', delimiter=',')).values('items'), output_field=models.TextField(), ), - Value('') + Value(''), + output_field=models.TextField() ), disabled_vars=Coalesce( Subquery( @@ -1280,7 +1281,8 @@ class SubEvent(EventMixin, LoggedModel): ).order_by().values('subevent').annotate(items=GroupConcat('variation_id', delimiter=',')).values('items'), output_field=models.TextField(), ), - Value('') + Value(''), + output_field=models.TextField() ) ) diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index d1480dc41..6204bc666 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -401,7 +401,7 @@ class Item(LoggedModel): ), default=False ) - generate_tickets = models.NullBooleanField( + generate_tickets = models.BooleanField( verbose_name=_("Generate tickets"), blank=True, null=True, ) @@ -410,7 +410,7 @@ class Item(LoggedModel): help_text=_("This will only work if waiting lists are enabled for this event."), default=True ) - show_quota_left = models.NullBooleanField( + show_quota_left = models.BooleanField( verbose_name=_("Show number of tickets left"), help_text=_("Publicly show how many tickets are still available."), blank=True, null=True, diff --git a/src/pretix/base/models/memberships.py b/src/pretix/base/models/memberships.py index b47960325..f72880227 100644 --- a/src/pretix/base/models/memberships.py +++ b/src/pretix/base/models/memberships.py @@ -27,7 +27,6 @@ from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django_scopes import ScopedManager, scopes_disabled from i18nfield.fields import I18nCharField -from jsonfallback.fields import FallbackJSONField from pretix.base.models import Customer from pretix.base.models.base import LoggedModel @@ -59,6 +58,9 @@ class MembershipType(LoggedModel): null=True, blank=True, ) + class Meta: + ordering = ('id',) + def __str__(self): return str(self.name) @@ -87,7 +89,7 @@ class MembershipQuerySet(models.QuerySet): c=Count('*') ).values('c') ), - Value('0') + Value(0), ) ) @@ -135,7 +137,7 @@ class Membership(models.Model): date_end = models.DateTimeField( verbose_name=_('End date') ) - attendee_name_parts = FallbackJSONField(default=dict, null=True) + attendee_name_parts = models.JSONField(default=dict, null=True) objects = MembershipQuerySetManager() diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 8c528508c..1b86ea7e6 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -65,7 +65,6 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_countries.fields import Country from django_scopes import ScopedManager, scopes_disabled from i18nfield.strings import LazyI18nString -from jsonfallback.fields import FallbackJSONField from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.phonenumber import PhoneNumber from phonenumbers import NumberParseException @@ -378,12 +377,12 @@ class Order(LockModel, LoggedModel): refund_sum=refund_sum_sq, ) qs = qs.annotate( - computed_payment_refund_sum=Coalesce(payment_sum_sq, 0) - Coalesce(refund_sum_sq, 0), + computed_payment_refund_sum=Coalesce(payment_sum_sq, Decimal('0.00')) - Coalesce(refund_sum_sq, Decimal('0.00')), ) qs = qs.annotate( - pending_sum_t=F('total') - Coalesce(payment_sum_sq, 0) + Coalesce(refund_sum_sq, 0), - pending_sum_rc=-1 * Coalesce(payment_sum_sq, 0) + Coalesce(refund_sum_sq, 0), + pending_sum_t=F('total') - Coalesce(payment_sum_sq, Decimal('0.00')) + Coalesce(refund_sum_sq, Decimal('0.00')), + pending_sum_rc=-1 * Coalesce(payment_sum_sq, Decimal('0.00')) + Coalesce(refund_sum_sq, Decimal('0.00')), ) if refunds: qs = qs.annotate( @@ -394,23 +393,23 @@ class Order(LockModel, LoggedModel): qs = qs.annotate( is_overpaid=Case( When(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=-1e-8), - then=Value('1')), + then=Value(1)), When(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=-1e-8), - then=Value('1')), - default=Value('0'), + then=Value(1)), + default=Value(0), output_field=models.IntegerField() ), is_pending_with_full_payment=Case( When(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=1e-8) & Q(require_approval=False), - then=Value('1')), - default=Value('0'), + then=Value(1)), + default=Value(0), output_field=models.IntegerField() ), is_underpaid=Case( When(Q(status=Order.STATUS_PAID) & Q(pending_sum_t__gt=1e-8), - then=Value('1')), - default=Value('0'), + then=Value(1)), + default=Value(0), output_field=models.IntegerField() ) ) @@ -1190,7 +1189,7 @@ class AbstractPosition(models.Model): blank=True, null=True, help_text=_("Empty, if this product is not an admission ticket") ) - attendee_name_parts = FallbackJSONField( + attendee_name_parts = models.JSONField( blank=True, default=dict ) attendee_email = models.EmailField( @@ -2313,7 +2312,7 @@ class InvoiceAddress(models.Model): is_business = models.BooleanField(default=False, verbose_name=_('Business customer')) company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name')) name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) - name_parts = FallbackJSONField(default=dict) + name_parts = models.JSONField(default=dict) street = models.TextField(verbose_name=_('Address'), blank=False) zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False) city = models.CharField(max_length=255, verbose_name=_('City'), blank=False) diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py index 5ce850630..b63eead57 100644 --- a/src/pretix/base/models/waitinglist.py +++ b/src/pretix/base/models/waitinglist.py @@ -26,7 +26,6 @@ from django.db import models, transaction from django.utils.timezone import now from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_scopes import ScopedManager -from jsonfallback.fields import FallbackJSONField from phonenumber_field.modelfields import PhoneNumberField from pretix.base.email import get_email_context @@ -66,7 +65,7 @@ class WaitingListEntry(LoggedModel): verbose_name=_("Name"), blank=True, null=True, ) - name_parts = FallbackJSONField( + name_parts = models.JSONField( blank=True, default=dict ) email = models.EmailField( diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index cdd36eab5..5d3e5b7ca 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -31,7 +31,7 @@ # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. - +import os from datetime import datetime, timedelta from functools import partial, reduce @@ -545,7 +545,7 @@ def _save_answers(op, answers, given_answers): qa = answers[q] else: qa = op.answers.create(question=q, answer=str(a)) - qa.file.save(a.name, a, save=False) + qa.file.save(os.path.basename(a.name), a, save=False) qa.answer = 'file://' + qa.file.name qa.save() written = True diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index 4c0de5616..b044db101 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -198,9 +198,7 @@ class DeprecatedSignal(django.dispatch.Signal): super().connect(receiver, sender=None, weak=True, dispatch_uid=None) -event_live_issues = EventPluginSignal( - providing_args=[] -) +event_live_issues = EventPluginSignal() """ This signal is sent out to determine whether an event can be taken live. If you want to prevent the event from going live, return a string that will be displayed to the user @@ -210,9 +208,7 @@ As with all event-plugin signals, the ``sender`` keyword argument will contain t """ -register_payment_providers = EventPluginSignal( - providing_args=[] -) +register_payment_providers = EventPluginSignal() """ This signal is sent out to get all known payment providers. Receivers should return a subclass of pretix.base.payment.BasePaymentProvider or a list of these @@ -220,9 +216,7 @@ subclass of pretix.base.payment.BasePaymentProvider or a list of these As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_mail_placeholders = EventPluginSignal( - providing_args=[] -) +register_mail_placeholders = EventPluginSignal() """ This signal is sent out to get all known email text placeholders. Receivers should return an instance of a subclass of pretix.base.email.BaseMailTextPlaceholder or a list of these. @@ -230,9 +224,7 @@ an instance of a subclass of pretix.base.email.BaseMailTextPlaceholder or a list As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_html_mail_renderers = EventPluginSignal( - providing_args=[] -) +register_html_mail_renderers = EventPluginSignal() """ This signal is sent out to get all known HTML email renderers. Receivers should return a subclass of pretix.base.email.BaseHTMLMailRenderer or a list of these @@ -240,9 +232,7 @@ subclass of pretix.base.email.BaseHTMLMailRenderer or a list of these As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_invoice_renderers = EventPluginSignal( - providing_args=[] -) +register_invoice_renderers = EventPluginSignal() """ This signal is sent out to get all known invoice renderers. Receivers should return a subclass of pretix.base.invoice.BaseInvoiceRenderer or a list of these @@ -250,9 +240,7 @@ subclass of pretix.base.invoice.BaseInvoiceRenderer or a list of these As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_ticket_secret_generators = EventPluginSignal( - providing_args=[] -) +register_ticket_secret_generators = EventPluginSignal() """ This signal is sent out to get all known ticket secret generators. Receivers should return a subclass of ``pretix.base.secrets.BaseTicketSecretGenerator`` or a list of these @@ -260,9 +248,7 @@ subclass of ``pretix.base.secrets.BaseTicketSecretGenerator`` or a list of these As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_data_shredders = EventPluginSignal( - providing_args=[] -) +register_data_shredders = EventPluginSignal() """ This signal is sent out to get all known data shredders. Receivers should return a subclass of pretix.base.shredder.BaseDataShredder or a list of these @@ -270,9 +256,7 @@ subclass of pretix.base.shredder.BaseDataShredder or a list of these As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_ticket_outputs = EventPluginSignal( - providing_args=[] -) +register_ticket_outputs = EventPluginSignal() """ This signal is sent out to get all known ticket outputs. Receivers should return a subclass of pretix.base.ticketoutput.BaseTicketOutput @@ -280,9 +264,7 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_notification_types = EventPluginSignal( - providing_args=[] -) +register_notification_types = EventPluginSignal() """ This signal is sent out to get all known notification types. Receivers should return an instance of a subclass of pretix.base.notifications.NotificationType or a list of such @@ -293,18 +275,14 @@ however for this signal, the ``sender`` **may also be None** to allow creating t notification settings! """ -register_sales_channels = django.dispatch.Signal( - providing_args=[] -) +register_sales_channels = django.dispatch.Signal() """ This signal is sent out to get all known sales channels types. Receivers should return an instance of a subclass of ``pretix.base.channels.SalesChannel`` or a list of such instances. """ -register_data_exporters = EventPluginSignal( - providing_args=[] -) +register_data_exporters = EventPluginSignal() """ This signal is sent out to get all known data exporters. Receivers should return a subclass of pretix.base.exporter.BaseExporter @@ -312,10 +290,10 @@ subclass of pretix.base.exporter.BaseExporter As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -register_multievent_data_exporters = django.dispatch.Signal( - providing_args=["event"] -) +register_multievent_data_exporters = django.dispatch.Signal() """ +Arguments: ``event`` + This signal is sent out to get all known data exporters, which support exporting data for multiple events. Receivers should return a subclass of pretix.base.exporter.BaseExporter @@ -323,10 +301,11 @@ The ``sender`` keyword argument will contain an organizer. """ validate_order = EventPluginSignal( - providing_args=["payment_provider", "positions", "email", "locale", "invoice_address", - "meta_info", "customer"] ) """ +Arguments: ``payment_provider``, ``positions``, ``email``, ``locale``, ``invoice_address``, +``meta_info``, ``customer`` + This signal is sent out when the user tries to confirm the order, before we actually create the order. It allows you to inspect the cart positions. Your return value will be ignored, but you can raise an OrderError with an appropriate exception message if you like to block @@ -335,10 +314,10 @@ the order. We strongly discourage making changes to the order here. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -validate_cart = EventPluginSignal( - providing_args=["positions"] -) +validate_cart = EventPluginSignal() """ +Arguments: ``positions`` + This signal is sent out before the user starts checkout. It includes an iterable with the current CartPosition objects. The response of receivers will be ignored, but you can raise a CartError with an @@ -347,10 +326,10 @@ appropriate exception message. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -validate_cart_addons = EventPluginSignal( - providing_args=["addons", "base_position", "iao"] -) +validate_cart_addons = EventPluginSignal() """ +Arguments: ``addons``, ``base_position``, ``iao`` + This signal is sent when a user tries to select a combination of addons. In contrast to ``validate_cart``, this is executed before the cart is actually modified. You are passed an argument ``addons`` containing a dict of ``(item, variation or None) → count`` tuples as well @@ -362,10 +341,10 @@ appropriate exception message. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_placed = EventPluginSignal( - providing_args=["order"] -) +order_placed = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is placed. The order object is given as the first argument. This signal is *not* sent out if an order is created through splitting an existing order, so you can not expect to see all orders by listening @@ -374,10 +353,10 @@ to this signal. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_paid = EventPluginSignal( - providing_args=["order"] -) +order_paid = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is paid. The order object is given as the first argument. This signal is *not* sent out if an order is marked as paid because an already-paid order has been split. @@ -385,80 +364,80 @@ because an already-paid order has been split. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_canceled = EventPluginSignal( - providing_args=["order"] -) +order_canceled = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is canceled. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_reactivated = EventPluginSignal( - providing_args=["order"] -) +order_reactivated = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time a canceled order is reactivated. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_expired = EventPluginSignal( - providing_args=["order"] -) +order_expired = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is marked as expired. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_modified = EventPluginSignal( - providing_args=["order"] -) +order_modified = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order's information is modified. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_changed = EventPluginSignal( - providing_args=["order"] -) +order_changed = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order's content is changed. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_approved = EventPluginSignal( - providing_args=["order"] -) +order_approved = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is being approved. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_denied = EventPluginSignal( - providing_args=["order"] -) +order_denied = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time an order is being denied. The order object is given as the first argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -order_gracefully_delete = EventPluginSignal( - providing_args=["order"] -) +order_gracefully_delete = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out every time a test-mode order is being deleted. The order object is given as the first argument. @@ -469,10 +448,10 @@ the deletion of the order. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -checkin_created = EventPluginSignal( - providing_args=["checkin"], -) +checkin_created = EventPluginSignal() """ +Arguments: ``checkin`` + This signal is sent out every time a check-in is created (i.e. an order position is marked as checked in). It is not send if the position was already checked in and is force-checked-in a second time. The check-in object is given as the first argument @@ -480,10 +459,10 @@ The check-in object is given as the first argument As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -logentry_display = EventPluginSignal( - providing_args=["logentry"] -) +logentry_display = EventPluginSignal() """ +Arguments: ``logentry`` + To display an instance of the ``LogEntry`` model to a human user, ``pretix.base.signals.logentry_display`` will be sent out with a ``logentry`` argument. @@ -493,10 +472,10 @@ to the user. The receivers are expected to return plain text. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -logentry_object_link = EventPluginSignal( - providing_args=["logentry"] -) +logentry_object_link = EventPluginSignal() """ +Arguments: ``logentry`` + To display the relationship of an instance of the ``LogEntry`` model to another model to a human user, ``pretix.base.signals.logentry_object_link`` will be sent out with a ``logentry`` argument. @@ -521,10 +500,10 @@ Make sure that any user content in the HTML code you return is properly escaped! As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -requiredaction_display = EventPluginSignal( - providing_args=["action", "request"] -) +requiredaction_display = EventPluginSignal() """ +Arguments: ``action``, ``request`` + To display an instance of the ``RequiredAction`` model to a human user, ``pretix.base.signals.requiredaction_display`` will be sent out with a ``action`` argument. You will also get the current ``request`` in a different argument. @@ -535,10 +514,10 @@ to the user. The receivers are expected to return HTML code. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -event_copy_data = EventPluginSignal( - providing_args=["other", "tax_map", "category_map", "item_map", "question_map", "variation_map", "checkin_list_map"] -) +event_copy_data = EventPluginSignal() """ +Arguments: "other", ``tax_map``, ``category_map``, ``item_map``, ``question_map``, ``variation_map``, ``checkin_list_map`` + This signal is sent out when a new event is created as a clone of an existing event, i.e. the settings from the older event are copied to the newer one. You can listen to this signal to copy data or configuration stored within your plugin's models as well. @@ -553,10 +532,10 @@ keyword argument will contain the event to **copy from**. The keyword arguments in the new event of the respective types. """ -item_copy_data = EventPluginSignal( - providing_args=["source", "target"] -) +item_copy_data = EventPluginSignal() """ +Arguments: ``source``, ``target`` + This signal is sent out when a new product is created as a clone of an existing product, i.e. the settings from the older product are copied to the newer one. You can listen to this signal to copy data or configuration stored within your plugin's models as well. @@ -580,10 +559,10 @@ All plugins that are installed may send fields for the global settings form, as an OrderedDict of (setting name, form field). """ -order_fee_calculation = EventPluginSignal( - providing_args=['positions', 'invoice_address', 'meta_info', 'total', 'gift_cards'] -) +order_fee_calculation = EventPluginSignal() """ +Arguments: ``positions``, ``invoice_address``, ``meta_info``, ``total``, ``gift_cards`` + This signals allows you to add fees to an order while it is being created. You are expected to return a list of ``OrderFee`` objects that are not yet saved to the database (because there is no order yet). @@ -596,10 +575,10 @@ keyword argument will contain the total cart sum without any fees. You should no the gift cards in use. """ -order_fee_type_name = EventPluginSignal( - providing_args=['request', 'fee'] -) +order_fee_type_name = EventPluginSignal() """ +Arguments: ``request``, ``fee`` + This signals allows you to return a human-readable description for a fee type based on the ``fee_type`` and ``internal_type`` attributes of the ``OrderFee`` model that you get as keyword arguments. You are expected to return a string or None, if you don't know about this fee. @@ -607,20 +586,20 @@ expected to return a string or None, if you don't know about this fee. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -allow_ticket_download = EventPluginSignal( - providing_args=['order'] -) +allow_ticket_download = EventPluginSignal() """ +Arguments: ``order`` + This signal is sent out to check if tickets for an order can be downloaded. If any receiver returns false, a download will not be offered. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -email_filter = EventPluginSignal( - providing_args=['message', 'order', 'user'] -) +email_filter = EventPluginSignal() """ +Arguments: ``message``, ``order``, ``user`` + This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to return a (possibly modified) copy of the message object passed to you. @@ -632,10 +611,10 @@ If the email is associated with a specific user, e.g. a notification email, the well, otherwise it will be ``None``. """ -global_email_filter = GlobalSignal( - providing_args=['message', 'order', 'user', 'customer', 'organizer'] -) +global_email_filter = GlobalSignal() """ +Arguments: ``message``, ``order``, ``user``, ``customer``, ``organizer`` + This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to return a (possibly modified) copy of the message object passed to you. @@ -700,10 +679,10 @@ a ``subevent`` argument which might be none and you are expected to return a lis """ -quota_availability = EventPluginSignal( - providing_args=['quota', 'result', 'count_waitinglist'] -) +quota_availability = EventPluginSignal() """ +Arguments: ``quota``, ``result``, ``count_waitinglist`` + This signal allows you to modify the availability of a quota. You are passed the ``quota`` and an ``availability`` result calculated by pretix code or other plugins. ``availability`` is a tuple with the first entry being one of the ``Quota.AVAILABILITY_*`` constants and the second entry being @@ -716,25 +695,23 @@ system really bad.** Also, keep in mind that your response is subject to caching quotas might be used for display (not for actual order processing). """ -order_split = EventPluginSignal( - providing_args=["original", "split_order"] -) +order_split = EventPluginSignal() """ +Arguments: ``original``, ``split_order`` + This signal is sent out when an order is split into two orders and allows you to copy related models to the new order. You will be passed the old order as ``original`` and the new order as ``split_order``. """ -invoice_line_text = EventPluginSignal( - providing_args=["position"] -) +invoice_line_text = EventPluginSignal() """ +Arguments: ``position`` + This signal is sent out when an invoice is built for an order. You can return additional text that should be shown on the invoice for the given ``position``. """ -order_import_columns = EventPluginSignal( - providing_args=[] -) +order_import_columns = EventPluginSignal() """ This signal is sent out if the user performs an import of orders from an external source. You can use this to define additional columns that can be read during import. You are expected to return a list of instances of @@ -743,10 +720,10 @@ to define additional columns that can be read during import. You are expected to As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -validate_event_settings = EventPluginSignal( - providing_args=["settings_dict"] -) +validate_event_settings = EventPluginSignal() """ +Arguments: ``settings_dict`` + This signal is sent out if the user performs an update of event settings through the API or web interface. You are passed a ``settings_dict`` dictionary with the new state of the event settings object and are expected to raise a ``django.core.exceptions.ValidationError`` if the new state is not valid. @@ -757,9 +734,7 @@ serializer field instead. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -api_event_settings_fields = EventPluginSignal( - providing_args=[] -) +api_event_settings_fields = EventPluginSignal() """ This signal is sent out to collect serializable settings fields for the API. You are expected to return a dictionary mapping names of attributes in the settings store to DRF serializer field instances. diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py index 357b7a032..f121af864 100644 --- a/src/pretix/base/templatetags/rich_text.py +++ b/src/pretix/base/templatetags/rich_text.py @@ -187,9 +187,9 @@ def markdown_compile_email(source): class SnippetExtension(markdown.extensions.Extension): def extendMarkdown(self, md, *args, **kwargs): - del md.parser.blockprocessors['olist'] - del md.parser.blockprocessors['ulist'] - del md.parser.blockprocessors['quote'] + md.parser.blockprocessors.deregister('olist') + md.parser.blockprocessors.deregister('ulist') + md.parser.blockprocessors.deregister('quote') def markdown_compile(source, snippet=False): diff --git a/src/pretix/control/__init__.py b/src/pretix/control/__init__.py index 6edc3b1b7..9fd5bdc50 100644 --- a/src/pretix/control/__init__.py +++ b/src/pretix/control/__init__.py @@ -19,29 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # - -# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of -# the Apache License 2.0 can be obtained at . -# -# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A -# full history of changes and contributors is available at . -# -# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze -# -# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig - - -class PretixControlConfig(AppConfig): - name = 'pretix.control' - label = 'pretixcontrol' - - def ready(self): - from .views import dashboards # noqa - from . import logdisplay # noqa - - -default_app_config = 'pretix.control.PretixControlConfig' diff --git a/src/pretix/control/apps.py b/src/pretix/control/apps.py new file mode 100644 index 000000000..1b2148618 --- /dev/null +++ b/src/pretix/control/apps.py @@ -0,0 +1,44 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig + + +class PretixControlConfig(AppConfig): + name = 'pretix.control' + label = 'pretixcontrol' + + def ready(self): + from .views import dashboards # noqa + from . import logdisplay # noqa diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 111f91848..5edc02f20 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -40,7 +40,7 @@ from django import forms from django.apps import apps from django.conf import settings from django.db.models import ( - Count, Exists, F, Max, Model, OuterRef, Q, QuerySet, + Count, Exists, F, Max, Model, OrderBy, OuterRef, Q, QuerySet, ) from django.db.models.functions import Coalesce, ExtractWeekDay from django.urls import reverse, reverse_lazy @@ -62,7 +62,7 @@ from pretix.base.signals import register_payment_providers from pretix.control.forms.widgets import Select2 from pretix.control.signals import order_search_filter_q from pretix.helpers.countries import CachedCountries -from pretix.helpers.database import FixedOrderBy, rolledback_transaction +from pretix.helpers.database import rolledback_transaction from pretix.helpers.dicts import move_to_end from pretix.helpers.i18n import i18ncomp @@ -1270,10 +1270,10 @@ class CheckInFilterForm(FilterForm): '-code': ('-order__code', '-item__name'), 'email': ('order__email', 'item__name'), '-email': ('-order__email', '-item__name'), - 'status': (FixedOrderBy(F('last_entry'), nulls_first=True, descending=True), 'order__code'), - '-status': (FixedOrderBy(F('last_entry'), nulls_last=True), '-order__code'), - 'timestamp': (FixedOrderBy(F('last_entry'), nulls_first=True), 'order__code'), - '-timestamp': (FixedOrderBy(F('last_entry'), nulls_last=True, descending=True), '-order__code'), + 'status': (OrderBy(F('last_entry'), nulls_first=True, descending=True), 'order__code'), + '-status': (OrderBy(F('last_entry'), nulls_last=True), '-order__code'), + 'timestamp': (OrderBy(F('last_entry'), nulls_first=True), 'order__code'), + '-timestamp': (OrderBy(F('last_entry'), nulls_last=True, descending=True), '-order__code'), 'item': ('item__name', 'variation__value', 'order__code'), '-item': ('-item__name', '-variation__value', '-order__code'), 'seat': ('seat__sorting_rank', 'seat__guid'), diff --git a/src/pretix/control/signals.py b/src/pretix/control/signals.py index bc851e860..00ad50805 100644 --- a/src/pretix/control/signals.py +++ b/src/pretix/control/signals.py @@ -36,9 +36,7 @@ from django.dispatch import Signal from pretix.base.signals import DeprecatedSignal, EventPluginSignal -html_page_start = Signal( - providing_args=[] -) +html_page_start = Signal() """ This signal allows you to put code in the beginning of the main page for every page in the backend. You are expected to return HTML. @@ -46,10 +44,10 @@ page in the backend. You are expected to return HTML. The ``sender`` keyword argument will contain the request. """ -html_head = EventPluginSignal( - providing_args=["request"] -) +html_head = EventPluginSignal() """ +Arguments: ``request`` + This signal allows you to put code inside the HTML ```` tag of every page in the backend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -57,10 +55,10 @@ of every page in the backend. You will get the request as the keyword argument As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -nav_event = EventPluginSignal( - providing_args=["request"] -) +nav_event = EventPluginSignal() """ +Arguments: ``request`` + This signal allows you to add additional views to the admin panel navigation. You will get the request as a keyword argument ``request``. Receivers are expected to return a list of dictionaries. The dictionaries @@ -82,10 +80,10 @@ in pretix. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -nav_topbar = Signal( - providing_args=["request"] -) +nav_topbar = Signal() """ +Arguments: ``request`` + This signal allows you to add additional views to the top navigation bar. You will get the request as a keyword argument ``request``. Receivers are expected to return a list of dictionaries. The dictionaries @@ -101,10 +99,10 @@ This is no ``EventPluginSignal``, so you do not get the event in the ``sender`` and you may get the signal regardless of whether your plugin is active. """ -nav_global = Signal( - providing_args=["request"] -) +nav_global = Signal() """ +Arguments: ``request`` + This signal allows you to add additional views to the navigation bar when no event is selected. You will get the request as a keyword argument ``request``. Receivers are expected to return a list of dictionaries. The dictionaries @@ -126,10 +124,10 @@ This is no ``EventPluginSignal``, so you do not get the event in the ``sender`` and you may get the signal regardless of whether your plugin is active. """ -event_dashboard_top = EventPluginSignal( - providing_args=['request'] -) +event_dashboard_top = EventPluginSignal() """ +Arguments: 'request' + This signal is sent out to include custom HTML in the top part of the the event dashboard. Receivers should return HTML. @@ -137,9 +135,7 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve An additional keyword argument ``subevent`` *can* contain a sub-event. """ -event_dashboard_widgets = EventPluginSignal( - providing_args=[] -) +event_dashboard_widgets = EventPluginSignal() """ This signal is sent out to include widgets in the event dashboard. Receivers should return a list of dictionaries, where each dictionary can have the keys: @@ -154,10 +150,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve An additional keyword argument ``subevent`` *can* contain a sub-event. """ -user_dashboard_widgets = Signal( - providing_args=['user'] -) +user_dashboard_widgets = Signal() """ +Arguments: 'user' + This signal is sent out to include widgets in the personal user dashboard. Receivers should return a list of dictionaries, where each dictionary can have the keys: @@ -170,20 +166,20 @@ should return a list of dictionaries, where each dictionary can have the keys: This is a regular django signal (no pretix event signal). """ -voucher_form_html = EventPluginSignal( - providing_args=['form'] -) +voucher_form_html = EventPluginSignal() """ +Arguments: 'form' + This signal allows you to add additional HTML to the form that is used for modifying vouchers. You receive the form object in the ``form`` keyword argument. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -voucher_form_class = EventPluginSignal( - providing_args=['cls'] -) +voucher_form_class = EventPluginSignal() """ +Arguments: ``cls`` + This signal allows you to replace the form class that is used for modifying vouchers. You will receive the default form class (or the class set by a previous plugin) in the ``cls`` argument so that you can inherit from it. @@ -196,10 +192,10 @@ for every batch persisted to the database. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -voucher_form_validation = EventPluginSignal( - providing_args=['form'] -) +voucher_form_validation = EventPluginSignal() """ +Arguments: 'form' + This signal allows you to add additional validation to the form that is used for creating and modifying vouchers. You will receive the form instance in the ``form`` argument and the current data state in the ``data`` argument. @@ -207,28 +203,28 @@ argument and the current data state in the ``data`` argument. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -quota_detail_html = EventPluginSignal( - providing_args=['quota'] -) +quota_detail_html = EventPluginSignal() """ +Arguments: 'quota' + This signal allows you to append HTML to a Quota's detail view. You receive the quota as argument in the ``quota`` keyword argument. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -organizer_edit_tabs = DeprecatedSignal( - providing_args=['organizer', 'request'] -) +organizer_edit_tabs = DeprecatedSignal() """ +Arguments: 'organizer', 'request' + Deprecated signal, no longer works. We just keep the definition so old plugins don't break the installation. """ -nav_organizer = Signal( - providing_args=['organizer', 'request'] -) +nav_organizer = Signal() """ +Arguments: 'organizer', 'request' + This signal is sent out to include tab links on the detail page of an organizer. Receivers are expected to return a list of dictionaries. The dictionaries should contain at least the keys ``label`` and ``url``. You should also return @@ -249,30 +245,30 @@ This is a regular django signal (no pretix event signal). Receivers will be pass the keyword arguments ``organizer`` and ``request``. """ -order_info = EventPluginSignal( - providing_args=["order", "request"] -) +order_info = EventPluginSignal() """ +Arguments: ``order``, ``request`` + This signal is sent out to display additional information on the order detail page As with all plugin signals, the ``sender`` keyword argument will contain the event. Additionally, the argument ``order`` and ``request`` are available. """ -order_position_buttons = EventPluginSignal( - providing_args=["order", "position", "request"] -) +order_position_buttons = EventPluginSignal() """ +Arguments: ``order``, ``position``, ``request`` + This signal is sent out to display additional buttons for a single position of an order. As with all plugin signals, the ``sender`` keyword argument will contain the event. Additionally, the argument ``order`` and ``request`` are available. """ -nav_event_settings = EventPluginSignal( - providing_args=['request'] -) +nav_event_settings = EventPluginSignal() """ +Arguments: 'request' + This signal is sent out to include tab links on the settings page of an event. Receivers are expected to return a list of dictionaries. The dictionaries should contain at least the keys ``label`` and ``url``. You should also return @@ -287,10 +283,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve A second keyword argument ``request`` will contain the request object. """ -event_settings_widget = EventPluginSignal( - providing_args=['request'] -) +event_settings_widget = EventPluginSignal() """ +Arguments: 'request' + This signal is sent out to include template snippets on the settings page of an event that allows generating a pretix Widget code. @@ -298,10 +294,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve A second keyword argument ``request`` will contain the request object. """ -item_forms = EventPluginSignal( - providing_args=['request', 'item'] -) +item_forms = EventPluginSignal() """ +Arguments: 'request', 'item' + This signal allows you to return additional forms that should be rendered on the product modification page. You are passed ``request`` and ``item`` arguments and are expected to return an instance of a form class that you bind yourself when appropriate. Your form will be executed @@ -311,10 +307,10 @@ styles. It is advisable to set a prefix for your form to avoid clashes with othe As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -item_formsets = EventPluginSignal( - providing_args=['request', 'item'] -) +item_formsets = EventPluginSignal() """ +Arguments: 'request', 'item' + This signal allows you to return additional formsets that should be rendered on the product modification page. You are passed ``request`` and ``item`` arguments and are expected to return an instance of a formset class that you bind yourself when appropriate. Your formset will be @@ -329,10 +325,10 @@ will be passed a ``formset`` variable with your formset. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -subevent_forms = EventPluginSignal( - providing_args=['request', 'subevent', 'copy_from'] -) +subevent_forms = EventPluginSignal() """ +Arguments: 'request', 'subevent', 'copy_from' + This signal allows you to return additional forms that should be rendered on the subevent creation or modification page. You are passed ``request`` and ``subevent`` arguments and are expected to return an instance of a form class that you bind yourself when appropriate. Your form will be executed @@ -346,17 +342,17 @@ creation, ``copy_from`` can be a subevent that is being copied from. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -oauth_application_registered = Signal( - providing_args=["user", "application"] -) +oauth_application_registered = Signal() """ +Arguments: ``user``, ``application`` + This signal will be called whenever a user registers a new OAuth application. """ -order_search_filter_q = Signal( - providing_args=["query"] -) +order_search_filter_q = Signal() """ +Arguments: ``query`` + This signal will be called whenever a free-text order search is performed. You are expected to return one Q object that will be OR-ed with existing search queries. As order search exists on a global level as well, this is not an Event signal and will be called even if your plugin is not active. ``sender`` will contain the @@ -364,10 +360,10 @@ event if the search is performed within an event, and ``None`` otherwise. The se ``query``. """ -order_search_forms = EventPluginSignal( - providing_args=['request'] -) +order_search_forms = EventPluginSignal() """ +Arguments: 'request' + This signal allows you to return additional forms that should be rendered in the advanced order search. You are passed ``request`` argument and are expected to return an instance of a form class that you bind yourself when appropriate. Your form will be executed as part of the standard validation and rendering diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index ca08c6517..b4370c92c 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -33,7 +33,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. -from django.conf.urls import include, url +from django.conf.urls import include, re_path from django.views.generic.base import RedirectView from pretix.control.views import ( @@ -43,334 +43,334 @@ from pretix.control.views import ( ) urlpatterns = [ - url(r'^logout$', auth.logout, name='auth.logout'), - url(r'^login$', auth.login, name='auth.login'), - url(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'), - url(r'^register$', auth.register, name='auth.register'), - url(r'^invite/(?P[a-zA-Z0-9]+)$', auth.invite, name='auth.invite'), - url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'), - url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'), - url(r'^$', dashboards.user_index, name='index'), - url(r'^widgets.json$', dashboards.user_index_widgets_lazy, name='index.widgets'), - url(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'), - url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'), - url(r'^global/license/$', global_settings.LicenseCheckView.as_view(), name='global.license'), - url(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'), - url(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'), - url(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'), - url(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'), - url(r'^geocode/$', geo.GeoCodeView.as_view(), name='global.geocode'), - url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'), - url(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'), - url(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'), - url(r'^sudo/(?P\d+)/$', user.EditStaffSession.as_view(), name='user.sudo.edit'), - url(r'^sudo/sessions/$', user.StaffSessionList.as_view(), name='user.sudo.list'), - url(r'^users/$', users.UserListView.as_view(), name='users'), - url(r'^users/select2$', typeahead.users_select2, name='users.select2'), - url(r'^users/add$', users.UserCreateView.as_view(), name='users.add'), - url(r'^users/impersonate/stop', users.UserImpersonateStopView.as_view(), name='users.impersonate.stop'), - url(r'^users/(?P\d+)/$', users.UserEditView.as_view(), name='users.edit'), - url(r'^users/(?P\d+)/reset$', users.UserResetView.as_view(), name='users.reset'), - url(r'^users/(?P\d+)/impersonate', users.UserImpersonateView.as_view(), name='users.impersonate'), - url(r'^users/(?P\d+)/anonymize', users.UserAnonymizeView.as_view(), name='users.anonymize'), - url(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'), - url(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'), - url(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'), - url(r'^settings/notifications/$', user.UserNotificationsEditView.as_view(), name='user.settings.notifications'), - url(r'^settings/notifications/off/(?P\d+)/(?P[^/]+)/$', user.UserNotificationsDisableView.as_view(), - name='user.settings.notifications.off'), - url(r'^settings/oauth/authorized/$', oauth.AuthorizationListView.as_view(), - name='user.settings.oauth.list'), - url(r'^settings/oauth/authorized/(?P\d+)/revoke$', oauth.AuthorizationRevokeView.as_view(), - name='user.settings.oauth.revoke'), - url(r'^settings/oauth/apps/$', oauth.OAuthApplicationListView.as_view(), - name='user.settings.oauth.apps'), - url(r'^settings/oauth/apps/add$', oauth.OAuthApplicationRegistrationView.as_view(), - name='user.settings.oauth.apps.register'), - url(r'^settings/oauth/apps/(?P\d+)/$', oauth.OAuthApplicationUpdateView.as_view(), - name='user.settings.oauth.app'), - url(r'^settings/oauth/apps/(?P\d+)/disable$', oauth.OAuthApplicationDeleteView.as_view(), - name='user.settings.oauth.app.disable'), - url(r'^settings/oauth/apps/(?P\d+)/roll$', oauth.OAuthApplicationRollView.as_view(), - name='user.settings.oauth.app.roll'), - url(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'), - url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'), - url(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'), - url(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'), - url(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(), - name='user.settings.2fa.regenemergency'), - url(r'^settings/2fa/totp/(?P[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(), - name='user.settings.2fa.confirm.totp'), - url(r'^settings/2fa/webauthn/(?P[0-9]+)/confirm', user.User2FADeviceConfirmWebAuthnView.as_view(), - name='user.settings.2fa.confirm.webauthn'), - url(r'^settings/2fa/(?P[^/]+)/(?P[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(), - name='user.settings.2fa.delete'), - url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'), - url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'), - url(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'), - url(r'^organizer/(?P[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'), - url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'), - url(r'^organizer/(?P[^/]+)/settings/email$', - organizer.OrganizerMailSettings.as_view(), name='organizer.settings.mail'), - url(r'^organizer/(?P[^/]+)/settings/email/preview$', - organizer.MailSettingsPreview.as_view(), name='organizer.settings.mail.preview'), - url(r'^organizer/(?P[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'), - url(r'^organizer/(?P[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(), - name='organizer.display'), - url(r'^organizer/(?P[^/]+)/properties$', organizer.EventMetaPropertyListView.as_view(), name='organizer.properties'), - url(r'^organizer/(?P[^/]+)/property/add$', organizer.EventMetaPropertyCreateView.as_view(), - name='organizer.property.add'), - url(r'^organizer/(?P[^/]+)/property/(?P[^/]+)/edit$', organizer.EventMetaPropertyUpdateView.as_view(), - name='organizer.property.edit'), - url(r'^organizer/(?P[^/]+)/property/(?P[^/]+)/delete$', organizer.EventMetaPropertyDeleteView.as_view(), - name='organizer.property.delete'), - url(r'^organizer/(?P[^/]+)/membershiptypes$', organizer.MembershipTypeListView.as_view(), name='organizer.membershiptypes'), - url(r'^organizer/(?P[^/]+)/membershiptype/add$', organizer.MembershipTypeCreateView.as_view(), - name='organizer.membershiptype.add'), - url(r'^organizer/(?P[^/]+)/membershiptype/(?P[^/]+)/edit$', organizer.MembershipTypeUpdateView.as_view(), - name='organizer.membershiptype.edit'), - url(r'^organizer/(?P[^/]+)/membershiptype/(?P[^/]+)/delete$', organizer.MembershipTypeDeleteView.as_view(), - name='organizer.membershiptype.delete'), - url(r'^organizer/(?P[^/]+)/customers$', organizer.CustomerListView.as_view(), name='organizer.customers'), - url(r'^organizer/(?P[^/]+)/customers/select2$', typeahead.customer_select2, name='organizer.customers.select2'), - url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/$', - organizer.CustomerDetailView.as_view(), name='organizer.customer'), - url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/edit$', - organizer.CustomerUpdateView.as_view(), name='organizer.customer.edit'), - url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/membership/add$', - organizer.MembershipCreateView.as_view(), name='organizer.customer.membership.add'), - url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/membership/(?P[^/]+)/edit$', - organizer.MembershipUpdateView.as_view(), name='organizer.customer.membership.edit'), - url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/anonymize$', - organizer.CustomerAnonymizeView.as_view(), name='organizer.customer.anonymize'), - url(r'^organizer/(?P[^/]+)/giftcards$', organizer.GiftCardListView.as_view(), name='organizer.giftcards'), - url(r'^organizer/(?P[^/]+)/giftcard/add$', organizer.GiftCardCreateView.as_view(), name='organizer.giftcard.add'), - url(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/$', organizer.GiftCardDetailView.as_view(), name='organizer.giftcard'), - url(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/edit$', organizer.GiftCardUpdateView.as_view(), - name='organizer.giftcard.edit'), - url(r'^organizer/(?P[^/]+)/webhooks$', organizer.WebHookListView.as_view(), name='organizer.webhooks'), - url(r'^organizer/(?P[^/]+)/webhook/add$', organizer.WebHookCreateView.as_view(), - name='organizer.webhook.add'), - url(r'^organizer/(?P[^/]+)/webhook/(?P[^/]+)/edit$', organizer.WebHookUpdateView.as_view(), - name='organizer.webhook.edit'), - url(r'^organizer/(?P[^/]+)/webhook/(?P[^/]+)/logs$', organizer.WebHookLogsView.as_view(), - name='organizer.webhook.logs'), - url(r'^organizer/(?P[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'), - url(r'^organizer/(?P[^/]+)/device/add$', organizer.DeviceCreateView.as_view(), - name='organizer.device.add'), - url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/edit$', organizer.DeviceUpdateView.as_view(), - name='organizer.device.edit'), - url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/connect$', organizer.DeviceConnectView.as_view(), - name='organizer.device.connect'), - url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/revoke$', organizer.DeviceRevokeView.as_view(), - name='organizer.device.revoke'), - url(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/logs$', organizer.DeviceLogView.as_view(), - name='organizer.device.logs'), - url(r'^organizer/(?P[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'), - url(r'^organizer/(?P[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'), - url(r'^organizer/(?P[^/]+)/gate/(?P[^/]+)/edit$', organizer.GateUpdateView.as_view(), - name='organizer.gate.edit'), - url(r'^organizer/(?P[^/]+)/gate/(?P[^/]+)/delete$', organizer.GateDeleteView.as_view(), - name='organizer.gate.delete'), - url(r'^organizer/(?P[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'), - url(r'^organizer/(?P[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'), - url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/$', organizer.TeamMemberView.as_view(), - name='organizer.team'), - url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/edit$', organizer.TeamUpdateView.as_view(), - name='organizer.team.edit'), - url(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/delete$', organizer.TeamDeleteView.as_view(), - name='organizer.team.delete'), - url(r'^organizer/(?P[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'), - url(r'^organizer/(?P[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'), - url(r'^organizer/(?P[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'), - url(r'^organizer/(?P[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'), - url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'), - url(r'^events/$', main.EventList.as_view(), name='events'), - url(r'^events/add$', main.EventWizard.as_view(), name='events.add'), - url(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'), - url(r'^events/typeahead/meta/$', typeahead.meta_values, name='events.meta.typeahead'), - url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'), - url(r'^event/(?P[^/]+)/(?P[^/]+)/', include([ - url(r'^$', dashboards.event_index, name='event.index'), - url(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'), - url(r'^live/$', event.EventLive.as_view(), name='event.live'), - url(r'^logs/$', event.EventLog.as_view(), name='event.log'), - url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'), - url(r'^requiredactions/$', event.EventActions.as_view(), name='event.requiredactions'), - url(r'^requiredactions/(?P\d+)/discard$', event.EventActionDiscard.as_view(), - name='event.requiredaction.discard'), - url(r'^comment/$', event.EventComment.as_view(), - name='event.comment'), - url(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'), - url(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'), - url(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'), - url(r'^settings/payment/(?P[^/]+)$', event.PaymentProviderSettings.as_view(), - name='event.settings.payment.provider'), - url(r'^settings/payment$', event.PaymentSettings.as_view(), name='event.settings.payment'), - url(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'), - url(r'^settings/tickets/preview/(?P[^/]+)$', event.TicketSettingsPreview.as_view(), - name='event.settings.tickets.preview'), - url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'), - url(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'), - url(r'^settings/email/layoutpreview$', event.MailSettingsRendererPreview.as_view(), - name='event.settings.mail.preview.layout'), - url(r'^settings/cancel', event.CancelSettings.as_view(), name='event.settings.cancel'), - url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'), - url(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'), - url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'), - url(r'^settings/tax/$', event.TaxList.as_view(), name='event.settings.tax'), - url(r'^settings/tax/(?P\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'), - url(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'), - url(r'^settings/tax/(?P\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'), - url(r'^settings/widget$', event.WidgetSettings.as_view(), name='event.settings.widget'), - url(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'), - url(r'^pdf/editor/(?P[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'), - url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'), - url(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'), - url(r'^subevents/(?P\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'), - url(r'^subevents/(?P\d+)/delete$', subevents.SubEventDelete.as_view(), - name='event.subevent.delete'), - url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'), - url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'), - url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'), - url(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'), - url(r'^items/$', item.ItemList.as_view(), name='event.items'), - url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'), - url(r'^items/(?P\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'), - url(r'^items/(?P\d+)/up$', item.item_move_up, name='event.items.up'), - url(r'^items/(?P\d+)/down$', item.item_move_down, name='event.items.down'), - url(r'^items/(?P\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'), - url(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'), - url(r'^items/select2$', typeahead.items_select2, name='event.items.select2'), - url(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'), - url(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'), - url(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'), - url(r'^categories/(?P\d+)/delete$', item.CategoryDelete.as_view(), - name='event.items.categories.delete'), - url(r'^categories/(?P\d+)/up$', item.category_move_up, name='event.items.categories.up'), - url(r'^categories/(?P\d+)/down$', item.category_move_down, - name='event.items.categories.down'), - url(r'^categories/(?P\d+)/$', item.CategoryUpdate.as_view(), - name='event.items.categories.edit'), - url(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'), - url(r'^questions/$', item.QuestionList.as_view(), name='event.items.questions'), - url(r'^questions/reorder$', item.reorder_questions, name='event.items.questions.reorder'), - url(r'^questions/(?P\d+)/delete$', item.QuestionDelete.as_view(), - name='event.items.questions.delete'), - url(r'^questions/(?P\d+)/$', item.QuestionView.as_view(), - name='event.items.questions.show'), - url(r'^questions/(?P\d+)/change$', item.QuestionUpdate.as_view(), - name='event.items.questions.edit'), - url(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'), - url(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'), - url(r'^quotas/(?P\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'), - url(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'), - url(r'^quotas/(?P\d+)/change$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'), - url(r'^quotas/(?P\d+)/delete$', item.QuotaDelete.as_view(), - name='event.items.quotas.delete'), - url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), - url(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'), - url(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'), - url(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'), - url(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'), - url(r'^vouchers/(?P\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'), - url(r'^vouchers/(?P\d+)/delete$', vouchers.VoucherDelete.as_view(), - name='event.voucher.delete'), - url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'), - url(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'), - url(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'), - url(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'), - url(r'^orders/(?P[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(), - name='event.order.transition'), - url(r'^orders/(?P[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(), - name='event.order.resendlink'), - url(r'^orders/(?P[0-9A-Z]+)/(?P\d+)/resend$', orders.OrderResendLink.as_view(), - name='event.order.resendlink'), - url(r'^orders/(?P[0-9A-Z]+)/invoice$', orders.OrderInvoiceCreate.as_view(), - name='event.order.geninvoice'), - url(r'^orders/(?P[0-9A-Z]+)/invoices/(?P\d+)/regenerate$', orders.OrderInvoiceRegenerate.as_view(), - name='event.order.regeninvoice'), - url(r'^orders/(?P[0-9A-Z]+)/invoices/(?P\d+)/reissue$', orders.OrderInvoiceReissue.as_view(), - name='event.order.reissueinvoice'), - url(r'^orders/(?P[0-9A-Z]+)/download/(?P\d+)/(?P[^/]+)/$', - orders.OrderDownload.as_view(), - name='event.order.download.ticket'), - url(r'^orders/(?P[0-9A-Z]+)/answer/(?P[^/]+)/$', - orders.AnswerDownload.as_view(), - name='event.order.download.answer'), - url(r'^orders/(?P[0-9A-Z]+)/checkvatid', orders.OrderCheckVATID.as_view(), - name='event.order.checkvatid'), - url(r'^orders/(?P[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(), - name='event.order.extend'), - url(r'^orders/(?P[0-9A-Z]+)/reactivate$', orders.OrderReactivate.as_view(), - name='event.order.reactivate'), - url(r'^orders/(?P[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(), - name='event.order.contact'), - url(r'^orders/(?P[0-9A-Z]+)/locale', orders.OrderLocaleChange.as_view(), - name='event.order.locale'), - url(r'^orders/(?P[0-9A-Z]+)/comment$', orders.OrderComment.as_view(), - name='event.order.comment'), - url(r'^orders/(?P[0-9A-Z]+)/change$', orders.OrderChange.as_view(), - name='event.order.change'), - url(r'^orders/(?P[0-9A-Z]+)/approve', orders.OrderApprove.as_view(), - name='event.order.approve'), - url(r'^orders/(?P[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(), - name='event.order.deny'), - url(r'^orders/(?P[0-9A-Z]+)/delete$', orders.OrderDelete.as_view(), - name='event.order.delete'), - url(r'^orders/(?P[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(), - name='event.order.info'), - url(r'^orders/(?P[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(), - name='event.order.sendmail'), - url(r'^orders/(?P[0-9A-Z]+)/(?P[0-9A-Z]+)/sendmail$', orders.OrderPositionSendMail.as_view(), - name='event.order.position.sendmail'), - url(r'^orders/(?P[0-9A-Z]+)/mail_history$', orders.OrderEmailHistory.as_view(), - name='event.order.mail_history'), - url(r'^orders/(?P[0-9A-Z]+)/payments/(?P\d+)/cancel$', orders.OrderPaymentCancel.as_view(), - name='event.order.payments.cancel'), - url(r'^orders/(?P[0-9A-Z]+)/payments/(?P\d+)/confirm$', orders.OrderPaymentConfirm.as_view(), - name='event.order.payments.confirm'), - url(r'^orders/(?P[0-9A-Z]+)/refund$', orders.OrderRefundView.as_view(), - name='event.order.refunds.start'), - url(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/cancel$', orders.OrderRefundCancel.as_view(), - name='event.order.refunds.cancel'), - url(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/process$', orders.OrderRefundProcess.as_view(), - name='event.order.refunds.process'), - url(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/done$', orders.OrderRefundDone.as_view(), - name='event.order.refunds.done'), - url(r'^orders/(?P[0-9A-Z]+)/cancellationrequests/(?P\d+)/delete$', - orders.OrderCancellationRequestDelete.as_view(), - name='event.order.cancellationrequests.delete'), - url(r'^orders/(?P[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'), - url(r'^invoice/(?P[^/]+)$', orders.InvoiceDownload.as_view(), - name='event.invoice.download'), - url(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'), - url(r'^orders/import/$', orderimport.ImportView.as_view(), name='event.orders.import'), - url(r'^orders/import/(?P[^/]+)/$', orderimport.ProcessView.as_view(), name='event.orders.import.process'), - url(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'), - url(r'^orders/export/do$', orders.ExportDoView.as_view(), name='event.orders.export.do'), - url(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'), - url(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'), - url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'), - url(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'), - url(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'), - url(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'), - url(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'), - url(r'^shredder/export$', shredder.ShredExportView.as_view(), name='event.shredder.export'), - url(r'^shredder/download/(?P[^/]+)/$', shredder.ShredDownloadView.as_view(), name='event.shredder.download'), - url(r'^shredder/shred', shredder.ShredDoView.as_view(), name='event.shredder.shred'), - url(r'^waitinglist/$', waitinglist.WaitingListView.as_view(), name='event.orders.waitinglist'), - url(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'), - url(r'^waitinglist/(?P\d+)/delete$', waitinglist.EntryDelete.as_view(), - name='event.orders.waitinglist.delete'), - url(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'), - url(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'), - url(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'), - url(r'^checkinlists/(?P\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'), - url(r'^checkinlists/(?P\d+)/change$', checkin.CheckinListUpdate.as_view(), - name='event.orders.checkinlists.edit'), - url(r'^checkinlists/(?P\d+)/delete$', checkin.CheckinListDelete.as_view(), - name='event.orders.checkinlists.delete'), + re_path(r'^logout$', auth.logout, name='auth.logout'), + re_path(r'^login$', auth.login, name='auth.login'), + re_path(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'), + re_path(r'^register$', auth.register, name='auth.register'), + re_path(r'^invite/(?P[a-zA-Z0-9]+)$', auth.invite, name='auth.invite'), + re_path(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'), + re_path(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'), + re_path(r'^$', dashboards.user_index, name='index'), + re_path(r'^widgets.json$', dashboards.user_index_widgets_lazy, name='index.widgets'), + re_path(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'), + re_path(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'), + re_path(r'^global/license/$', global_settings.LicenseCheckView.as_view(), name='global.license'), + re_path(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'), + re_path(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'), + re_path(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'), + re_path(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'), + re_path(r'^geocode/$', geo.GeoCodeView.as_view(), name='global.geocode'), + re_path(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'), + re_path(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'), + re_path(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'), + re_path(r'^sudo/(?P\d+)/$', user.EditStaffSession.as_view(), name='user.sudo.edit'), + re_path(r'^sudo/sessions/$', user.StaffSessionList.as_view(), name='user.sudo.list'), + re_path(r'^users/$', users.UserListView.as_view(), name='users'), + re_path(r'^users/select2$', typeahead.users_select2, name='users.select2'), + re_path(r'^users/add$', users.UserCreateView.as_view(), name='users.add'), + re_path(r'^users/impersonate/stop', users.UserImpersonateStopView.as_view(), name='users.impersonate.stop'), + re_path(r'^users/(?P\d+)/$', users.UserEditView.as_view(), name='users.edit'), + re_path(r'^users/(?P\d+)/reset$', users.UserResetView.as_view(), name='users.reset'), + re_path(r'^users/(?P\d+)/impersonate', users.UserImpersonateView.as_view(), name='users.impersonate'), + re_path(r'^users/(?P\d+)/anonymize', users.UserAnonymizeView.as_view(), name='users.anonymize'), + re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'), + re_path(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'), + re_path(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'), + re_path(r'^settings/notifications/$', user.UserNotificationsEditView.as_view(), name='user.settings.notifications'), + re_path(r'^settings/notifications/off/(?P\d+)/(?P[^/]+)/$', user.UserNotificationsDisableView.as_view(), + name='user.settings.notifications.off'), + re_path(r'^settings/oauth/authorized/$', oauth.AuthorizationListView.as_view(), + name='user.settings.oauth.list'), + re_path(r'^settings/oauth/authorized/(?P\d+)/revoke$', oauth.AuthorizationRevokeView.as_view(), + name='user.settings.oauth.revoke'), + re_path(r'^settings/oauth/apps/$', oauth.OAuthApplicationListView.as_view(), + name='user.settings.oauth.apps'), + re_path(r'^settings/oauth/apps/add$', oauth.OAuthApplicationRegistrationView.as_view(), + name='user.settings.oauth.apps.register'), + re_path(r'^settings/oauth/apps/(?P\d+)/$', oauth.OAuthApplicationUpdateView.as_view(), + name='user.settings.oauth.app'), + re_path(r'^settings/oauth/apps/(?P\d+)/disable$', oauth.OAuthApplicationDeleteView.as_view(), + name='user.settings.oauth.app.disable'), + re_path(r'^settings/oauth/apps/(?P\d+)/roll$', oauth.OAuthApplicationRollView.as_view(), + name='user.settings.oauth.app.roll'), + re_path(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'), + re_path(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'), + re_path(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'), + re_path(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'), + re_path(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(), + name='user.settings.2fa.regenemergency'), + re_path(r'^settings/2fa/totp/(?P[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(), + name='user.settings.2fa.confirm.totp'), + re_path(r'^settings/2fa/webauthn/(?P[0-9]+)/confirm', user.User2FADeviceConfirmWebAuthnView.as_view(), + name='user.settings.2fa.confirm.webauthn'), + re_path(r'^settings/2fa/(?P[^/]+)/(?P[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(), + name='user.settings.2fa.delete'), + re_path(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'), + re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'), + re_path(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'), + re_path(r'^organizer/(?P[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'), + re_path(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'), + re_path(r'^organizer/(?P[^/]+)/settings/email$', + organizer.OrganizerMailSettings.as_view(), name='organizer.settings.mail'), + re_path(r'^organizer/(?P[^/]+)/settings/email/preview$', + organizer.MailSettingsPreview.as_view(), name='organizer.settings.mail.preview'), + re_path(r'^organizer/(?P[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'), + re_path(r'^organizer/(?P[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(), + name='organizer.display'), + re_path(r'^organizer/(?P[^/]+)/properties$', organizer.EventMetaPropertyListView.as_view(), name='organizer.properties'), + re_path(r'^organizer/(?P[^/]+)/property/add$', organizer.EventMetaPropertyCreateView.as_view(), + name='organizer.property.add'), + re_path(r'^organizer/(?P[^/]+)/property/(?P[^/]+)/edit$', organizer.EventMetaPropertyUpdateView.as_view(), + name='organizer.property.edit'), + re_path(r'^organizer/(?P[^/]+)/property/(?P[^/]+)/delete$', organizer.EventMetaPropertyDeleteView.as_view(), + name='organizer.property.delete'), + re_path(r'^organizer/(?P[^/]+)/membershiptypes$', organizer.MembershipTypeListView.as_view(), name='organizer.membershiptypes'), + re_path(r'^organizer/(?P[^/]+)/membershiptype/add$', organizer.MembershipTypeCreateView.as_view(), + name='organizer.membershiptype.add'), + re_path(r'^organizer/(?P[^/]+)/membershiptype/(?P[^/]+)/edit$', organizer.MembershipTypeUpdateView.as_view(), + name='organizer.membershiptype.edit'), + re_path(r'^organizer/(?P[^/]+)/membershiptype/(?P[^/]+)/delete$', organizer.MembershipTypeDeleteView.as_view(), + name='organizer.membershiptype.delete'), + re_path(r'^organizer/(?P[^/]+)/customers$', organizer.CustomerListView.as_view(), name='organizer.customers'), + re_path(r'^organizer/(?P[^/]+)/customers/select2$', typeahead.customer_select2, name='organizer.customers.select2'), + re_path(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/$', + organizer.CustomerDetailView.as_view(), name='organizer.customer'), + re_path(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/edit$', + organizer.CustomerUpdateView.as_view(), name='organizer.customer.edit'), + re_path(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/membership/add$', + organizer.MembershipCreateView.as_view(), name='organizer.customer.membership.add'), + re_path(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/membership/(?P[^/]+)/edit$', + organizer.MembershipUpdateView.as_view(), name='organizer.customer.membership.edit'), + re_path(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/anonymize$', + organizer.CustomerAnonymizeView.as_view(), name='organizer.customer.anonymize'), + re_path(r'^organizer/(?P[^/]+)/giftcards$', organizer.GiftCardListView.as_view(), name='organizer.giftcards'), + re_path(r'^organizer/(?P[^/]+)/giftcard/add$', organizer.GiftCardCreateView.as_view(), name='organizer.giftcard.add'), + re_path(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/$', organizer.GiftCardDetailView.as_view(), name='organizer.giftcard'), + re_path(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/edit$', organizer.GiftCardUpdateView.as_view(), + name='organizer.giftcard.edit'), + re_path(r'^organizer/(?P[^/]+)/webhooks$', organizer.WebHookListView.as_view(), name='organizer.webhooks'), + re_path(r'^organizer/(?P[^/]+)/webhook/add$', organizer.WebHookCreateView.as_view(), + name='organizer.webhook.add'), + re_path(r'^organizer/(?P[^/]+)/webhook/(?P[^/]+)/edit$', organizer.WebHookUpdateView.as_view(), + name='organizer.webhook.edit'), + re_path(r'^organizer/(?P[^/]+)/webhook/(?P[^/]+)/logs$', organizer.WebHookLogsView.as_view(), + name='organizer.webhook.logs'), + re_path(r'^organizer/(?P[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'), + re_path(r'^organizer/(?P[^/]+)/device/add$', organizer.DeviceCreateView.as_view(), + name='organizer.device.add'), + re_path(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/edit$', organizer.DeviceUpdateView.as_view(), + name='organizer.device.edit'), + re_path(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/connect$', organizer.DeviceConnectView.as_view(), + name='organizer.device.connect'), + re_path(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/revoke$', organizer.DeviceRevokeView.as_view(), + name='organizer.device.revoke'), + re_path(r'^organizer/(?P[^/]+)/device/(?P[^/]+)/logs$', organizer.DeviceLogView.as_view(), + name='organizer.device.logs'), + re_path(r'^organizer/(?P[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'), + re_path(r'^organizer/(?P[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'), + re_path(r'^organizer/(?P[^/]+)/gate/(?P[^/]+)/edit$', organizer.GateUpdateView.as_view(), + name='organizer.gate.edit'), + re_path(r'^organizer/(?P[^/]+)/gate/(?P[^/]+)/delete$', organizer.GateDeleteView.as_view(), + name='organizer.gate.delete'), + re_path(r'^organizer/(?P[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'), + re_path(r'^organizer/(?P[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'), + re_path(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/$', organizer.TeamMemberView.as_view(), + name='organizer.team'), + re_path(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/edit$', organizer.TeamUpdateView.as_view(), + name='organizer.team.edit'), + re_path(r'^organizer/(?P[^/]+)/team/(?P[^/]+)/delete$', organizer.TeamDeleteView.as_view(), + name='organizer.team.delete'), + re_path(r'^organizer/(?P[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'), + re_path(r'^organizer/(?P[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'), + re_path(r'^organizer/(?P[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'), + re_path(r'^organizer/(?P[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'), + re_path(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'), + re_path(r'^events/$', main.EventList.as_view(), name='events'), + re_path(r'^events/add$', main.EventWizard.as_view(), name='events.add'), + re_path(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'), + re_path(r'^events/typeahead/meta/$', typeahead.meta_values, name='events.meta.typeahead'), + re_path(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'), + re_path(r'^event/(?P[^/]+)/(?P[^/]+)/', include([ + re_path(r'^$', dashboards.event_index, name='event.index'), + re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'), + re_path(r'^live/$', event.EventLive.as_view(), name='event.live'), + re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'), + re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'), + re_path(r'^requiredactions/$', event.EventActions.as_view(), name='event.requiredactions'), + re_path(r'^requiredactions/(?P\d+)/discard$', event.EventActionDiscard.as_view(), + name='event.requiredaction.discard'), + re_path(r'^comment/$', event.EventComment.as_view(), + name='event.comment'), + re_path(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'), + re_path(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'), + re_path(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'), + re_path(r'^settings/payment/(?P[^/]+)$', event.PaymentProviderSettings.as_view(), + name='event.settings.payment.provider'), + re_path(r'^settings/payment$', event.PaymentSettings.as_view(), name='event.settings.payment'), + re_path(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'), + re_path(r'^settings/tickets/preview/(?P[^/]+)$', event.TicketSettingsPreview.as_view(), + name='event.settings.tickets.preview'), + re_path(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'), + re_path(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'), + re_path(r'^settings/email/layoutpreview$', event.MailSettingsRendererPreview.as_view(), + name='event.settings.mail.preview.layout'), + re_path(r'^settings/cancel', event.CancelSettings.as_view(), name='event.settings.cancel'), + re_path(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'), + re_path(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'), + re_path(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'), + re_path(r'^settings/tax/$', event.TaxList.as_view(), name='event.settings.tax'), + re_path(r'^settings/tax/(?P\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'), + re_path(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'), + re_path(r'^settings/tax/(?P\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'), + re_path(r'^settings/widget$', event.WidgetSettings.as_view(), name='event.settings.widget'), + re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'), + re_path(r'^pdf/editor/(?P[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'), + re_path(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'), + re_path(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'), + re_path(r'^subevents/(?P\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'), + re_path(r'^subevents/(?P\d+)/delete$', subevents.SubEventDelete.as_view(), + name='event.subevent.delete'), + re_path(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'), + re_path(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'), + re_path(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'), + re_path(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'), + re_path(r'^items/$', item.ItemList.as_view(), name='event.items'), + re_path(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'), + re_path(r'^items/(?P\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'), + re_path(r'^items/(?P\d+)/up$', item.item_move_up, name='event.items.up'), + re_path(r'^items/(?P\d+)/down$', item.item_move_down, name='event.items.down'), + re_path(r'^items/(?P\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'), + re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'), + re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'), + re_path(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'), + re_path(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'), + re_path(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'), + re_path(r'^categories/(?P\d+)/delete$', item.CategoryDelete.as_view(), + name='event.items.categories.delete'), + re_path(r'^categories/(?P\d+)/up$', item.category_move_up, name='event.items.categories.up'), + re_path(r'^categories/(?P\d+)/down$', item.category_move_down, + name='event.items.categories.down'), + re_path(r'^categories/(?P\d+)/$', item.CategoryUpdate.as_view(), + name='event.items.categories.edit'), + re_path(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'), + re_path(r'^questions/$', item.QuestionList.as_view(), name='event.items.questions'), + re_path(r'^questions/reorder$', item.reorder_questions, name='event.items.questions.reorder'), + re_path(r'^questions/(?P\d+)/delete$', item.QuestionDelete.as_view(), + name='event.items.questions.delete'), + re_path(r'^questions/(?P\d+)/$', item.QuestionView.as_view(), + name='event.items.questions.show'), + re_path(r'^questions/(?P\d+)/change$', item.QuestionUpdate.as_view(), + name='event.items.questions.edit'), + re_path(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'), + re_path(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'), + re_path(r'^quotas/(?P\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'), + re_path(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'), + re_path(r'^quotas/(?P\d+)/change$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'), + re_path(r'^quotas/(?P\d+)/delete$', item.QuotaDelete.as_view(), + name='event.items.quotas.delete'), + re_path(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), + re_path(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'), + re_path(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'), + re_path(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'), + re_path(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'), + re_path(r'^vouchers/(?P\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'), + re_path(r'^vouchers/(?P\d+)/delete$', vouchers.VoucherDelete.as_view(), + name='event.voucher.delete'), + re_path(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'), + re_path(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'), + re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'), + re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'), + re_path(r'^orders/(?P[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(), + name='event.order.transition'), + re_path(r'^orders/(?P[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(), + name='event.order.resendlink'), + re_path(r'^orders/(?P[0-9A-Z]+)/(?P\d+)/resend$', orders.OrderResendLink.as_view(), + name='event.order.resendlink'), + re_path(r'^orders/(?P[0-9A-Z]+)/invoice$', orders.OrderInvoiceCreate.as_view(), + name='event.order.geninvoice'), + re_path(r'^orders/(?P[0-9A-Z]+)/invoices/(?P\d+)/regenerate$', orders.OrderInvoiceRegenerate.as_view(), + name='event.order.regeninvoice'), + re_path(r'^orders/(?P[0-9A-Z]+)/invoices/(?P\d+)/reissue$', orders.OrderInvoiceReissue.as_view(), + name='event.order.reissueinvoice'), + re_path(r'^orders/(?P[0-9A-Z]+)/download/(?P\d+)/(?P[^/]+)/$', + orders.OrderDownload.as_view(), + name='event.order.download.ticket'), + re_path(r'^orders/(?P[0-9A-Z]+)/answer/(?P[^/]+)/$', + orders.AnswerDownload.as_view(), + name='event.order.download.answer'), + re_path(r'^orders/(?P[0-9A-Z]+)/checkvatid', orders.OrderCheckVATID.as_view(), + name='event.order.checkvatid'), + re_path(r'^orders/(?P[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(), + name='event.order.extend'), + re_path(r'^orders/(?P[0-9A-Z]+)/reactivate$', orders.OrderReactivate.as_view(), + name='event.order.reactivate'), + re_path(r'^orders/(?P[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(), + name='event.order.contact'), + re_path(r'^orders/(?P[0-9A-Z]+)/locale', orders.OrderLocaleChange.as_view(), + name='event.order.locale'), + re_path(r'^orders/(?P[0-9A-Z]+)/comment$', orders.OrderComment.as_view(), + name='event.order.comment'), + re_path(r'^orders/(?P[0-9A-Z]+)/change$', orders.OrderChange.as_view(), + name='event.order.change'), + re_path(r'^orders/(?P[0-9A-Z]+)/approve', orders.OrderApprove.as_view(), + name='event.order.approve'), + re_path(r'^orders/(?P[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(), + name='event.order.deny'), + re_path(r'^orders/(?P[0-9A-Z]+)/delete$', orders.OrderDelete.as_view(), + name='event.order.delete'), + re_path(r'^orders/(?P[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(), + name='event.order.info'), + re_path(r'^orders/(?P[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(), + name='event.order.sendmail'), + re_path(r'^orders/(?P[0-9A-Z]+)/(?P[0-9A-Z]+)/sendmail$', orders.OrderPositionSendMail.as_view(), + name='event.order.position.sendmail'), + re_path(r'^orders/(?P[0-9A-Z]+)/mail_history$', orders.OrderEmailHistory.as_view(), + name='event.order.mail_history'), + re_path(r'^orders/(?P[0-9A-Z]+)/payments/(?P\d+)/cancel$', orders.OrderPaymentCancel.as_view(), + name='event.order.payments.cancel'), + re_path(r'^orders/(?P[0-9A-Z]+)/payments/(?P\d+)/confirm$', orders.OrderPaymentConfirm.as_view(), + name='event.order.payments.confirm'), + re_path(r'^orders/(?P[0-9A-Z]+)/refund$', orders.OrderRefundView.as_view(), + name='event.order.refunds.start'), + re_path(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/cancel$', orders.OrderRefundCancel.as_view(), + name='event.order.refunds.cancel'), + re_path(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/process$', orders.OrderRefundProcess.as_view(), + name='event.order.refunds.process'), + re_path(r'^orders/(?P[0-9A-Z]+)/refunds/(?P\d+)/done$', orders.OrderRefundDone.as_view(), + name='event.order.refunds.done'), + re_path(r'^orders/(?P[0-9A-Z]+)/cancellationrequests/(?P\d+)/delete$', + orders.OrderCancellationRequestDelete.as_view(), + name='event.order.cancellationrequests.delete'), + re_path(r'^orders/(?P[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'), + re_path(r'^invoice/(?P[^/]+)$', orders.InvoiceDownload.as_view(), + name='event.invoice.download'), + re_path(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'), + re_path(r'^orders/import/$', orderimport.ImportView.as_view(), name='event.orders.import'), + re_path(r'^orders/import/(?P[^/]+)/$', orderimport.ProcessView.as_view(), name='event.orders.import.process'), + re_path(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'), + re_path(r'^orders/export/do$', orders.ExportDoView.as_view(), name='event.orders.export.do'), + re_path(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'), + re_path(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'), + re_path(r'^orders/$', orders.OrderList.as_view(), name='event.orders'), + re_path(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'), + re_path(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'), + re_path(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'), + re_path(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'), + re_path(r'^shredder/export$', shredder.ShredExportView.as_view(), name='event.shredder.export'), + re_path(r'^shredder/download/(?P[^/]+)/$', shredder.ShredDownloadView.as_view(), name='event.shredder.download'), + re_path(r'^shredder/shred', shredder.ShredDoView.as_view(), name='event.shredder.shred'), + re_path(r'^waitinglist/$', waitinglist.WaitingListView.as_view(), name='event.orders.waitinglist'), + re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'), + re_path(r'^waitinglist/(?P\d+)/delete$', waitinglist.EntryDelete.as_view(), + name='event.orders.waitinglist.delete'), + re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'), + re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'), + re_path(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'), + re_path(r'^checkinlists/(?P\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'), + re_path(r'^checkinlists/(?P\d+)/change$', checkin.CheckinListUpdate.as_view(), + name='event.orders.checkinlists.edit'), + re_path(r'^checkinlists/(?P\d+)/delete$', checkin.CheckinListDelete.as_view(), + name='event.orders.checkinlists.delete'), ])), - url(r'^event/(?P[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'), name='event.organizerredirect'), + re_path(r'^event/(?P[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'), name='event.organizerredirect'), ] diff --git a/src/pretix/control/views/__init__.py b/src/pretix/control/views/__init__.py index 3aaf5e038..486cbe8eb 100644 --- a/src/pretix/control/views/__init__.py +++ b/src/pretix/control/views/__init__.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -import collections +import collections.abc import warnings from django.core.paginator import ( @@ -86,7 +86,7 @@ class PaginationMixin: return ctx -class LargeResultSetPage(collections.Sequence): +class LargeResultSetPage(collections.abc.Sequence): def __init__(self, object_list, number, paginator): self.object_list = object_list diff --git a/src/pretix/helpers/__init__.py b/src/pretix/helpers/__init__.py index d5ff66ba6..588116763 100644 --- a/src/pretix/helpers/__init__.py +++ b/src/pretix/helpers/__init__.py @@ -19,14 +19,4 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig - from .database import * # noqa - - -class PretixHelpersConfig(AppConfig): - name = 'pretix.helpers' - label = 'pretixhelpers' - - -default_app_config = 'pretix.helpers.PretixHelpersConfig' diff --git a/src/pretix/helpers/apps.py b/src/pretix/helpers/apps.py new file mode 100644 index 000000000..0d0d2a3d4 --- /dev/null +++ b/src/pretix/helpers/apps.py @@ -0,0 +1,27 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig + + +class PretixHelpersConfig(AppConfig): + name = 'pretix.helpers' + label = 'pretixhelpers' diff --git a/src/pretix/helpers/database.py b/src/pretix/helpers/database.py index 7d406e7dc..b5c0a43d7 100644 --- a/src/pretix/helpers/database.py +++ b/src/pretix/helpers/database.py @@ -23,7 +23,6 @@ import contextlib from django.db import transaction from django.db.models import Aggregate, Field, Lookup -from django.db.models.expressions import OrderBy class DummyRollbackException(Exception): @@ -59,28 +58,6 @@ def casual_reads(): yield -class FixedOrderBy(OrderBy): - # Workaround for https://code.djangoproject.com/ticket/28848 - template = '%(expression)s %(ordering)s' - - def as_sql(self, compiler, connection, template=None, **extra_context): - if not template: - if self.nulls_last: - template = '%s NULLS LAST' % self.template - elif self.nulls_first: - template = '%s NULLS FIRST' % self.template - connection.ops.check_expression_support(self) - expression_sql, params = compiler.compile(self.expression) - placeholders = { - 'expression': expression_sql, - 'ordering': 'DESC' if self.descending else 'ASC', - } - placeholders.update(extra_context) - template = template or self.template - params = params * template.count('%(expression)s') - return (template % placeholders).rstrip(), params - - class GroupConcat(Aggregate): function = 'group_concat' template = '%(function)s(%(field)s, "%(separator)s")' diff --git a/src/pretix/helpers/models.py b/src/pretix/helpers/models.py index 8526e6e9f..8bec3c8b7 100644 --- a/src/pretix/helpers/models.py +++ b/src/pretix/helpers/models.py @@ -21,6 +21,7 @@ # import copy +from django.core.files import File from django.db import models @@ -37,7 +38,7 @@ def modelcopy(obj: models.Model, **kwargs): n = obj.__class__(**kwargs) for f in obj._meta.fields: val = getattr(obj, f.name) - if isinstance(val, models.Model): + if isinstance(val, (models.Model, File)): setattr(n, f.name, copy.copy(val)) else: setattr(n, f.name, copy.deepcopy(val)) diff --git a/src/pretix/helpers/templatetags/jsonfield.py b/src/pretix/helpers/templatetags/jsonfield.py new file mode 100644 index 000000000..dc7d3dc8d --- /dev/null +++ b/src/pretix/helpers/templatetags/jsonfield.py @@ -0,0 +1,94 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +import copy + +from django.db import NotSupportedError +from django.db.models import Expression, JSONField + + +def postgres_compile_json_path(key_transforms): + return "{" + ','.join(key_transforms) + "}" + + +def mysql_compile_json_path(key_transforms): + path = ['$'] + for key_transform in key_transforms: + try: + num = int(key_transform) + path.append('[{}]'.format(num)) + except ValueError: # non-integer + path.append('.') + path.append(key_transform) + return ''.join(path) + + +sqlite_compile_json_path = mysql_compile_json_path + + +class JSONExtract(Expression): + def __init__(self, expression, *path, output_field=JSONField(), **extra): + super().__init__(output_field=output_field) + self.path = path + self.source_expression = self._parse_expressions(expression)[0] + self.extra = extra + + def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): + c = self.copy() + c.is_summary = summarize + c.source_expression = c.source_expression.resolve_expression(query, allow_joins, reuse, summarize, for_save) + return c + + def as_sql(self, compiler, connection, function=None, template=None, arg_joiner=None, **extra_context): + if '.postgresql' in connection.settings_dict['ENGINE']: + params = [] + arg_sql, arg_params = compiler.compile(self.source_expression) + params.extend(arg_params) + json_path = postgres_compile_json_path(self.path) + params.append(json_path) + template = '{} #> %s'.format(arg_sql) + return template, params + elif '.mysql' in connection.settings_dict['ENGINE']: + params = [] + arg_sql, arg_params = compiler.compile(self.source_expression) + params.extend(arg_params) + json_path = mysql_compile_json_path(self.path) + params.append(json_path) + template = 'JSON_EXTRACT({}, %s)'.format(arg_sql) + return template, params + elif '.sqlite' in connection.settings_dict['ENGINE']: + params = [] + arg_sql, arg_params = compiler.compile(self.source_expression) + params.extend(arg_params) + json_path = sqlite_compile_json_path(self.path) + params.append(json_path) + template = 'json_extract({}, %s)'.format(arg_sql) + return template, params + else: + raise NotSupportedError( + 'Functions on JSONFields are only supported on SQLite, PostgreSQL, and MySQL at the moment.' + ) + + def copy(self): + c = super().copy() + c.source_expression = copy.copy(self.source_expression) + c.extra = self.extra.copy() + return c diff --git a/src/pretix/multidomain/__init__.py b/src/pretix/multidomain/__init__.py index b7ec681c0..488c94f94 100644 --- a/src/pretix/multidomain/__init__.py +++ b/src/pretix/multidomain/__init__.py @@ -32,19 +32,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. -from django.apps import AppConfig from django.urls import URLPattern from django.urls.resolvers import RegexPattern -class PretixMultidomainConfig(AppConfig): - name = 'pretix.multidomain' - label = 'pretixmultidomain' - - -default_app_config = 'pretix.multidomain.PretixMultidomainConfig' - - def event_url(route, view, name=None, require_live=True): if callable(view): pattern = RegexPattern(route, name=name, is_endpoint=True) diff --git a/src/pretix/multidomain/apps.py b/src/pretix/multidomain/apps.py new file mode 100644 index 000000000..778d619be --- /dev/null +++ b/src/pretix/multidomain/apps.py @@ -0,0 +1,40 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig + + +class PretixMultidomainConfig(AppConfig): + name = 'pretix.multidomain' + label = 'pretixmultidomain' diff --git a/src/pretix/multidomain/event_domain_urlconf.py b/src/pretix/multidomain/event_domain_urlconf.py index e02b89815..2aea8ca74 100644 --- a/src/pretix/multidomain/event_domain_urlconf.py +++ b/src/pretix/multidomain/event_domain_urlconf.py @@ -22,15 +22,15 @@ import importlib.util from django.apps import apps -from django.conf.urls import include, url +from django.conf.urls import include, re_path from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import event_patterns, locale_patterns from pretix.urls import common_patterns presale_patterns = [ - url(r'', include((locale_patterns + [ - url(r'', include(event_patterns)), + re_path(r'', include((locale_patterns + [ + re_path(r'', include(event_patterns)), ], 'presale'))) ] @@ -42,11 +42,11 @@ for app in apps.get_app_configs(): if hasattr(urlmod, 'event_patterns'): patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name) raw_plugin_patterns.append( - url(r'', include((patterns, app.label))) + re_path(r'', include((patterns, app.label))) ) plugin_patterns = [ - url(r'', include((raw_plugin_patterns, 'plugins'))) + re_path(r'', include((raw_plugin_patterns, 'plugins'))) ] # The presale namespace comes last, because it contains a wildcard catch diff --git a/src/pretix/multidomain/maindomain_urlconf.py b/src/pretix/multidomain/maindomain_urlconf.py index 48271d602..61f8980f5 100644 --- a/src/pretix/multidomain/maindomain_urlconf.py +++ b/src/pretix/multidomain/maindomain_urlconf.py @@ -35,7 +35,7 @@ import importlib.util from django.apps import apps -from django.conf.urls import include, url +from django.conf.urls import include, re_path from django.views.generic import TemplateView from pretix.multidomain.plugin_handler import plugin_event_urls @@ -45,10 +45,10 @@ from pretix.presale.urls import ( from pretix.urls import common_patterns presale_patterns_main = [ - url(r'', include((locale_patterns + [ - url(r'^(?P[^/]+)/', include(organizer_patterns)), - url(r'^(?P[^/]+)/(?P[^/]+)/', include(event_patterns)), - url(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html'), name="index") + re_path(r'', include((locale_patterns + [ + re_path(r'^(?P[^/]+)/', include(organizer_patterns)), + re_path(r'^(?P[^/]+)/(?P[^/]+)/', include(event_patterns)), + re_path(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html'), name="index") ], 'presale'))) ] @@ -62,18 +62,18 @@ for app in apps.get_app_configs(): single_plugin_patterns += urlmod.urlpatterns if hasattr(urlmod, 'event_patterns'): patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name) - single_plugin_patterns.append(url(r'^(?P[^/]+)/(?P[^/]+)/', - include(patterns))) + single_plugin_patterns.append(re_path(r'^(?P[^/]+)/(?P[^/]+)/', + include(patterns))) if hasattr(urlmod, 'organizer_patterns'): patterns = urlmod.organizer_patterns - single_plugin_patterns.append(url(r'^(?P[^/]+)/', - include(patterns))) + single_plugin_patterns.append(re_path(r'^(?P[^/]+)/', + include(patterns))) raw_plugin_patterns.append( - url(r'', include((single_plugin_patterns, app.label))) + re_path(r'', include((single_plugin_patterns, app.label))) ) plugin_patterns = [ - url(r'', include((raw_plugin_patterns, 'plugins'))) + re_path(r'', include((raw_plugin_patterns, 'plugins'))) ] # The presale namespace comes last, because it contains a wildcard catch diff --git a/src/pretix/multidomain/organizer_domain_urlconf.py b/src/pretix/multidomain/organizer_domain_urlconf.py index dfa9996f3..65e870884 100644 --- a/src/pretix/multidomain/organizer_domain_urlconf.py +++ b/src/pretix/multidomain/organizer_domain_urlconf.py @@ -22,7 +22,7 @@ import importlib.util from django.apps import apps -from django.conf.urls import include, url +from django.conf.urls import include, re_path from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import ( @@ -31,9 +31,9 @@ from pretix.presale.urls import ( from pretix.urls import common_patterns presale_patterns = [ - url(r'', include((locale_patterns + [ - url(r'', include(organizer_patterns)), - url(r'^(?P[^/]+)/', include(event_patterns)), + re_path(r'', include((locale_patterns + [ + re_path(r'', include(organizer_patterns)), + re_path(r'^(?P[^/]+)/', include(event_patterns)), ], 'presale'))) ] @@ -45,16 +45,16 @@ for app in apps.get_app_configs(): if hasattr(urlmod, 'event_patterns'): patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name) raw_plugin_patterns.append( - url(r'^(?P[^/]+)/', include((patterns, app.label))) + re_path(r'^(?P[^/]+)/', include((patterns, app.label))) ) if hasattr(urlmod, 'organizer_patterns'): patterns = urlmod.organizer_patterns raw_plugin_patterns.append( - url(r'', include((patterns, app.label))) + re_path(r'', include((patterns, app.label))) ) plugin_patterns = [ - url(r'', include((raw_plugin_patterns, 'plugins'))) + re_path(r'', include((raw_plugin_patterns, 'plugins'))) ] # The presale namespace comes last, because it contains a wildcard catch diff --git a/src/pretix/plugins/badges/__init__.py b/src/pretix/plugins/badges/__init__.py index 426642f7a..9fd5bdc50 100644 --- a/src/pretix/plugins/badges/__init__.py +++ b/src/pretix/plugins/badges/__init__.py @@ -19,32 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext, gettext_lazy as _ - -from pretix import __version__ as version - - -class BadgesApp(AppConfig): - name = 'pretix.plugins.badges' - verbose_name = _("Badges") - - class PretixPluginMeta: - name = _("Badges") - author = _("the pretix team") - version = version - category = "FEATURE" - description = _("This plugin allows you to generate badges or name tags for your attendees.") - - def ready(self): - from . import signals # NOQA - - def installed(self, event): - if not event.badge_layouts.exists(): - event.badge_layouts.create( - name=gettext('Default'), - default=True, - ) - - -default_app_config = 'pretix.plugins.badges.BadgesApp' diff --git a/src/pretix/plugins/badges/apps.py b/src/pretix/plugins/badges/apps.py new file mode 100644 index 000000000..edb338d9a --- /dev/null +++ b/src/pretix/plugins/badges/apps.py @@ -0,0 +1,47 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext, gettext_lazy as _ + +from pretix import __version__ as version + + +class BadgesApp(AppConfig): + name = 'pretix.plugins.badges' + verbose_name = _("Badges") + + class PretixPluginMeta: + name = _("Badges") + author = _("the pretix team") + version = version + category = "FEATURE" + description = _("This plugin allows you to generate badges or name tags for your attendees.") + + def ready(self): + from . import signals # NOQA + + def installed(self, event): + if not event.badge_layouts.exists(): + event.badge_layouts.create( + name=gettext('Default'), + default=True, + ) diff --git a/src/pretix/plugins/badges/exporters.py b/src/pretix/plugins/badges/exporters.py index c061a6920..6effab993 100644 --- a/src/pretix/plugins/badges/exporters.py +++ b/src/pretix/plugins/badges/exporters.py @@ -41,7 +41,6 @@ from typing import Tuple import dateutil.parser from django import forms -from django.conf import settings from django.contrib.staticfiles import finders from django.core.files import File from django.core.files.storage import default_storage @@ -49,7 +48,6 @@ from django.db.models import Exists, OuterRef, Q from django.db.models.functions import Coalesce from django.utils.timezone import make_aware from django.utils.translation import gettext as _, gettext_lazy -from jsonfallback.functions import JSONExtract from reportlab.lib import pagesizes from reportlab.lib.units import mm from reportlab.pdfgen import canvas @@ -60,6 +58,7 @@ from pretix.base.models import Order, OrderPosition from pretix.base.pdf import Renderer from pretix.base.services.orders import OrderError from pretix.base.settings import PERSON_NAME_SCHEMES +from pretix.helpers.templatetags.jsonfield import JSONExtract from pretix.plugins.badges.models import BadgeItem, BadgeLayout @@ -297,7 +296,7 @@ class BadgeExporter(BaseExporter): ] + ([ ('name:{}'.format(k), _('Attendee name: {part}').format(part=label)) for k, label, w in name_scheme['fields'] - ] if settings.JSON_FIELD_AVAILABLE and len(name_scheme['fields']) > 1 else []), + ] if len(name_scheme['fields']) > 1 else []), )), ] ) diff --git a/src/pretix/plugins/badges/urls.py b/src/pretix/plugins/badges/urls.py index 5b8f6c424..13523a786 100644 --- a/src/pretix/plugins/badges/urls.py +++ b/src/pretix/plugins/badges/urls.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from pretix.api.urls import event_router from pretix.plugins.badges.api import BadgeItemViewSet, BadgeLayoutViewSet @@ -30,18 +30,18 @@ from .views import ( ) urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/$', - LayoutListView.as_view(), name='index'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/print$', - OrderPrintDo.as_view(), name='print'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/add$', - LayoutCreate.as_view(), name='add'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/default$', - LayoutSetDefault.as_view(), name='default'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/delete$', - LayoutDelete.as_view(), name='delete'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/editor', - LayoutEditorView.as_view(), name='edit'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/$', + LayoutListView.as_view(), name='index'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/print$', + OrderPrintDo.as_view(), name='print'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/add$', + LayoutCreate.as_view(), name='add'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/default$', + LayoutSetDefault.as_view(), name='default'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/delete$', + LayoutDelete.as_view(), name='delete'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/badges/(?P\d+)/editor', + LayoutEditorView.as_view(), name='edit'), ] event_router.register('badgelayouts', BadgeLayoutViewSet) event_router.register('badgeitems', BadgeItemViewSet) diff --git a/src/pretix/plugins/banktransfer/__init__.py b/src/pretix/plugins/banktransfer/__init__.py index 335fba156..9fd5bdc50 100644 --- a/src/pretix/plugins/banktransfer/__init__.py +++ b/src/pretix/plugins/banktransfer/__init__.py @@ -19,38 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class BankTransferApp(AppConfig): - name = 'pretix.plugins.banktransfer' - verbose_name = _("Bank transfer") - - class PretixPluginMeta: - name = _("Bank transfer") - author = _("the pretix team") - category = 'PAYMENT' - version = version - description = _("This plugin allows you to receive payments " + - "via bank transfer.") - - def ready(self): - from . import signals # NOQA - from . import tasks # NOQA - from .templatetags import commadecimal, dotdecimal # NOQA - - @cached_property - def compatibility_warnings(self): - errs = [] - try: - import chardet # NOQA - except ImportError: - errs.append(_("Install the python package 'chardet' for better CSV import capabilities.")) - return errs - - -default_app_config = 'pretix.plugins.banktransfer.BankTransferApp' diff --git a/src/pretix/plugins/banktransfer/apps.py b/src/pretix/plugins/banktransfer/apps.py new file mode 100644 index 000000000..1fd56394a --- /dev/null +++ b/src/pretix/plugins/banktransfer/apps.py @@ -0,0 +1,53 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class BankTransferApp(AppConfig): + name = 'pretix.plugins.banktransfer' + verbose_name = _("Bank transfer") + + class PretixPluginMeta: + name = _("Bank transfer") + author = _("the pretix team") + category = 'PAYMENT' + version = version + description = _("This plugin allows you to receive payments " + + "via bank transfer.") + + def ready(self): + from . import signals # NOQA + from . import tasks # NOQA + from .templatetags import commadecimal, dotdecimal # NOQA + + @cached_property + def compatibility_warnings(self): + errs = [] + try: + import chardet # NOQA + except ImportError: + errs.append(_("Install the python package 'chardet' for better CSV import capabilities.")) + return errs diff --git a/src/pretix/plugins/banktransfer/urls.py b/src/pretix/plugins/banktransfer/urls.py index 8555aa58b..79442e775 100644 --- a/src/pretix/plugins/banktransfer/urls.py +++ b/src/pretix/plugins/banktransfer/urls.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from pretix.api.urls import orga_router from pretix.plugins.banktransfer.api import BankImportJobViewSet @@ -27,38 +27,38 @@ from pretix.plugins.banktransfer.api import BankImportJobViewSet from . import views urlpatterns = [ - url(r'^control/organizer/(?P[^/]+)/banktransfer/import/', - views.OrganizerImportView.as_view(), - name='import'), - url(r'^control/organizer/(?P[^/]+)/banktransfer/job/(?P\d+)/', - views.OrganizerJobDetailView.as_view(), name='import.job'), - url(r'^control/organizer/(?P[^/]+)/banktransfer/action/', - views.OrganizerActionView.as_view(), name='import.action'), - url(r'^control/organizer/(?P[^/]+)/banktransfer/refunds/', - views.OrganizerRefundExportListView.as_view(), name='refunds.list'), - url(r'^control/organizer/(?P[^/]+)/banktransfer/export/(?P\d+)/$', - views.OrganizerDownloadRefundExportView.as_view(), - name='refunds.download'), - url(r'^control/organizer/(?P[^/]+)/banktransfer/sepa-export/(?P\d+)/$', - views.OrganizerSepaXMLExportView.as_view(), - name='refunds.sepa'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/import/', + views.OrganizerImportView.as_view(), + name='import'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/job/(?P\d+)/', + views.OrganizerJobDetailView.as_view(), name='import.job'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/action/', + views.OrganizerActionView.as_view(), name='import.action'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/refunds/', + views.OrganizerRefundExportListView.as_view(), name='refunds.list'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/export/(?P\d+)/$', + views.OrganizerDownloadRefundExportView.as_view(), + name='refunds.download'), + re_path(r'^control/organizer/(?P[^/]+)/banktransfer/sepa-export/(?P\d+)/$', + views.OrganizerSepaXMLExportView.as_view(), + name='refunds.sepa'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/import/', - views.EventImportView.as_view(), - name='import'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/job/(?P\d+)/', - views.EventJobDetailView.as_view(), name='import.job'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/action/', - views.EventActionView.as_view(), name='import.action'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/refunds/', - views.EventRefundExportListView.as_view(), - name='refunds.list'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/export/(?P\d+)/$', - views.EventDownloadRefundExportView.as_view(), - name='refunds.download'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/sepa-export/(?P\d+)/$', - views.EventSepaXMLExportView.as_view(), - name='refunds.sepa'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/import/', + views.EventImportView.as_view(), + name='import'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/job/(?P\d+)/', + views.EventJobDetailView.as_view(), name='import.job'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/action/', + views.EventActionView.as_view(), name='import.action'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/refunds/', + views.EventRefundExportListView.as_view(), + name='refunds.list'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/export/(?P\d+)/$', + views.EventDownloadRefundExportView.as_view(), + name='refunds.download'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/sepa-export/(?P\d+)/$', + views.EventSepaXMLExportView.as_view(), + name='refunds.sepa'), ] orga_router.register('bankimportjobs', BankImportJobViewSet) diff --git a/src/pretix/plugins/checkinlists/__init__.py b/src/pretix/plugins/checkinlists/__init__.py index 9974d4000..c4b4deab3 100644 --- a/src/pretix/plugins/checkinlists/__init__.py +++ b/src/pretix/plugins/checkinlists/__init__.py @@ -31,36 +31,3 @@ # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class CheckinlistsApp(AppConfig): - name = 'pretix.plugins.checkinlists' - verbose_name = _("Check-in lists") - - class PretixPluginMeta: - name = _("Check-in list exporter") - author = _("the pretix team") - version = version - visible = False - description = _("This plugin allows you to generate check-in lists for your conference.") - - def ready(self): - from . import signals # NOQA - - @cached_property - def compatibility_errors(self): - errs = [] - try: - import reportlab # NOQA - except ImportError: - errs.append("Python package 'reportlab' is not installed.") - return errs - - -default_app_config = 'pretix.plugins.checkinlists.CheckinlistsApp' diff --git a/src/pretix/plugins/checkinlists/apps.py b/src/pretix/plugins/checkinlists/apps.py new file mode 100644 index 000000000..72f7c38f1 --- /dev/null +++ b/src/pretix/plugins/checkinlists/apps.py @@ -0,0 +1,63 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class CheckinlistsApp(AppConfig): + name = 'pretix.plugins.checkinlists' + verbose_name = _("Check-in lists") + + class PretixPluginMeta: + name = _("Check-in list exporter") + author = _("the pretix team") + version = version + visible = False + description = _("This plugin allows you to generate check-in lists for your conference.") + + def ready(self): + from . import signals # NOQA + + @cached_property + def compatibility_errors(self): + errs = [] + try: + import reportlab # NOQA + except ImportError: + errs.append("Python package 'reportlab' is not installed.") + return errs diff --git a/src/pretix/plugins/checkinlists/exporters.py b/src/pretix/plugins/checkinlists/exporters.py index 7967320b3..d82236b34 100644 --- a/src/pretix/plugins/checkinlists/exporters.py +++ b/src/pretix/plugins/checkinlists/exporters.py @@ -37,7 +37,6 @@ from datetime import datetime, time, timedelta import dateutil.parser from django import forms -from django.conf import settings from django.db.models import ( Case, Exists, Max, OuterRef, Q, Subquery, Value, When, ) @@ -46,7 +45,6 @@ from django.urls import reverse from django.utils.formats import date_format from django.utils.timezone import is_aware, make_aware from django.utils.translation import gettext as _, gettext_lazy, pgettext -from jsonfallback.functions import JSONExtract from pytz import UTC from reportlab.lib.units import mm from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle @@ -58,6 +56,7 @@ from pretix.base.models import ( from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.templatetags.money import money_filter from pretix.control.forms.widgets import Select2 +from pretix.helpers.templatetags.jsonfield import JSONExtract from pretix.plugins.reports.exporters import ReportlabExportMixin @@ -110,7 +109,7 @@ class CheckInListMixin(BaseExporter): ] + ([ ('name:{}'.format(k), _('Attendee name: {part}').format(part=label)) for k, label, w in name_scheme['fields'] - ] if settings.JSON_FIELD_AVAILABLE and len(name_scheme['fields']) > 1 else []), + ] if len(name_scheme['fields']) > 1 else []), widget=forms.RadioSelect, required=False )), diff --git a/src/pretix/plugins/manualpayment/__init__.py b/src/pretix/plugins/manualpayment/__init__.py index 48c0fcb0e..9fd5bdc50 100644 --- a/src/pretix/plugins/manualpayment/__init__.py +++ b/src/pretix/plugins/manualpayment/__init__.py @@ -19,22 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class ManualPaymentApp(AppConfig): - name = 'pretix.plugins.manualpayment' - verbose_name = _("Manual payment") - - class PretixPluginMeta: - name = _("Manual payment") - author = _("the pretix team") - version = version - category = 'PAYMENT' - description = _("This plugin adds a customizable payment method for manual processing.") - - -default_app_config = 'pretix.plugins.manualpayment.ManualPaymentApp' diff --git a/src/pretix/plugins/manualpayment/apps.py b/src/pretix/plugins/manualpayment/apps.py new file mode 100644 index 000000000..123a3fbb8 --- /dev/null +++ b/src/pretix/plugins/manualpayment/apps.py @@ -0,0 +1,37 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class ManualPaymentApp(AppConfig): + name = 'pretix.plugins.manualpayment' + verbose_name = _("Manual payment") + + class PretixPluginMeta: + name = _("Manual payment") + author = _("the pretix team") + version = version + category = 'PAYMENT' + description = _("This plugin adds a customizable payment method for manual processing.") diff --git a/src/pretix/plugins/paypal/__init__.py b/src/pretix/plugins/paypal/__init__.py index 1eb01983f..c4b4deab3 100644 --- a/src/pretix/plugins/paypal/__init__.py +++ b/src/pretix/plugins/paypal/__init__.py @@ -31,36 +31,3 @@ # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class PaypalApp(AppConfig): - name = 'pretix.plugins.paypal' - verbose_name = _("PayPal") - - class PretixPluginMeta: - name = _("PayPal") - author = _("the pretix team") - version = version - category = 'PAYMENT' - description = _("This plugin allows you to receive payments via PayPal") - - def ready(self): - from . import signals # NOQA - - @cached_property - def compatibility_errors(self): - errs = [] - try: - import paypalrestsdk # NOQA - except ImportError: - errs.append("Python package 'paypalrestsdk' is not installed.") - return errs - - -default_app_config = 'pretix.plugins.paypal.PaypalApp' diff --git a/src/pretix/plugins/paypal/apps.py b/src/pretix/plugins/paypal/apps.py new file mode 100644 index 000000000..eae1fd701 --- /dev/null +++ b/src/pretix/plugins/paypal/apps.py @@ -0,0 +1,63 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class PaypalApp(AppConfig): + name = 'pretix.plugins.paypal' + verbose_name = _("PayPal") + + class PretixPluginMeta: + name = _("PayPal") + author = _("the pretix team") + version = version + category = 'PAYMENT' + description = _("This plugin allows you to receive payments via PayPal") + + def ready(self): + from . import signals # NOQA + + @cached_property + def compatibility_errors(self): + errs = [] + try: + import paypalrestsdk # NOQA + except ImportError: + errs.append("Python package 'paypalrestsdk' is not installed.") + return errs diff --git a/src/pretix/plugins/paypal/urls.py b/src/pretix/plugins/paypal/urls.py index bb8f96e8c..361cec20e 100644 --- a/src/pretix/plugins/paypal/urls.py +++ b/src/pretix/plugins/paypal/urls.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import include, url +from django.conf.urls import include, re_path from pretix.multidomain import event_url @@ -28,22 +28,21 @@ from .views import ( ) event_patterns = [ - url(r'^paypal/', include([ - url(r'^abort/$', abort, name='abort'), - url(r'^return/$', success, name='return'), - url(r'^redirect/$', redirect_view, name='redirect'), + re_path(r'^paypal/', include([ + re_path(r'^abort/$', abort, name='abort'), + re_path(r'^return/$', success, name='return'), + re_path(r'^redirect/$', redirect_view, name='redirect'), - url(r'w/(?P[a-zA-Z0-9]{16})/abort/', abort, name='abort'), - url(r'w/(?P[a-zA-Z0-9]{16})/return/', success, name='return'), + re_path(r'w/(?P[a-zA-Z0-9]{16})/abort/', abort, name='abort'), + re_path(r'w/(?P[a-zA-Z0-9]{16})/return/', success, name='return'), event_url(r'^webhook/$', webhook, name='webhook', require_live=False), ])), ] - urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/paypal/disconnect/', - oauth_disconnect, name='oauth.disconnect'), - url(r'^_paypal/webhook/$', webhook, name='webhook'), - url(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/paypal/disconnect/', + oauth_disconnect, name='oauth.disconnect'), + re_path(r'^_paypal/webhook/$', webhook, name='webhook'), + re_path(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'), ] diff --git a/src/pretix/plugins/pretixdroid/__init__.py b/src/pretix/plugins/pretixdroid/__init__.py index 888b06c2c..9fd5bdc50 100644 --- a/src/pretix/plugins/pretixdroid/__init__.py +++ b/src/pretix/plugins/pretixdroid/__init__.py @@ -19,24 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class PretixdroidApp(AppConfig): - name = 'pretix.plugins.pretixdroid' - verbose_name = _("Old check-in device API") - - class PretixPluginMeta: - name = _("Old check-in device API") - author = _("the pretix team") - version = version - visible = False - restricted = True - category = 'INTEGRATION' - description = _("This plugin allows you to use the pretixdroid and pretixdesk apps for your event.") - - -default_app_config = 'pretix.plugins.pretixdroid.PretixdroidApp' diff --git a/src/pretix/plugins/pretixdroid/apps.py b/src/pretix/plugins/pretixdroid/apps.py new file mode 100644 index 000000000..d97868d47 --- /dev/null +++ b/src/pretix/plugins/pretixdroid/apps.py @@ -0,0 +1,39 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class PretixdroidApp(AppConfig): + name = 'pretix.plugins.pretixdroid' + verbose_name = _("Old check-in device API") + + class PretixPluginMeta: + name = _("Old check-in device API") + author = _("the pretix team") + version = version + visible = False + restricted = True + category = 'INTEGRATION' + description = _("This plugin allows you to use the pretixdroid and pretixdesk apps for your event.") diff --git a/src/pretix/plugins/reports/__init__.py b/src/pretix/plugins/reports/__init__.py index 5d09d6b20..c4b4deab3 100644 --- a/src/pretix/plugins/reports/__init__.py +++ b/src/pretix/plugins/reports/__init__.py @@ -31,36 +31,3 @@ # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class ReportsApp(AppConfig): - name = 'pretix.plugins.reports' - verbose_name = _("Report exporter") - - class PretixPluginMeta: - name = _("Report exporter") - author = _("the pretix team") - version = version - category = 'FORMAT' - description = _("This plugin allows you to generate printable reports about your sales.") - - def ready(self): - from . import signals # NOQA - - @cached_property - def compatibility_errors(self): - errs = [] - try: - import reportlab # NOQA - except ImportError: - errs.append("Python package 'reportlab' is not installed.") - return errs - - -default_app_config = 'pretix.plugins.reports.ReportsApp' diff --git a/src/pretix/plugins/reports/apps.py b/src/pretix/plugins/reports/apps.py new file mode 100644 index 000000000..db776affd --- /dev/null +++ b/src/pretix/plugins/reports/apps.py @@ -0,0 +1,63 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class ReportsApp(AppConfig): + name = 'pretix.plugins.reports' + verbose_name = _("Report exporter") + + class PretixPluginMeta: + name = _("Report exporter") + author = _("the pretix team") + version = version + category = 'FORMAT' + description = _("This plugin allows you to generate printable reports about your sales.") + + def ready(self): + from . import signals # NOQA + + @cached_property + def compatibility_errors(self): + errs = [] + try: + import reportlab # NOQA + except ImportError: + errs.append("Python package 'reportlab' is not installed.") + return errs diff --git a/src/pretix/plugins/returnurl/__init__.py b/src/pretix/plugins/returnurl/__init__.py index 23b771428..9fd5bdc50 100644 --- a/src/pretix/plugins/returnurl/__init__.py +++ b/src/pretix/plugins/returnurl/__init__.py @@ -19,26 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class ReturnURLApp(AppConfig): - name = 'pretix.plugins.returnurl' - verbose_name = _("Redirection from order page") - - class PretixPluginMeta: - name = _("Redirection from order page") - author = _("the pretix team") - version = version - category = 'API' - description = _("This plugin allows to link to payments and redirect back afterwards. This is useful in " - "combination with our API.") - - def ready(self): - from . import signals # NOQA - - -default_app_config = 'pretix.plugins.returnurl.ReturnURLApp' diff --git a/src/pretix/plugins/returnurl/apps.py b/src/pretix/plugins/returnurl/apps.py new file mode 100644 index 000000000..fed4a5e9e --- /dev/null +++ b/src/pretix/plugins/returnurl/apps.py @@ -0,0 +1,41 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class ReturnURLApp(AppConfig): + name = 'pretix.plugins.returnurl' + verbose_name = _("Redirection from order page") + + class PretixPluginMeta: + name = _("Redirection from order page") + author = _("the pretix team") + version = version + category = 'API' + description = _("This plugin allows to link to payments and redirect back afterwards. This is useful in " + "combination with our API.") + + def ready(self): + from . import signals # NOQA diff --git a/src/pretix/plugins/returnurl/urls.py b/src/pretix/plugins/returnurl/urls.py index 88167a528..51b1a51d7 100644 --- a/src/pretix/plugins/returnurl/urls.py +++ b/src/pretix/plugins/returnurl/urls.py @@ -19,11 +19,11 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from .views import ReturnSettings urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/returnurl/settings$', - ReturnSettings.as_view(), name='settings'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/returnurl/settings$', + ReturnSettings.as_view(), name='settings'), ] diff --git a/src/pretix/plugins/sendmail/__init__.py b/src/pretix/plugins/sendmail/__init__.py index 7ef05b2d7..9fd5bdc50 100644 --- a/src/pretix/plugins/sendmail/__init__.py +++ b/src/pretix/plugins/sendmail/__init__.py @@ -19,27 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class SendMailApp(AppConfig): - name = 'pretix.plugins.sendmail' - verbose_name = _("Send out emails") - - class PretixPluginMeta: - name = _("Send out emails") - author = _("the pretix team") - category = 'FEATURE' - version = version - description = _("This plugin allows you to send out emails " + - "to all your customers.") - - def ready(self): - from . import signals # NOQA - from . import tasks # NOQA - - -default_app_config = 'pretix.plugins.sendmail.SendMailApp' diff --git a/src/pretix/plugins/sendmail/apps.py b/src/pretix/plugins/sendmail/apps.py new file mode 100644 index 000000000..723cf2bd4 --- /dev/null +++ b/src/pretix/plugins/sendmail/apps.py @@ -0,0 +1,42 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class SendMailApp(AppConfig): + name = 'pretix.plugins.sendmail' + verbose_name = _("Send out emails") + + class PretixPluginMeta: + name = _("Send out emails") + author = _("the pretix team") + category = 'FEATURE' + version = version + description = _("This plugin allows you to send out emails " + + "to all your customers.") + + def ready(self): + from . import signals # NOQA + from . import tasks # NOQA diff --git a/src/pretix/plugins/sendmail/urls.py b/src/pretix/plugins/sendmail/urls.py index 60db5213a..3e1412559 100644 --- a/src/pretix/plugins/sendmail/urls.py +++ b/src/pretix/plugins/sendmail/urls.py @@ -32,12 +32,12 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. -from django.conf.urls import url +from django.conf.urls import re_path from . import views urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/sendmail/$', views.SenderView.as_view(), - name='send'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/sendmail/history/', views.EmailHistoryView.as_view(), name='history') + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/sendmail/$', views.SenderView.as_view(), + name='send'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/sendmail/history/', views.EmailHistoryView.as_view(), name='history') ] diff --git a/src/pretix/plugins/statistics/__init__.py b/src/pretix/plugins/statistics/__init__.py index f9d444b2d..9fd5bdc50 100644 --- a/src/pretix/plugins/statistics/__init__.py +++ b/src/pretix/plugins/statistics/__init__.py @@ -19,25 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class StatisticsApp(AppConfig): - name = 'pretix.plugins.statistics' - verbose_name = _("Statistics") - - class PretixPluginMeta: - name = _("Statistics") - author = _("the pretix team") - version = version - category = 'FEATURE' - description = _("This plugin shows you various statistics.") - - def ready(self): - from . import signals # NOQA - - -default_app_config = 'pretix.plugins.statistics.StatisticsApp' diff --git a/src/pretix/plugins/statistics/apps.py b/src/pretix/plugins/statistics/apps.py new file mode 100644 index 000000000..825eaa1e9 --- /dev/null +++ b/src/pretix/plugins/statistics/apps.py @@ -0,0 +1,40 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class StatisticsApp(AppConfig): + name = 'pretix.plugins.statistics' + verbose_name = _("Statistics") + + class PretixPluginMeta: + name = _("Statistics") + author = _("the pretix team") + version = version + category = 'FEATURE' + description = _("This plugin shows you various statistics.") + + def ready(self): + from . import signals # NOQA diff --git a/src/pretix/plugins/statistics/urls.py b/src/pretix/plugins/statistics/urls.py index cb36609b9..8231d59fe 100644 --- a/src/pretix/plugins/statistics/urls.py +++ b/src/pretix/plugins/statistics/urls.py @@ -19,11 +19,11 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from . import views urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/statistics/', views.IndexView.as_view(), - name='index'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/statistics/', views.IndexView.as_view(), + name='index'), ] diff --git a/src/pretix/plugins/stripe/__init__.py b/src/pretix/plugins/stripe/__init__.py index b8d0fe62f..9fd5bdc50 100644 --- a/src/pretix/plugins/stripe/__init__.py +++ b/src/pretix/plugins/stripe/__init__.py @@ -19,36 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class StripeApp(AppConfig): - name = 'pretix.plugins.stripe' - verbose_name = _("Stripe") - - class PretixPluginMeta: - name = _("Stripe") - author = _("the pretix team") - version = version - category = 'PAYMENT' - description = _("This plugin allows you to receive credit card payments " + - "via Stripe") - - def ready(self): - from . import signals, tasks # NOQA - - @cached_property - def compatibility_errors(self): - errs = [] - try: - import stripe # NOQA - except ImportError: - errs.append("Python package 'stripe' is not installed.") - return errs - - -default_app_config = 'pretix.plugins.stripe.StripeApp' diff --git a/src/pretix/plugins/stripe/apps.py b/src/pretix/plugins/stripe/apps.py new file mode 100644 index 000000000..5b4f3edcc --- /dev/null +++ b/src/pretix/plugins/stripe/apps.py @@ -0,0 +1,51 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class StripeApp(AppConfig): + name = 'pretix.plugins.stripe' + verbose_name = _("Stripe") + + class PretixPluginMeta: + name = _("Stripe") + author = _("the pretix team") + version = version + category = 'PAYMENT' + description = _("This plugin allows you to receive credit card payments " + + "via Stripe") + + def ready(self): + from . import signals, tasks # NOQA + + @cached_property + def compatibility_errors(self): + errs = [] + try: + import stripe # NOQA + except ImportError: + errs.append("Python package 'stripe' is not installed.") + return errs diff --git a/src/pretix/plugins/stripe/urls.py b/src/pretix/plugins/stripe/urls.py index 988ea22dd..011a3cf98 100644 --- a/src/pretix/plugins/stripe/urls.py +++ b/src/pretix/plugins/stripe/urls.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import include, url +from django.conf.urls import include, re_path from pretix.multidomain import event_url @@ -30,30 +30,30 @@ from .views import ( ) event_patterns = [ - url(r'^stripe/', include([ + re_path(r'^stripe/', include([ event_url(r'^webhook/$', webhook, name='webhook', require_live=False), - url(r'^redirect/$', redirect_view, name='redirect'), - url(r'^return/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/$', ReturnView.as_view(), name='return'), - url(r'^sca/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/$', ScaView.as_view(), name='sca'), - url(r'^sca/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/return/$', - ScaReturnView.as_view(), name='sca.return'), + re_path(r'^redirect/$', redirect_view, name='redirect'), + re_path(r'^return/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/$', ReturnView.as_view(), name='return'), + re_path(r'^sca/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/$', ScaView.as_view(), name='sca'), + re_path(r'^sca/(?P[^/]+)/(?P[^/]+)/(?P[0-9]+)/return/$', + ScaReturnView.as_view(), name='sca.return'), ])), - url(r'^.well-known/apple-developer-merchantid-domain-association$', - applepay_association, name='applepay.association'), + re_path(r'^.well-known/apple-developer-merchantid-domain-association$', + applepay_association, name='applepay.association'), ] organizer_patterns = [ - url(r'^.well-known/apple-developer-merchantid-domain-association$', - applepay_association, name='applepay.association'), + re_path(r'^.well-known/apple-developer-merchantid-domain-association$', + applepay_association, name='applepay.association'), ] urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/stripe/disconnect/', - oauth_disconnect, name='oauth.disconnect'), - url(r'^control/organizer/(?P[^/]+)/stripeconnect/', - OrganizerSettingsFormView.as_view(), name='settings.connect'), - url(r'^_stripe/webhook/$', webhook, name='webhook'), - url(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'), - url(r'^.well-known/apple-developer-merchantid-domain-association$', - applepay_association, name='applepay.association'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/stripe/disconnect/', + oauth_disconnect, name='oauth.disconnect'), + re_path(r'^control/organizer/(?P[^/]+)/stripeconnect/', + OrganizerSettingsFormView.as_view(), name='settings.connect'), + re_path(r'^_stripe/webhook/$', webhook, name='webhook'), + re_path(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'), + re_path(r'^.well-known/apple-developer-merchantid-domain-association$', + applepay_association, name='applepay.association'), ] diff --git a/src/pretix/plugins/ticketoutputpdf/__init__.py b/src/pretix/plugins/ticketoutputpdf/__init__.py index d130946d1..c4b4deab3 100644 --- a/src/pretix/plugins/ticketoutputpdf/__init__.py +++ b/src/pretix/plugins/ticketoutputpdf/__init__.py @@ -31,36 +31,3 @@ # Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. - -from django.apps import AppConfig -from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class TicketOutputPdfApp(AppConfig): - name = 'pretix.plugins.ticketoutputpdf' - verbose_name = _("PDF ticket output") - - class PretixPluginMeta: - name = _("PDF ticket output") - author = _("the pretix team") - version = version - category = 'FORMAT' - description = _("This plugin allows you to print out tickets as PDF files") - - def ready(self): - from . import signals # NOQA - - @cached_property - def compatibility_errors(self): - errs = [] - try: - import reportlab # NOQA - except ImportError: - errs.append("Python package 'reportlab' is not installed.") - return errs - - -default_app_config = 'pretix.plugins.ticketoutputpdf.TicketOutputPdfApp' diff --git a/src/pretix/plugins/ticketoutputpdf/apps.py b/src/pretix/plugins/ticketoutputpdf/apps.py new file mode 100644 index 000000000..2b2282f61 --- /dev/null +++ b/src/pretix/plugins/ticketoutputpdf/apps.py @@ -0,0 +1,63 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# + +# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of +# the Apache License 2.0 can be obtained at . +# +# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A +# full history of changes and contributors is available at . +# +# This file contains Apache-licensed contributions copyrighted by: Tobias Kunze +# +# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under the License. + +from django.apps import AppConfig +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class TicketOutputPdfApp(AppConfig): + name = 'pretix.plugins.ticketoutputpdf' + verbose_name = _("PDF ticket output") + + class PretixPluginMeta: + name = _("PDF ticket output") + author = _("the pretix team") + version = version + category = 'FORMAT' + description = _("This plugin allows you to print out tickets as PDF files") + + def ready(self): + from . import signals # NOQA + + @cached_property + def compatibility_errors(self): + errs = [] + try: + import reportlab # NOQA + except ImportError: + errs.append("Python package 'reportlab' is not installed.") + return errs diff --git a/src/pretix/plugins/ticketoutputpdf/exporters.py b/src/pretix/plugins/ticketoutputpdf/exporters.py index 44377bada..f686f6300 100644 --- a/src/pretix/plugins/ticketoutputpdf/exporters.py +++ b/src/pretix/plugins/ticketoutputpdf/exporters.py @@ -38,13 +38,11 @@ from io import BytesIO import dateutil.parser from django import forms -from django.conf import settings from django.core.files.base import ContentFile from django.db.models import Q from django.db.models.functions import Coalesce from django.utils.timezone import make_aware from django.utils.translation import gettext as _, gettext_lazy -from jsonfallback.functions import JSONExtract from PyPDF2.merger import PdfFileMerger from pretix.base.exporter import BaseExporter @@ -52,6 +50,7 @@ from pretix.base.i18n import language from pretix.base.models import Event, Order, OrderPosition from pretix.base.settings import PERSON_NAME_SCHEMES +from ...helpers.templatetags.jsonfield import JSONExtract from .ticketoutput import PdfTicketOutput @@ -94,7 +93,7 @@ class AllTicketsPDF(BaseExporter): ] + ([ ('name:{}'.format(k), _('Attendee name: {part}').format(part=label)) for k, label, w in name_scheme['fields'] - ] if settings.JSON_FIELD_AVAILABLE and name_scheme and len(name_scheme['fields']) > 1 else []), + ] if name_scheme and len(name_scheme['fields']) > 1 else []), )), ] ) diff --git a/src/pretix/plugins/ticketoutputpdf/signals.py b/src/pretix/plugins/ticketoutputpdf/signals.py index b507659e3..8adfa704e 100644 --- a/src/pretix/plugins/ticketoutputpdf/signals.py +++ b/src/pretix/plugins/ticketoutputpdf/signals.py @@ -152,10 +152,10 @@ def pdf_logentry_object_link(sender, logentry, **kwargs): return a_text.format_map(a_map) -override_layout = EventPluginSignal( - providing_args=["layout", "orderposition"] -) +override_layout = EventPluginSignal() """ +Arguments: ``layout``, ``orderposition`` + This signal allows you to forcefully override the ticket layout that is being used to create the ticket PDF. Use with care, as this will render any specifically by the organizer selected templates useless. diff --git a/src/pretix/plugins/ticketoutputpdf/urls.py b/src/pretix/plugins/ticketoutputpdf/urls.py index 1338e5371..863772b46 100644 --- a/src/pretix/plugins/ticketoutputpdf/urls.py +++ b/src/pretix/plugins/ticketoutputpdf/urls.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from pretix.api.urls import event_router from pretix.plugins.ticketoutputpdf.api import ( @@ -31,18 +31,18 @@ from pretix.plugins.ticketoutputpdf.views import ( ) urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/$', - LayoutListView.as_view(), name='index'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/add$', - LayoutCreate.as_view(), name='add'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/default$', - LayoutSetDefault.as_view(), name='default'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/default$', - LayoutGetDefault.as_view(), name='getdefault'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/delete$', - LayoutDelete.as_view(), name='delete'), - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/editor', - LayoutEditorView.as_view(), name='edit'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/$', + LayoutListView.as_view(), name='index'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/add$', + LayoutCreate.as_view(), name='add'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/default$', + LayoutSetDefault.as_view(), name='default'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/default$', + LayoutGetDefault.as_view(), name='getdefault'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/delete$', + LayoutDelete.as_view(), name='delete'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/pdfoutput/(?P\d+)/editor', + LayoutEditorView.as_view(), name='edit'), ] event_router.register('ticketlayouts', TicketLayoutViewSet) event_router.register('ticketlayoutitems', TicketLayoutItemViewSet) diff --git a/src/pretix/plugins/webcheckin/__init__.py b/src/pretix/plugins/webcheckin/__init__.py index 82a14f13f..9fd5bdc50 100644 --- a/src/pretix/plugins/webcheckin/__init__.py +++ b/src/pretix/plugins/webcheckin/__init__.py @@ -19,25 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - -from pretix import __version__ as version - - -class WebCheckinApp(AppConfig): - name = 'pretix.plugins.webcheckin' - verbose_name = _("Web-based check-in") - - class PretixPluginMeta: - name = _("Web-based check-in") - author = _("the pretix team") - version = version - category = "FEATURE" - description = _("This plugin allows you to perform check-in actions in your browser.") - - def ready(self): - from . import signals # NOQA - - -default_app_config = 'pretix.plugins.webcheckin.WebCheckinApp' diff --git a/src/pretix/plugins/webcheckin/apps.py b/src/pretix/plugins/webcheckin/apps.py new file mode 100644 index 000000000..5c83a4ef8 --- /dev/null +++ b/src/pretix/plugins/webcheckin/apps.py @@ -0,0 +1,40 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class WebCheckinApp(AppConfig): + name = 'pretix.plugins.webcheckin' + verbose_name = _("Web-based check-in") + + class PretixPluginMeta: + name = _("Web-based check-in") + author = _("the pretix team") + version = version + category = "FEATURE" + description = _("This plugin allows you to perform check-in actions in your browser.") + + def ready(self): + from . import signals # NOQA diff --git a/src/pretix/plugins/webcheckin/urls.py b/src/pretix/plugins/webcheckin/urls.py index 68dac9b12..5fa2b7875 100644 --- a/src/pretix/plugins/webcheckin/urls.py +++ b/src/pretix/plugins/webcheckin/urls.py @@ -19,11 +19,11 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.conf.urls import url +from django.conf.urls import re_path from .views import IndexView urlpatterns = [ - url(r'^control/event/(?P[^/]+)/(?P[^/]+)/webcheckin/$', - IndexView.as_view(), name='index'), + re_path(r'^control/event/(?P[^/]+)/(?P[^/]+)/webcheckin/$', + IndexView.as_view(), name='index'), ] diff --git a/src/pretix/presale/__init__.py b/src/pretix/presale/__init__.py index ae60d325e..9fd5bdc50 100644 --- a/src/pretix/presale/__init__.py +++ b/src/pretix/presale/__init__.py @@ -19,15 +19,3 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # -from django.apps import AppConfig - - -class PretixPresaleConfig(AppConfig): - name = 'pretix.presale' - label = 'pretixpresale' - - def ready(self): - from . import style # noqa - - -default_app_config = 'pretix.presale.PretixPresaleConfig' diff --git a/src/pretix/presale/apps.py b/src/pretix/presale/apps.py new file mode 100644 index 000000000..144152c50 --- /dev/null +++ b/src/pretix/presale/apps.py @@ -0,0 +1,30 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig + + +class PretixPresaleConfig(AppConfig): + name = 'pretix.presale' + label = 'pretixpresale' + + def ready(self): + from . import style # noqa diff --git a/src/pretix/presale/signals.py b/src/pretix/presale/signals.py index 703aaf47e..373e13049 100644 --- a/src/pretix/presale/signals.py +++ b/src/pretix/presale/signals.py @@ -36,10 +36,10 @@ from django.dispatch import Signal from pretix.base.signals import EventPluginSignal -global_html_head = Signal( - providing_args=["request"] -) +global_html_head = Signal() """ +Arguments: ``request`` + This signal allows you to put code inside the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -47,10 +47,10 @@ of every page in the frontend. You will get the request as the keyword argument This signal is called regardless of whether your plugin is active for all pages of the system. """ -global_html_page_header = Signal( - providing_args=["request"] -) +global_html_page_header = Signal() """ +Arguments: ``request`` + This signal allows you to put code right in the beginning of the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -58,10 +58,10 @@ of every page in the frontend. You will get the request as the keyword argument This signal is called regardless of whether your plugin is active for all pages of the system. """ -global_html_footer = Signal( - providing_args=["request"] -) +global_html_footer = Signal() """ +Arguments: ``request`` + This signal allows you to put code before the end of the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -69,10 +69,10 @@ of every page in the frontend. You will get the request as the keyword argument This signal is called regardless of whether your plugin is active for all pages of the system. """ -html_head = EventPluginSignal( - providing_args=["request"] -) +html_head = EventPluginSignal() """ +Arguments: ``request`` + This signal allows you to put code inside the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -80,10 +80,10 @@ of every page in the frontend. You will get the request as the keyword argument As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -html_page_header = EventPluginSignal( - providing_args=["request"] -) +html_page_header = EventPluginSignal() """ +Arguments: ``request`` + This signal allows you to put code right in the beginning of the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -91,10 +91,10 @@ of every page in the frontend. You will get the request as the keyword argument As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -html_footer = EventPluginSignal( - providing_args=["request"] -) +html_footer = EventPluginSignal() """ +Arguments: ``request`` + This signal allows you to put code before the end of the HTML ```` tag of every page in the frontend. You will get the request as the keyword argument ``request`` and are expected to return plain HTML. @@ -102,10 +102,10 @@ of every page in the frontend. You will get the request as the keyword argument As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -sass_preamble = EventPluginSignal( - providing_args=["filename"] -) +sass_preamble = EventPluginSignal() """ +Arguments: ``filename`` + This signal allows you to put SASS code at the beginning of the event-specific stylesheet. Keep in mind that this will only be called/rebuilt when the user changes display settings or pretix gets updated. You will get the filename that is being @@ -116,10 +116,10 @@ code. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -sass_postamble = EventPluginSignal( - providing_args=["filename"] -) +sass_postamble = EventPluginSignal() """ +Arguments: ``filename`` + This signal allows you to put SASS code at the end of the event-specific stylesheet. Keep in mind that this will only be called/rebuilt when the user changes display settings or pretix gets updated. You will get the filename that is being @@ -129,20 +129,20 @@ all of pretix' SASS code. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -footer_link = EventPluginSignal( - providing_args=["request"] -) +footer_link = EventPluginSignal() """ +Arguments: ``request`` + The signal ``pretix.presale.signals.footer_link`` allows you to add links to the footer of an event page. You are expected to return a dictionary containing the keys ``label`` and ``url``. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -global_footer_link = Signal( - providing_args=["request"] -) +global_footer_link = Signal() """ +Arguments: ``request`` + The signal ``pretix.presale.signals.global_footer_link`` allows you to add links to the footer of any page. You are expected to return a dictionary containing the keys ``label`` and ``url``. """ @@ -165,29 +165,29 @@ a subclass of ``pretix.presale.checkoutflow.BaseCheckoutFlowStep``. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -voucher_redeem_info = EventPluginSignal( - providing_args=["voucher"] -) +voucher_redeem_info = EventPluginSignal() """ +Arguments: ``voucher`` + This signal is sent out to display additional information on the "redeem a voucher" page As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -order_meta_from_request = EventPluginSignal( - providing_args=["request"] -) +order_meta_from_request = EventPluginSignal() """ +Arguments: ``request`` + This signal is sent before an order is created through the pretixpresale frontend. It allows you to return a dictionary that will be merged in the meta_info attribute of the order. You will receive the request triggering the order creation as the ``request`` keyword argument. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ -checkout_confirm_page_content = EventPluginSignal( - providing_args=['request'] -) +checkout_confirm_page_content = EventPluginSignal() """ +Arguments: 'request' + This signals allows you to add HTML content to the confirmation page that is presented at the end of the checkout process, just before the order is being created. @@ -195,10 +195,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve argument will contain the request object. """ -fee_calculation_for_cart = EventPluginSignal( - providing_args=['request', 'invoice_address', 'total', 'positions'] -) +fee_calculation_for_cart = EventPluginSignal() """ +Arguments: 'request', 'invoice_address', 'total', 'positions' + This signals allows you to add fees to a cart. You are expected to return a list of ``OrderFee`` objects that are not yet saved to the database (because there is no order yet). @@ -209,9 +209,7 @@ You should not rely on this ``total`` value for fee calculations as other fees m The ``positions`` argument will contain a list or queryset of ``CartPosition`` objects. """ -contact_form_fields = EventPluginSignal( - providing_args=[] -) +contact_form_fields = EventPluginSignal() """ This signals allows you to add form fields to the contact form that is presented during checkout and by default only asks for the email address. You are supposed to return a dictionary of @@ -222,10 +220,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve argument will contain the request object. """ -contact_form_fields_overrides = EventPluginSignal( - providing_args=["request", "order"] -) +contact_form_fields_overrides = EventPluginSignal() """ +Arguments: ``request``, ``order`` + This signal allows you to override fields of the contact form that is presented during checkout and by default only asks for the email address. It is also being used for the invoice address form. You are supposed to return a dictionary of dictionaries with globally unique keys. The @@ -237,10 +235,10 @@ argument will contain the request object. The ``order`` argument is ``None`` dur process and contains an order if the customer is trying to change an existing order. """ -question_form_fields = EventPluginSignal( - providing_args=["position"] -) +question_form_fields = EventPluginSignal() """ +Arguments: ``position`` + This signals allows you to add form fields to the questions form that is presented during checkout and by default asks for the questions configured in the backend. You are supposed to return a dictionary of form fields with globally unique keys. The validated form results will be saved into the @@ -253,10 +251,10 @@ later. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -question_form_fields_overrides = EventPluginSignal( - providing_args=["position", "request"] -) +question_form_fields_overrides = EventPluginSignal() """ +Arguments: ``position``, ``request`` + This signal allows you to override fields of the questions form that is presented during checkout and by default only asks for the questions configured in the backend. You are supposed to return a dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or @@ -270,46 +268,46 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve argument will contain the request object. """ -order_info = EventPluginSignal( - providing_args=["order", "request"] -) +order_info = EventPluginSignal() """ +Arguments: ``order``, ``request`` + This signal is sent out to display additional information on the order detail page As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -position_info = EventPluginSignal( - providing_args=["order", "position", "request"] -) +position_info = EventPluginSignal() """ +Arguments: ``order``, ``position``, ``request`` + This signal is sent out to display additional information on the position detail page As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -order_info_top = EventPluginSignal( - providing_args=["order", "request"] -) +order_info_top = EventPluginSignal() """ +Arguments: ``order``, ``request`` + This signal is sent out to display additional information on top of the order detail page As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -position_info_top = EventPluginSignal( - providing_args=["order", "position", "request"] -) +position_info_top = EventPluginSignal() """ +Arguments: ``order``, ``position``, ``request`` + This signal is sent out to display additional information on top of the position detail page As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -process_request = EventPluginSignal( - providing_args=["request"] -) +process_request = EventPluginSignal() """ +Arguments: ``request`` + This signal is sent out whenever a request is made to a event presale page. Most of the time, this will be called from the middleware layer (except on plugin-provided pages this will be called by the @event_view decorator). Similarly to Django's process_request @@ -322,10 +320,10 @@ easy to cause serious performance problems. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -process_response = EventPluginSignal( - providing_args=["request", "response"] -) +process_response = EventPluginSignal() """ +Arguments: ``request``, ``response`` + This signal is sent out whenever a response is sent from a event presale page. Most of the time, this will be called from the middleware layer (except on plugin-provided pages this will be called by the @event_view decorator). Similarly to Django's process_response @@ -339,10 +337,10 @@ easy to cause serious performance problems. As with all plugin signals, the ``sender`` keyword argument will contain the event. """ -front_page_top = EventPluginSignal( - providing_args=["request", "subevent"] -) +front_page_top = EventPluginSignal() """ +Arguments: ``request``, ``subevent`` + This signal is sent out to display additional information on the frontpage above the list of products and but below a custom frontpage text. @@ -350,10 +348,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve receivers are expected to return HTML. """ -render_seating_plan = EventPluginSignal( - providing_args=["request", "subevent", "voucher"] -) +render_seating_plan = EventPluginSignal() """ +Arguments: ``request``, ``subevent``, ``voucher`` + This signal is sent out to render a seating plan, if one is configured for the specific event. You will be passed the ``request`` as a keyword argument. If applicable, a ``subevent`` or ``voucher`` argument might be given. @@ -362,10 +360,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve receivers are expected to return HTML. """ -front_page_bottom = EventPluginSignal( - providing_args=["request", "subevent"] -) +front_page_bottom = EventPluginSignal() """ +Arguments: ``request``, ``subevent`` + This signal is sent out to display additional information on the frontpage below the list of products. @@ -373,10 +371,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve receivers are expected to return HTML. """ -front_page_bottom_widget = EventPluginSignal( - providing_args=["request", "subevent"] -) +front_page_bottom_widget = EventPluginSignal() """ +Arguments: ``request``, ``subevent`` + This signal is sent out to display additional information on the frontpage below the list of products if the front page is shown in the widget. @@ -384,10 +382,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve receivers are expected to return HTML. """ -checkout_all_optional = EventPluginSignal( - providing_args=['request'] -) +checkout_all_optional = EventPluginSignal() """ +Arguments: 'request' + If any receiver of this signal returns ``True``, all input fields during checkout (contact data, invoice address, confirmations) will be optional, except for questions. Use with care! @@ -395,10 +393,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve argument will contain the request object. """ -item_description = EventPluginSignal( - providing_args=["item", "variation"] -) +item_description = EventPluginSignal() """ +Arguments: ``item``, ``variation`` + This signal is sent out when the description of an item or variation is rendered and allows you to append additional text to the description. You are passed the ``item`` and ``variation`` and expected to return HTML. diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 6cfb53bc7..84e16cab0 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -32,7 +32,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. -from django.conf.urls import include, url +from django.conf.urls import include, re_path from django.views.decorators.csrf import csrf_exempt import pretix.presale.views.cart @@ -52,137 +52,137 @@ import pretix.presale.views.widget # configuration is done by the pretix.multidomain package. frame_wrapped_urls = [ - url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'), - url(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'), - url(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'), - url(r'^cart/answer/(?P[^/]+)/$', - pretix.presale.views.cart.AnswerDownload.as_view(), - name='event.cart.download.answer'), - url(r'^checkout/start$', pretix.presale.views.checkout.CheckoutView.as_view(), name='event.checkout.start'), - url(r'^checkout/(?P[^/]+)/$', pretix.presale.views.checkout.CheckoutView.as_view(), - name='event.checkout'), - url(r'^redeem/?$', pretix.presale.views.cart.RedeemView.as_view(), - name='event.redeem'), - url(r'^seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(), - name='event.seatingplan'), - url(r'^(?P[0-9]+)/seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(), - name='event.seatingplan'), - url(r'^(?P[0-9]+)/$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), - url(r'^waitinglist', pretix.presale.views.waiting.WaitingView.as_view(), name='event.waitinglist'), - url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), + re_path(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'), + re_path(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'), + re_path(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'), + re_path(r'^cart/answer/(?P[^/]+)/$', + pretix.presale.views.cart.AnswerDownload.as_view(), + name='event.cart.download.answer'), + re_path(r'^checkout/start$', pretix.presale.views.checkout.CheckoutView.as_view(), name='event.checkout.start'), + re_path(r'^checkout/(?P[^/]+)/$', pretix.presale.views.checkout.CheckoutView.as_view(), + name='event.checkout'), + re_path(r'^redeem/?$', pretix.presale.views.cart.RedeemView.as_view(), + name='event.redeem'), + re_path(r'^seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(), + name='event.seatingplan'), + re_path(r'^(?P[0-9]+)/seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(), + name='event.seatingplan'), + re_path(r'^(?P[0-9]+)/$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), + re_path(r'^waitinglist', pretix.presale.views.waiting.WaitingView.as_view(), name='event.waitinglist'), + re_path(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'), ] event_patterns = [ # Cart/checkout patterns are a bit more complicated, as they should have simple URLs like cart/clear in normal # cases, but need to have versions with unguessable URLs like w/8l4Y83XNonjLxoBb/cart/clear to be used in widget # mode. This is required to prevent all clickjacking and CSRF attacks that would otherwise be possible. # First, we define the normal version. The docstring of get_or_create_cart_id() has more information on this. - url(r'', include(frame_wrapped_urls)), + re_path(r'', include(frame_wrapped_urls)), # Second, the widget version - url(r'w/(?P[a-zA-Z0-9]{16})/', include(frame_wrapped_urls)), + re_path(r'w/(?P[a-zA-Z0-9]{16})/', include(frame_wrapped_urls)), # Third, a fake version that is defined like the first (and never gets called), but makes reversing URLs easier - url(r'(?P[_]{0})', include(frame_wrapped_urls)), + re_path(r'(?P[_]{0})', include(frame_wrapped_urls)), # CartAdd goes extra since it also gets a csrf_exempt decorator in one of the cases - url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), - url(r'^(?P[_]{0})cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), - url(r'w/(?P[a-zA-Z0-9]{16})/cart/add', - csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()), - name='event.cart.add'), + re_path(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), + re_path(r'^(?P[_]{0})cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'), + re_path(r'w/(?P[a-zA-Z0-9]{16})/cart/add', + csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()), + name='event.cart.add'), - url(r'unlock/(?P[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(), - name='event.payment.unlock'), - url(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'), + re_path(r'unlock/(?P[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(), + name='event.payment.unlock'), + re_path(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/open/(?P[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(), - name='event.order.open'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(), - name='event.order'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/invoice$', - pretix.presale.views.order.OrderInvoiceCreate.as_view(), - name='event.order.geninvoice'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/change$', - pretix.presale.views.order.OrderChange.as_view(), - name='event.order.change'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel$', - pretix.presale.views.order.OrderCancel.as_view(), - name='event.order.cancel'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel/do$', - pretix.presale.views.order.OrderCancelDo.as_view(), - name='event.order.cancel.do'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/modify$', - pretix.presale.views.order.OrderModify.as_view(), - name='event.order.modify'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/$', - pretix.presale.views.order.OrderPaymentStart.as_view(), - name='event.order.pay'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/confirm$', - pretix.presale.views.order.OrderPaymentConfirm.as_view(), - name='event.order.pay.confirm'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/complete$', - pretix.presale.views.order.OrderPaymentComplete.as_view(), - name='event.order.pay.complete'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/change', - pretix.presale.views.order.OrderPayChangeMethod.as_view(), - name='event.order.pay.change'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/answer/(?P[^/]+)/$', - pretix.presale.views.order.AnswerDownload.as_view(), - name='event.order.download.answer'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/download/(?P[^/]+)$', - pretix.presale.views.order.OrderDownload.as_view(), - name='event.order.download.combined'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/download/(?P[0-9]+)/(?P[^/]+)$', - pretix.presale.views.order.OrderDownload.as_view(), - name='event.order.download'), - url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/invoice/(?P[0-9]+)$', - pretix.presale.views.order.InvoiceDownload.as_view(), - name='event.invoice.download'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/open/(?P[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(), + name='event.order.open'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(), + name='event.order'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/invoice$', + pretix.presale.views.order.OrderInvoiceCreate.as_view(), + name='event.order.geninvoice'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/change$', + pretix.presale.views.order.OrderChange.as_view(), + name='event.order.change'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel$', + pretix.presale.views.order.OrderCancel.as_view(), + name='event.order.cancel'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel/do$', + pretix.presale.views.order.OrderCancelDo.as_view(), + name='event.order.cancel.do'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/modify$', + pretix.presale.views.order.OrderModify.as_view(), + name='event.order.modify'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/$', + pretix.presale.views.order.OrderPaymentStart.as_view(), + name='event.order.pay'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/confirm$', + pretix.presale.views.order.OrderPaymentConfirm.as_view(), + name='event.order.pay.confirm'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/(?P[0-9]+)/complete$', + pretix.presale.views.order.OrderPaymentComplete.as_view(), + name='event.order.pay.complete'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/change', + pretix.presale.views.order.OrderPayChangeMethod.as_view(), + name='event.order.pay.change'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/answer/(?P[^/]+)/$', + pretix.presale.views.order.AnswerDownload.as_view(), + name='event.order.download.answer'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/download/(?P[^/]+)$', + pretix.presale.views.order.OrderDownload.as_view(), + name='event.order.download.combined'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/download/(?P[0-9]+)/(?P[^/]+)$', + pretix.presale.views.order.OrderDownload.as_view(), + name='event.order.download'), + re_path(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/invoice/(?P[0-9]+)$', + pretix.presale.views.order.InvoiceDownload.as_view(), + name='event.invoice.download'), - url(r'^ticket/(?P[^/]+)/(?P\d+)/(?P[A-Za-z0-9]+)/$', - pretix.presale.views.order.OrderPositionDetails.as_view(), - name='event.order.position'), - url(r'^ticket/(?P[^/]+)/(?P\d+)/(?P[A-Za-z0-9]+)/download/(?P[0-9]+)/(?P[^/]+)$', - pretix.presale.views.order.OrderPositionDownload.as_view(), - name='event.order.position.download'), + re_path(r'^ticket/(?P[^/]+)/(?P\d+)/(?P[A-Za-z0-9]+)/$', + pretix.presale.views.order.OrderPositionDetails.as_view(), + name='event.order.position'), + re_path(r'^ticket/(?P[^/]+)/(?P\d+)/(?P[A-Za-z0-9]+)/download/(?P[0-9]+)/(?P[^/]+)$', + pretix.presale.views.order.OrderPositionDownload.as_view(), + name='event.order.position.download'), - url(r'^ical/?$', - pretix.presale.views.event.EventIcalDownload.as_view(), - name='event.ical.download'), - url(r'^ical/(?P[0-9]+)/$', - pretix.presale.views.event.EventIcalDownload.as_view(), - name='event.ical.download'), - url(r'^auth/$', pretix.presale.views.event.EventAuth.as_view(), name='event.auth'), + re_path(r'^ical/?$', + pretix.presale.views.event.EventIcalDownload.as_view(), + name='event.ical.download'), + re_path(r'^ical/(?P[0-9]+)/$', + pretix.presale.views.event.EventIcalDownload.as_view(), + name='event.ical.download'), + re_path(r'^auth/$', pretix.presale.views.event.EventAuth.as_view(), name='event.auth'), - url(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), - name='event.widget.productlist'), - url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='event.widget.css'), - url(r'^(?P\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), - name='event.widget.productlist'), + re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), + name='event.widget.productlist'), + re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='event.widget.css'), + re_path(r'^(?P\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), + name='event.widget.productlist'), ] organizer_patterns = [ - url(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'), - url(r'^events/ical/$', - pretix.presale.views.organizer.OrganizerIcalDownload.as_view(), - name='organizer.ical'), - url(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), - name='organizer.widget.productlist'), - url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'), - url(r'^account/login$', pretix.presale.views.customer.LoginView.as_view(), name='organizer.customer.login'), - url(r'^account/logout$', pretix.presale.views.customer.LogoutView.as_view(), name='organizer.customer.logout'), - url(r'^account/register$', pretix.presale.views.customer.RegistrationView.as_view(), name='organizer.customer.register'), - url(r'^account/pwreset$', pretix.presale.views.customer.ResetPasswordView.as_view(), name='organizer.customer.resetpw'), - url(r'^account/pwrecover$', pretix.presale.views.customer.SetPasswordView.as_view(), name='organizer.customer.recoverpw'), - url(r'^account/activate$', pretix.presale.views.customer.SetPasswordView.as_view(), name='organizer.customer.activate'), - url(r'^account/password$', pretix.presale.views.customer.ChangePasswordView.as_view(), name='organizer.customer.password'), - url(r'^account/change$', pretix.presale.views.customer.ChangeInformationView.as_view(), name='organizer.customer.change'), - url(r'^account/confirmchange$', pretix.presale.views.customer.ConfirmChangeView.as_view(), name='organizer.customer.change.confirm'), - url(r'^account/membership/(?P\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'), - url(r'^account/$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profile'), + re_path(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'), + re_path(r'^events/ical/$', + pretix.presale.views.organizer.OrganizerIcalDownload.as_view(), + name='organizer.ical'), + re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), + name='organizer.widget.productlist'), + re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'), + re_path(r'^account/login$', pretix.presale.views.customer.LoginView.as_view(), name='organizer.customer.login'), + re_path(r'^account/logout$', pretix.presale.views.customer.LogoutView.as_view(), name='organizer.customer.logout'), + re_path(r'^account/register$', pretix.presale.views.customer.RegistrationView.as_view(), name='organizer.customer.register'), + re_path(r'^account/pwreset$', pretix.presale.views.customer.ResetPasswordView.as_view(), name='organizer.customer.resetpw'), + re_path(r'^account/pwrecover$', pretix.presale.views.customer.SetPasswordView.as_view(), name='organizer.customer.recoverpw'), + re_path(r'^account/activate$', pretix.presale.views.customer.SetPasswordView.as_view(), name='organizer.customer.activate'), + re_path(r'^account/password$', pretix.presale.views.customer.ChangePasswordView.as_view(), name='organizer.customer.password'), + re_path(r'^account/change$', pretix.presale.views.customer.ChangeInformationView.as_view(), name='organizer.customer.change'), + re_path(r'^account/confirmchange$', pretix.presale.views.customer.ConfirmChangeView.as_view(), name='organizer.customer.change.confirm'), + re_path(r'^account/membership/(?P\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'), + re_path(r'^account/$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profile'), ] locale_patterns = [ - url(r'^locale/set$', pretix.presale.views.locale.LocaleSet.as_view(), name='locale.set'), - url(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'), - url(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'), - url(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'), - url(r'^widget/v1\.(?P[a-zA-Z0-9_\-]+)\.js$', pretix.presale.views.widget.widget_js, name='widget.js'), + re_path(r'^locale/set$', pretix.presale.views.locale.LocaleSet.as_view(), name='locale.set'), + re_path(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'), + re_path(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'), + re_path(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'), + re_path(r'^widget/v1\.(?P[a-zA-Z0-9_\-]+)\.js$', pretix.presale.views.widget.widget_js, name='widget.js'), ] diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py index f03ed02b3..85393046d 100644 --- a/src/pretix/presale/utils.py +++ b/src/pretix/presale/utils.py @@ -230,7 +230,7 @@ def _detect_event(request, require_live=True, require_plugin=None): if hasattr(request, 'event'): # Restrict locales to the ones available for this event - LocaleMiddleware().process_request(request) + LocaleMiddleware(NotImplementedError).process_request(request) if require_live and not request.event.live: can_access = ( @@ -267,7 +267,7 @@ def _detect_event(request, require_live=True, require_plugin=None): return response elif hasattr(request, 'organizer'): # Restrict locales to the ones available for this organizer - LocaleMiddleware().process_request(request) + LocaleMiddleware(NotImplementedError).process_request(request) except Event.DoesNotExist: try: diff --git a/src/pretix/settings.py b/src/pretix/settings.py index 1229a08fa..637ffc2b0 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -116,7 +116,6 @@ else: if 'mysql' in db_backend: db_options['charset'] = 'utf8mb4' -JSON_FIELD_AVAILABLE = db_backend in ('mysql', 'postgresql') DATABASES = { 'default': { @@ -605,12 +604,14 @@ COMPRESS_PRECOMPILERS = ( COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback -COMPRESS_CSS_FILTERS = ( - # CssAbsoluteFilter is incredibly slow, especially when dealing with our _flags.scss - # However, we don't need it if we consequently use the static() function in Sass - # 'compressor.filters.css_default.CssAbsoluteFilter', - 'compressor.filters.cssmin.CSSCompressorFilter', -) +COMPRESS_FILTERS = { + 'css': ( + # CssAbsoluteFilter is incredibly slow, especially when dealing with our _flags.scss + # However, we don't need it if we consequently use the static() function in Sass + # 'compressor.filters.css_default.CssAbsoluteFilter', + 'compressor.filters.cssmin.CSSCompressorFilter', + ) +} INTERNAL_IPS = ('127.0.0.1', '::1') @@ -802,3 +803,5 @@ COUNTRIES_OVERRIDE = { DATA_UPLOAD_MAX_NUMBER_FIELDS = 25000 DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB + +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' # sadly. we would prefer BigInt, and should use it for all new models but the migration will be hard diff --git a/src/pretix/testutils/settings.py b/src/pretix/testutils/settings.py index 162a7106c..7e3767f15 100644 --- a/src/pretix/testutils/settings.py +++ b/src/pretix/testutils/settings.py @@ -37,7 +37,7 @@ SITE_URL = "http://example.com" atexit.register(tmpdir.cleanup) -EMAIL_BACKEND = 'django.core.mail.outbox' +EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' COMPRESS_ENABLED = COMPRESS_OFFLINE = False COMPRESS_CACHE_BACKEND = 'testcache' diff --git a/src/pretix/urls.py b/src/pretix/urls.py index b2593800b..3d6d96d64 100644 --- a/src/pretix/urls.py +++ b/src/pretix/urls.py @@ -33,7 +33,7 @@ # License for the specific language governing permissions and limitations under the License. from django.conf import settings -from django.conf.urls import include, url +from django.conf.urls import include, re_path from django.views.generic import RedirectView import pretix.control.urls @@ -45,23 +45,23 @@ from .base.views import ( ) base_patterns = [ - url(r'^download/(?P[^/]+)/$', cachedfiles.DownloadView.as_view(), - name='cachedfile.download'), - url(r'^healthcheck/$', health.healthcheck, - name='healthcheck'), - url(r'^redirect/$', redirect.redir_view, name='redirect'), - url(r'^jsi18n/(?P[a-zA-Z-_]+)/$', js_catalog.js_catalog, name='javascript-catalog'), - url(r'^metrics$', metrics.serve_metrics, - name='metrics'), - url(r'^csp_report/$', csp.csp_report, name='csp.report'), - url(r'^agpl_source$', source.get_source, name='source'), - url(r'^js_helpers/states/$', js_helpers.states, name='js_helpers.states'), - url(r'^api/v1/', include(('pretix.api.urls', 'pretixapi'), namespace='api-v1')), - url(r'^api/$', RedirectView.as_view(url='/api/v1/'), name='redirect-api-version') + re_path(r'^download/(?P[^/]+)/$', cachedfiles.DownloadView.as_view(), + name='cachedfile.download'), + re_path(r'^healthcheck/$', health.healthcheck, + name='healthcheck'), + re_path(r'^redirect/$', redirect.redir_view, name='redirect'), + re_path(r'^jsi18n/(?P[a-zA-Z-_]+)/$', js_catalog.js_catalog, name='javascript-catalog'), + re_path(r'^metrics$', metrics.serve_metrics, + name='metrics'), + re_path(r'^csp_report/$', csp.csp_report, name='csp.report'), + re_path(r'^agpl_source$', source.get_source, name='source'), + re_path(r'^js_helpers/states/$', js_helpers.states, name='js_helpers.states'), + re_path(r'^api/v1/', include(('pretix.api.urls', 'pretixapi'), namespace='api-v1')), + re_path(r'^api/$', RedirectView.as_view(url='/api/v1/'), name='redirect-api-version') ] control_patterns = [ - url(r'^control/', include((pretix.control.urls, 'control'))), + re_path(r'^control/', include((pretix.control.urls, 'control'))), ] debug_patterns = [] @@ -69,7 +69,7 @@ if settings.DEBUG: try: import debug_toolbar - debug_patterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) + debug_patterns.append(re_path(r'^__debug__/', include(debug_toolbar.urls))) except ImportError: pass diff --git a/src/requirements/dev.txt b/src/requirements/dev.txt index 11bb51df2..b38535446 100644 --- a/src/requirements/dev.txt +++ b/src/requirements/dev.txt @@ -1,4 +1,4 @@ -django-debug-toolbar==2.1 +django-debug-toolbar==3.2 # Testing requirements pycodestyle==2.5.* pyflakes==2.1.* diff --git a/src/requirements/production.txt b/src/requirements/production.txt index af451f811..4cdb92696 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -1,25 +1,25 @@ # Functional requirements -Django==3.0.*,>=3.0.14 -djangorestframework==3.11.* +Django==3.2.* +djangorestframework==3.12.* python-dateutil==2.8.* isoweek -requests==2.24.0 +requests==2.25.0 pytz -django-bootstrap3==12.0.* +django-bootstrap3==15.0.* django-formset-js-improved==0.5.0.2 django-compressor==2.4.* django-hierarkey==1.0.*,>=1.0.4 -django-filter==2.2.* +django-filter==2.4.* django-scopes==1.2.* -reportlab>=3.5.18 +reportlab>=3.5.65 PyPDF2==1.26.* -Pillow>=8.*,<9.0 +Pillow==8.* django-libsass==0.8 libsass==0.20.* django-otp==0.7.*,>=0.7.5 python-u2flib-server==4.* webauthn==0.4.* -django-formtools==2.2 +django-formtools==2.3 celery==4.4.* kombu==4.6.* django-statici18n==1.9.* @@ -41,8 +41,8 @@ jsonschema openpyxl==3.0.* django-oauth-toolkit==1.2.* oauthlib==3.1.* -django-jsonfallback>=2.1.2 psycopg2-binary +django-mysql tqdm==4.* # Stripe stripe==2.42.* @@ -58,7 +58,7 @@ django-countries>=6.0 pyuca # for better sorting of country names in django-countries defusedcsv>=1.1.0 vat_moss_forked==2020.3.20.0.11.0 -django-localflavor>=2.2 +django-localflavor==3.0.* django-redis==4.11.* redis==3.4.* django-phonenumber-field==4.0.* diff --git a/src/setup.cfg b/src/setup.cfg index ce3d95bdd..c7a73bf26 100644 --- a/src/setup.cfg +++ b/src/setup.cfg @@ -16,11 +16,21 @@ honor_noqa = true skip = make_testdata.py,wsgi.py,bootstrap,celery_app.py,pretix/settings.py,tests/settings.py,pretix/testutils/settings.py [tool:pytest] -DJANGO_SETTINGS_MODULE=tests.settings -addopts =--reruns 3 -rw +DJANGO_SETTINGS_MODULE = tests.settings +addopts = --reruns 3 -rw filterwarnings = + error ignore:The 'warn' method is deprecated:DeprecationWarning + ignore::DeprecationWarning:mt940 + ignore::DeprecationWarning:cbor2 + ignore::ResourceWarning ignore:django.contrib.staticfiles.templatetags.static:DeprecationWarning + ignore::django.utils.deprecation.RemovedInDjango41Warning + ignore::django.utils.deprecation.RemovedInDjango40Warning:oauth2_provider + ignore::django.utils.deprecation.RemovedInDjango40Warning:hijack + ignore::django.utils.deprecation.RemovedInDjango40Warning:django_otp + ignore::django.utils.deprecation.RemovedInDjango40Warning:compressor + [coverage:run] source = pretix diff --git a/src/setup.py b/src/setup.py index dcd074586..4c3109f9a 100644 --- a/src/setup.py +++ b/src/setup.py @@ -148,27 +148,27 @@ setup( keywords='tickets web shop ecommerce', install_requires=[ - 'Django==3.0.*,>=3.0.14', - 'djangorestframework==3.11.*', + 'Django==3.2.*', + 'djangorestframework==3.12.*', 'python-dateutil==2.8.*', 'isoweek', - 'requests==2.24.*', + 'requests==2.25.*', 'pytz', - 'django-bootstrap3==12.0.*', + 'django-bootstrap3==15.0.*', 'django-formset-js-improved==0.5.0.2', 'django-compressor==2.4.*', 'django-hierarkey==1.0.*,>=1.0.4', - 'django-filter==2.2.*', + 'django-filter==2.4.*', 'django-scopes==1.2.*', - 'reportlab>=3.5.18', - 'Pillow>=8.*,<9.0', + 'reportlab>=3.5.65', + 'Pillow==8.*', 'PyPDF2==1.26.*', 'django-libsass==0.8', 'libsass==0.20.*', 'django-otp==0.7.*,>=0.7.5', 'webauthn==0.4.*', 'python-u2flib-server==4.*', - 'django-formtools==2.2', + 'django-formtools==2.3', 'celery==4.4.*', 'kombu==4.6.*', 'django-statici18n==1.9.*', @@ -192,8 +192,8 @@ setup( 'chardet<3.1.0,>=3.0.2', 'mt-940==3.2', 'django-i18nfield==1.9.*,>=1.9.1', - 'django-jsonfallback>=2.1.2', 'psycopg2-binary', + 'django-mysql', 'tqdm==4.*', 'vobject==0.9.*', 'pycountry', @@ -201,7 +201,7 @@ setup( 'pyuca', 'defusedcsv>=1.1.0', 'vat_moss_forked==2020.3.20.0.11.0', - 'django-localflavor>=2.2', + 'django-localflavor==3.0.*', 'jsonschema', 'django-hijack>=2.1.10,<2.2.0', 'openpyxl==3.0.*', @@ -220,7 +220,7 @@ setup( ], extras_require={ 'dev': [ - 'django-debug-toolbar==2.1', + 'django-debug-toolbar==3.2', 'pycodestyle==2.5.*', 'pyflakes==2.1.*', 'flake8==3.7.*', diff --git a/src/tests/api/test_idempotency.py b/src/tests/api/test_idempotency.py index 76732d007..76d5fa3d4 100644 --- a/src/tests/api/test_idempotency.py +++ b/src/tests/api/test_idempotency.py @@ -67,7 +67,7 @@ def test_scoped_by_key(token_client, organizer): PAYLOAD, format='json', HTTP_X_IDEMPOTENCY_KEY='foo') assert resp.status_code == 201 assert d1.data == json.loads(resp.content.decode()) - assert d1._headers == resp._headers + assert d1.headers._store == resp.headers._store resp = token_client.post('/api/v1/organizers/{}/events/'.format(organizer.slug), PAYLOAD, format='json', HTTP_X_IDEMPOTENCY_KEY='bar') assert resp.status_code == 400 diff --git a/src/tests/base/test_checkin.py b/src/tests/base/test_checkin.py index 09d798623..b1bacd383 100644 --- a/src/tests/base/test_checkin.py +++ b/src/tests/base/test_checkin.py @@ -771,7 +771,7 @@ def test_rules_reasoning_prefer_number_over_date(event, position, clist): @pytest.mark.django_db(transaction=True) def test_position_queries(django_assert_num_queries, position, clist): - with django_assert_num_queries(11) as captured: + with django_assert_num_queries(12 if 'sqlite' in settings.DATABASES['default']['ENGINE'] else 11) as captured: perform_checkin(position, clist, {}) if 'sqlite' not in settings.DATABASES['default']['ENGINE']: assert any('FOR UPDATE' in s['sql'] for s in captured) diff --git a/src/tests/base/test_permissions.py b/src/tests/base/test_permissions.py index 7fc75f431..ed1f09590 100644 --- a/src/tests/base/test_permissions.py +++ b/src/tests/base/test_permissions.py @@ -59,7 +59,7 @@ def admin(): def admin_request(admin, client): factory = RequestFactory() r = factory.get('/') - SessionMiddleware().process_request(r) + SessionMiddleware(NotImplementedError).process_request(r) r.session.save() admin.staffsession_set.create(date_start=now(), session_key=r.session.session_key) admin.staffsession_set.create(date_start=now(), session_key=client.session.session_key) diff --git a/src/tests/control/test_auth.py b/src/tests/control/test_auth.py index 550175644..f2e95dea5 100644 --- a/src/tests/control/test_auth.py +++ b/src/tests/control/test_auth.py @@ -33,7 +33,7 @@ # License for the specific language governing permissions and limitations under the License. import time -from datetime import date, timedelta +from datetime import datetime, timedelta import pytest from django.conf import settings @@ -526,8 +526,8 @@ class PasswordRecoveryFormTest(TestCase): def test_recovery_expired_token(self): class Mocked(PasswordResetTokenGenerator): - def _today(self): - return date.today() - timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1) + def _now(self): + return datetime.now() - timedelta(seconds=settings.PASSWORD_RESET_TIMEOUT + 3600) generator = Mocked() token = generator.make_token(self.user) diff --git a/src/tests/plugins/test_checkinlist.py b/src/tests/plugins/test_checkinlist.py index 73b0e42d9..4471a5750 100644 --- a/src/tests/plugins/test_checkinlist.py +++ b/src/tests/plugins/test_checkinlist.py @@ -101,9 +101,6 @@ def test_csv_simple(event): @pytest.mark.django_db def test_csv_order_by_name_parts(event): # noqa - from django.conf import settings - if not settings.JSON_FIELD_AVAILABLE: - raise pytest.skip("Not supported on this database") c = CSVCheckinList(event) _, _, content = c.render({ 'list': event.checkin_lists.first().pk, @@ -142,10 +139,6 @@ def test_csv_order_by_name_parts(event): # noqa @pytest.mark.django_db def test_csv_order_by_inherited_name_parts(event): # noqa - from django.conf import settings - if not settings.JSON_FIELD_AVAILABLE: - raise pytest.skip("Not supported on this database") - with scope(organizer=event.organizer): OrderPosition.objects.filter(attendee_name_cached__icontains="Andrea").delete() op = OrderPosition.objects.get() diff --git a/src/tests/presale/test_checkoutflow.py b/src/tests/presale/test_checkoutflow.py index 95baec386..055b2415c 100644 --- a/src/tests/presale/test_checkoutflow.py +++ b/src/tests/presale/test_checkoutflow.py @@ -44,7 +44,7 @@ def event(): def req_with_session(): factory = RequestFactory() r = factory.get('/') - SessionMiddleware().process_request(r) + SessionMiddleware(NotImplementedError).process_request(r) r.session.save() return r