diff --git a/src/pretix/api/migrations/0001_initial.py b/src/pretix/api/migrations/0001_initial.py index 2906bc29c..39b370097 100644 --- a/src/pretix/api/migrations/0001_initial.py +++ b/src/pretix/api/migrations/0001_initial.py @@ -1,9 +1,10 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models +import uuid + import django.db.models.deletion import oauth2_provider.generators -import uuid +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/api/migrations/0002_initial.py b/src/pretix/api/migrations/0002_initial.py index ac2f94e36..c343b269d 100644 --- a/src/pretix/api/migrations/0002_initial.py +++ b/src/pretix/api/migrations/0002_initial.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/src/pretix/api/migrations/0004_oauthapplication_allowed_origins_and_more.py b/src/pretix/api/migrations/0004_oauthapplication_allowed_origins_and_more.py index 91865f850..0a426e100 100644 --- a/src/pretix/api/migrations/0004_oauthapplication_allowed_origins_and_more.py +++ b/src/pretix/api/migrations/0004_oauthapplication_allowed_origins_and_more.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.15 on 2024-08-30 08:15 -from django.db import migrations, models import oauth2_provider.generators import oauth2_provider.models +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/api/models.py b/src/pretix/api/models.py index 0c5e98a35..9a57654a1 100644 --- a/src/pretix/api/models.py +++ b/src/pretix/api/models.py @@ -9,7 +9,7 @@ ) from oauth2_provider.models import ( AbstractAccessToken, AbstractApplication, AbstractGrant, AbstractIDToken, - AbstractRefreshToken, ClientSecretField + AbstractRefreshToken, ClientSecretField, ) from oauth2_provider.validators import URIValidator diff --git a/src/pretix/api/serializers/i18n.py b/src/pretix/api/serializers/i18n.py index f348d37f7..f578892a3 100644 --- a/src/pretix/api/serializers/i18n.py +++ b/src/pretix/api/serializers/i18n.py @@ -88,4 +88,3 @@ def to_internal_value(self, value) -> LazyI18nString: url_validator(value.data) return value - diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 05fcececa..c20f15b1e 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -22,9 +22,9 @@ from pretix.base.decimal import round_decimal from pretix.base.i18n import language from pretix.base.models import ( - CachedFile, Checkin, Invoice, Customer, InvoiceAddress, InvoiceLine, Item, + CachedFile, Checkin, Customer, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question, QuestionAnswer, Seat, - SubEvent, TaxRule, Voucher + SubEvent, TaxRule, Voucher, ) from pretix.base.models.orders import ( CartPosition, OrderFee, OrderPayment, OrderRefund, RevokedTicketSecret, @@ -591,7 +591,7 @@ class Meta: 'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date', 'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads', 'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', - 'url', 'customer' + 'url', 'customer' ) read_only_fields = ( 'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date', diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 8f2e621ae..c01f872cc 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -13,8 +13,8 @@ from pretix.base.auth import get_auth_backends from pretix.base.i18n import get_language_without_region from pretix.base.models import ( - Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, Team, - TeamAPIToken, TeamInvite, User, Customer, + Customer, Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, + Team, TeamAPIToken, TeamInvite, User, ) from pretix.base.models.seating import SeatingPlanLayoutValidator from pretix.base.services.mail import SendMailException, mail diff --git a/src/pretix/api/urls.py b/src/pretix/api/urls.py index c1a2f371f..fb30c7085 100644 --- a/src/pretix/api/urls.py +++ b/src/pretix/api/urls.py @@ -1,8 +1,7 @@ import importlib from django.apps import apps -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from rest_framework import routers from pretix.api.views import cart diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index 62636c1e2..05cfc59d1 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -16,7 +16,8 @@ ) from pretix.api.views import ConditionalListView from pretix.base.models import ( - CartPosition, Device, Event, TaxRule, TeamAPIToken, Organizer, Customer, Order, + CartPosition, Customer, Device, Event, Order, Organizer, TaxRule, + TeamAPIToken, ) from pretix.base.models.event import SubEvent from pretix.base.settings import SETTINGS_AFFECTING_CSS @@ -429,7 +430,7 @@ def get(self, request, *args, **kwargs): # Get all orders of customer which belong to this event order_list = (Order.objects.filter(Q(event=event) & (Q(customer=customer) | Q( - email__iexact=customer.email))).select_related('event').order_by('-datetime')) + email__iexact=customer.email))).select_related('event').order_by('-datetime')) if not order_list: return JsonResponse(status=404, data={"error": "Customer has no orders for this event."}) diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 80511ce31..0646a748f 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -569,7 +569,7 @@ def create(self, request, *args, **kwargs): with transaction.atomic(): try: self.perform_create(serializer) - except TaxRule.SaleNotAllowed: + except TaxRule.SaleNotAllowedException: raise ValidationError(_('One of the selected products is not available in the selected country.')) send_mail = serializer._send_mail order = serializer.instance diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index 48d37f16b..ee1b3e98a 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -18,14 +18,14 @@ from pretix.api.models import OAuthAccessToken from pretix.api.serializers.organizer import ( - DeviceSerializer, GiftCardSerializer, GiftCardTransactionSerializer, - OrganizerSerializer, OrganizerSettingsSerializer, SeatingPlanSerializer, - TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer, - TeamSerializer, CustomerSerializer, CustomerCreateSerializer, + CustomerCreateSerializer, CustomerSerializer, DeviceSerializer, + GiftCardSerializer, GiftCardTransactionSerializer, OrganizerSerializer, + OrganizerSettingsSerializer, SeatingPlanSerializer, TeamAPITokenSerializer, + TeamInviteSerializer, TeamMemberSerializer, TeamSerializer, ) from pretix.base.models import ( - Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, Team, - TeamAPIToken, TeamInvite, User, Customer, + Customer, Device, GiftCard, GiftCardTransaction, Organizer, SeatingPlan, + Team, TeamAPIToken, TeamInvite, User, ) from pretix.base.settings import SETTINGS_AFFECTING_CSS from pretix.helpers.dicts import merge_dicts diff --git a/src/pretix/base/configurations/default_setting.py b/src/pretix/base/configurations/default_setting.py index ca769b95b..128b10e81 100644 --- a/src/pretix/base/configurations/default_setting.py +++ b/src/pretix/base/configurations/default_setting.py @@ -1,5 +1,5 @@ -from collections import OrderedDict import json +from collections import OrderedDict from datetime import datetime from decimal import Decimal @@ -11,18 +11,16 @@ ) from django.utils.text import format_lazy from django.utils.translation import ( - gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy + gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy, ) from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput from i18nfield.strings import LazyI18nString -from pretix.helpers.countries import CachedCountries - from rest_framework import serializers + from pretix.api.serializers.fields import ( ListMultipleChoiceField, UploadedFileField, ) from pretix.api.serializers.i18n import I18nField, I18nURLField -from .lazy_i18n_string_list_base import LazyI18nStringListBase from pretix.base.forms import I18nURLFormField from pretix.base.models.tax import TaxRule from pretix.base.reldate import ( @@ -32,105 +30,116 @@ from pretix.control.forms import ( ExtFileField, FontSelect, MultipleLanguagesWidget, SingleLanguageWidget, ) +from pretix.helpers.countries import CachedCountries + +from .lazy_i18n_string_list_base import LazyI18nStringListBase def country_choice_kwargs(): allcountries = list(CachedCountries()) - allcountries.insert(0, ('', _('Select country'))) - return { - 'choices': allcountries - } + allcountries.insert(0, ("", _("Select country"))) + return {"choices": allcountries} def primary_font_kwargs(): from pretix.presale.style import get_fonts - choices = [('Open Sans', 'Open Sans')] - choices += [ - (a, {"title": a, "data": v}) for a, v in get_fonts().items() - ] + choices = [("Open Sans", "Open Sans")] + choices += [(a, {"title": a, "data": v}) for a, v in get_fonts().items()] return { - 'choices': choices, + "choices": choices, } DEFAULT_SETTINGS = { - 'customer_accounts': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "customer_accounts": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Allow attendees to create an account"), - help_text=_( - "Allow your attendees to sign up an account.") - ) + help_text=_("Allow your attendees to sign up an account."), + ), }, - 'customer_accounts_native': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "customer_accounts_native": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Allow attendees to log in with email address and password"), - help_text=_("If this option is disabled, attendees will need to use Single Sign-On (SSO) to log in."), - widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_settings-customer_accounts'}), - ) - }, - 'max_items_per_order': { - 'default': '10', - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( + help_text=_( + "If this option is disabled, attendees will need to use Single Sign-On (SSO) to log in." + ), + widget=forms.CheckboxInput( + attrs={"data-display-dependency": "#id_settings-customer_accounts"} + ), + ), + }, + "max_items_per_order": { + "default": "10", + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( min_value=1, label=_("Maximum number of items per order"), - help_text=_("Add-on products will be excluded from the count.") - ) - }, - 'display_net_prices': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Display net prices in the product list instead of gross prices (not recommended)"), - help_text=_( - "Regardless of your selection, the cart will display gross prices as this is the final amount to be paid."), - - ) - }, - 'system_question_order': { - 'default': {}, - 'type': dict, - }, - 'attendee_names_asked': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_("Add-on products will be excluded from the count."), + ), + }, + "display_net_prices": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_( + "Display net prices in the product list instead of gross prices (not recommended)" + ), + help_text=_( + "Regardless of your selection, the cart will display gross prices as this is the final amount to be " + "paid." + ), + ), + }, + "system_question_order": { + "default": {}, + "type": dict, + }, + "attendee_names_asked": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require attendee names"), - help_text=_("Require attendee names for all tickets which include admission to the event."), - ) - }, - 'attendee_names_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Require attendee names for all tickets which include admission to the event." + ), + ), + }, + "attendee_names_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require attendee names"), - help_text=_("Require attendees to fill in their names for all admission tickets."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_names_asked'}), - ) - }, - 'attendee_emails_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Require attendees to fill in their names for all admission tickets." + ), + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_settings-attendee_names_asked"} + ), + ), + }, + "attendee_emails_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for email addresses per ticket"), help_text=_( "Normally, eventyay asks for one email address per order and the order confirmation will be sent " @@ -138,259 +147,309 @@ def primary_font_kwargs(): "individual email addresses for every admission ticket. This might be useful if you want to " "obtain individual addresses for every attendee even in case of group orders. However, " "eventyay will send the order confirmation by default only to the primary email address, not to " - "the per-attendee addresses. You can however enable this in the E-mail settings."), - ) - }, - 'attendee_emails_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "the per-attendee addresses. You can however enable this in the E-mail settings." + ), + ), + }, + "attendee_emails_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require email addresses per ticket"), - help_text=_("Require attendees to fill in individual e-mail addresses for all admission tickets. See the " - "above option for more details. One email address for the order confirmation will always be " - "required regardless of this setting."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_emails_asked'}), - ) - }, - 'attendee_company_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Require attendees to fill in individual e-mail addresses for all admission tickets. See the " + "above option for more details. One email address for the order confirmation will always be " + "required regardless of this setting." + ), + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_settings-attendee_emails_asked"} + ), + ), + }, + "attendee_company_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for company per ticket"), - ) - }, - 'attendee_company_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "attendee_company_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require company per ticket"), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_company_asked'}), - ) - }, - 'attendee_addresses_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_settings-attendee_company_asked" + } + ), + ), + }, + "attendee_addresses_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for postal addresses per ticket"), - ) - }, - 'attendee_addresses_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "attendee_addresses_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require postal addresses per ticket"), - help_text=_("Require attendees to fill in postal addresses for all admission tickets."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_addresses_asked'}), - ) - }, - 'order_email_asked_twice': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Require attendees to fill in postal addresses for all admission tickets." + ), + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_settings-attendee_addresses_asked" + } + ), + ), + }, + "order_email_asked_twice": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for the order email address twice"), - help_text=_("Require attendees to enter their primary email address twice to help prevent errors."), - ) - }, - 'order_phone_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Require attendees to enter their primary email address twice to help prevent errors." + ), + ), + }, + "order_phone_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for a phone number per order"), - ) - }, - 'order_phone_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "order_phone_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require a phone number per order"), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-order_phone_asked'}), - ) - }, - 'invoice_address_asked': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_settings-order_phone_asked"} + ), + ), + }, + "invoice_address_asked": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for invoice address"), - ) - }, - 'invoice_address_not_asked_free': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Do not ask for invoice address if an order is free of charge.'), - ) - }, - 'invoice_name_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "invoice_address_not_asked_free": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Do not ask for invoice address if an order is free of charge."), + ), + }, + "invoice_name_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require attendee name"), - ) - }, - 'invoice_attendee_name': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "invoice_attendee_name": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show attendee names on invoices"), - ) - }, - 'invoice_eu_currencies': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("On invoices from one EU country to another EU country with a different currency, display the tax amounts in both currencies."), - ) - }, - 'invoice_address_required': { - 'default': 'False', - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'type': bool, - 'form_kwargs': dict( + ), + }, + "invoice_eu_currencies": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_( + "On invoices from one EU country to another EU country with a different currency, display the tax " + "amounts in both currencies." + ), + ), + }, + "invoice_address_required": { + "default": "False", + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "type": bool, + "form_kwargs": dict( label=_("Require invoice address"), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), - ) - }, - 'invoice_address_company_required': { - 'default': 'False', - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'type': bool, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_invoice_address_asked"} + ), + ), + }, + "invoice_address_company_required": { + "default": "False", + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "type": bool, + "form_kwargs": dict( label=_("Require a business addresses"), - help_text=_('This will require attendees to enter a company name.'), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_required'}), - ) - }, - 'invoice_address_beneficiary': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_("This will require attendees to enter a company name."), + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_invoice_address_required"} + ), + ), + }, + "invoice_address_beneficiary": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for beneficiary"), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), - ) - }, - 'invoice_address_custom_field': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_invoice_address_asked"} + ), + ), + }, + "invoice_address_custom_field": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( label=_("Custom address field"), widget=I18nTextInput, - help_text=_("If you want to add a custom text field, such as a country-specific registration number, to your invoice address form, please enter the label here. This label will be used both for prompting the user to input their details and for displaying the value on the invoice. The field will be optional.") - ) - }, - 'invoice_address_vatid': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If you want to add a custom text field, such as a country-specific registration number, " + "to your invoice address form, please enter the label here. This label will be used both for " + "prompting the user to input their details and for displaying the value on the invoice. The field " + "will be optional." + ), + ), + }, + "invoice_address_vatid": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for VAT ID"), - help_text=_("This option is only applicable if an invoice address is requested. The VAT ID field will not be required."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), - ) - }, - 'invoice_address_explanation_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + help_text=_( + "This option is only applicable if an invoice address is requested. The VAT ID field will not be " + "required." + ), + widget=forms.CheckboxInput( + attrs={"data-checkbox-dependency": "#id_invoice_address_asked"} + ), + ), + }, + "invoice_address_explanation_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( label=_("Invoice address explanation"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_("This text will be shown above the invoice address form during checkout.") - ) - }, - 'invoice_show_payments': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown above the invoice address form during checkout." + ), + ), + }, + "invoice_show_payments": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show paid amount on partially paid invoices"), - help_text=_("If an invoice has already been paid partially, this option will add the paid and pending " - "amount to the invoice."), - ) - }, - 'invoice_include_free': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If an invoice has already been paid partially, this option will add the paid and pending " + "amount to the invoice." + ), + ), + }, + "invoice_include_free": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show free products on invoices"), - help_text=_("Note that invoices will never be generated for orders that contain only free " - "products."), - ) - }, - 'invoice_include_expire_date': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Note that invoices will never be generated for orders that contain only free " + "products." + ), + ), + }, + "invoice_include_expire_date": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show expiration date of order"), - help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."), - ) - }, - 'invoice_numbers_counter_length': { - 'default': '5', - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( + help_text=_( + "The expiration date will not be shown if the invoice is generated after the order is paid." + ), + ), + }, + "invoice_numbers_counter_length": { + "default": "5", + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( label=_("Minimum length of invoice number after prefix"), help_text=_( - "The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."), - ) + "The part of your invoice number after your prefix will be filled up with leading zeros up to this " + "length, e.g. INV-001 or INV-00001." + ), + ), }, - 'invoice_numbers_consecutive': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "invoice_numbers_consecutive": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Generate invoices with consecutive numbers"), - help_text=_("If deactivated, the order code will be used in the invoice number."), - ) - }, - 'invoice_numbers_prefix': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + help_text=_( + "If deactivated, the order code will be used in the invoice number." + ), + ), + }, + "invoice_numbers_prefix": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Invoice number prefix"), help_text=_( "This will be prepended to invoice numbers. If you leave this field empty, your event slug will " @@ -398,1094 +457,1224 @@ def primary_font_kwargs(): "same value in this field, they will share their number range, i.e. every full number will be " "used at most once over all of your events. This setting only affects future invoices. You can " "use %Y (with century) %y (without century) to insert the year of the invoice, or %m and %d for " - "the day of month."), - ) - }, - 'invoice_numbers_prefix_cancellations': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + "the day of month." + ), + ), + }, + "invoice_numbers_prefix_cancellations": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Invoice number prefix for cancellations"), - help_text=_("This will be prepended to invoice numbers of cancellations. If you leave this field empty, " - "the same numbering scheme will be used that you configured for regular invoices."), - ) - }, - 'invoice_renderer': { - 'default': 'classic', - 'type': str, - }, - 'ticket_secret_generator': { - 'default': 'random', - 'type': str, - }, - 'reservation_time': { - 'default': '30', - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwags': dict( + help_text=_( + "This will be prepended to invoice numbers of cancellations. If you leave this field empty, " + "the same numbering scheme will be used that you configured for regular invoices." + ), + ), + }, + "invoice_renderer": { + "default": "classic", + "type": str, + }, + "ticket_secret_generator": { + "default": "random", + "type": str, + }, + "reservation_time": { + "default": "30", + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwags": dict( min_value=0, label=_("Reservation period"), - help_text=_("The number of minutes the items in a user's cart are reserved for this user."), - ) - }, - 'redirect_to_checkout_directly': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( - label=_('Directly redirect to check-out after a product has been added to the cart.'), - ) - }, - 'presale_has_ended_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + help_text=_( + "The number of minutes the items in a user's cart are reserved for this user." + ), + ), + }, + "redirect_to_checkout_directly": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( + label=_( + "Directly redirect to check-out after a product has been added to the cart." + ), + ), + }, + "presale_has_ended_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( label=_("End of presale text"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, + widget_kwargs={"attrs": {"rows": "2"}}, help_text=_( "This text will be shown above the ticket shop once the designated sales timeframe for this event " - "is over. You can use it to describe other options to get a ticket, such as a box office.") - ) - }, - 'payment_explanation': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + "is over. You can use it to describe other options to get a ticket, such as a box office." + ), + ), + }, + "payment_explanation": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( widget=I18nTextarea, - widget_kwargs={'attrs': { - 'rows': 3, - }}, + widget_kwargs={ + "attrs": { + "rows": 3, + } + }, label=_("Guidance text"), help_text=_( "This text will be shown above the payment options. You can explain the choices to the user here, " - "if you want.") - ) - }, - 'payment_term_mode': { - 'default': 'days', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( - choices=( - ('days', _("in days")), - ('minutes', _("in minutes")) + "if you want." ), ), - 'form_kwargs': dict( + }, + "payment_term_mode": { + "default": "days", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( + choices=(("days", _("in days")), ("minutes", _("in minutes"))), + ), + "form_kwargs": dict( label=_("Set payment term"), widget=forms.RadioSelect, - choices=( - ('days', _("in days")), - ('minutes', _("in minutes")) - ), - help_text=_("If using days, the order will expire at the end of the last day. " - "Using minutes is more exact, but should only be used for real-time payment methods.") - ) - }, - 'payment_term_days': { - 'default': '14', - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( - label=_('Payment term in days'), + choices=(("days", _("in days")), ("minutes", _("in minutes"))), + help_text=_( + "If using days, the order will expire at the end of the last day. " + "Using minutes is more exact, but should only be used for real-time payment methods." + ), + ), + }, + "payment_term_days": { + "default": "14", + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( + label=_("Payment term in days"), widget=forms.NumberInput( attrs={ - 'data-display-dependency': '#id_payment_term_mode_0', - 'data-required-if': '#id_payment_term_mode_0' + "data-display-dependency": "#id_payment_term_mode_0", + "data-required-if": "#id_payment_term_mode_0", }, ), help_text=_( "The number of days after placing an order the user has to pay to preserve their reservation. If " "you use slow payment methods like bank transfer, we recommend 14 days. If you only use real-time " "payment methods, we recommend still setting two or three days to allow people to retry failed " - "payments."), - validators=[MinValueValidator(0), - MaxValueValidator(1000000)] - ), - 'serializer_kwargs': dict( - validators=[MinValueValidator(0), - MaxValueValidator(1000000)] - ) - }, - 'payment_term_weekdays': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Only end payment terms on weekdays'), + "payments." + ), + validators=[MinValueValidator(0), MaxValueValidator(1000000)], + ), + "serializer_kwargs": dict( + validators=[MinValueValidator(0), MaxValueValidator(1000000)] + ), + }, + "payment_term_weekdays": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Only end payment terms on weekdays"), help_text=_( "If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be " "moved to the next Monday instead. This is required in some countries by civil law. This will " - "not effect the last date of payments configured below."), + "not effect the last date of payments configured below." + ), widget=forms.CheckboxInput( attrs={ - 'data-display-dependency': '#id_payment_term_mode_0', + "data-display-dependency": "#id_payment_term_mode_0", }, ), - ) + ), }, - 'payment_term_minutes': { - 'default': '30', - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( - label=_('Payment term in minutes'), + "payment_term_minutes": { + "default": "30", + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( + label=_("Payment term in minutes"), help_text=_( "The number of minutes after placing an order the user has to pay to preserve their reservation. " - "Only use this if you exclusively offer real-time payment methods. Please note that for technical reasons, " - "the actual time frame might be a few minutes longer before the order is marked as expired."), - validators=[MinValueValidator(0), - MaxValueValidator(1440)], + "Only use this if you exclusively offer real-time payment methods. Please note that for technical " + "reasons, the actual time frame might be a few minutes longer before the order is marked as expired." + ), + validators=[MinValueValidator(0), MaxValueValidator(1440)], widget=forms.NumberInput( attrs={ - 'data-display-dependency': '#id_payment_term_mode_1', - 'data-required-if': '#id_payment_term_mode_1' + "data-display-dependency": "#id_payment_term_mode_1", + "data-required-if": "#id_payment_term_mode_1", }, ), ), - 'serializer_kwargs': dict( - validators=[MinValueValidator(0), - MaxValueValidator(1440)] - ) - }, - 'payment_term_last': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateField, - 'serializer_class': SerializerRelativeDateField, - 'form_kwargs': dict( - label=_('Last date of payments'), - help_text=_("The last date any payments are accepted. This has precedence over the terms " - "configured above. If you use the event series feature and an order contains tickets for " - "multiple dates, the earliest date will be used."), - ) - }, - 'payment_term_expire_automatically': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Automatically expire unpaid orders'), - help_text=_("If checked, all unpaid orders will automatically go from 'pending' to 'expired' " - "after the end of their payment deadline. This means that those tickets go back to " - "the pool and can be ordered by other people."), - ) - }, - 'payment_pending_hidden': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "serializer_kwargs": dict( + validators=[MinValueValidator(0), MaxValueValidator(1440)] + ), + }, + "payment_term_last": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateField, + "serializer_class": SerializerRelativeDateField, + "form_kwargs": dict( + label=_("Last date of payments"), + help_text=_( + "The last date any payments are accepted. This has precedence over the terms " + "configured above. If you use the event series feature and an order contains tickets for " + "multiple dates, the earliest date will be used." + ), + ), + }, + "payment_term_expire_automatically": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Automatically expire unpaid orders"), + help_text=_( + "If checked, all unpaid orders will automatically go from 'pending' to 'expired' " + "after the end of their payment deadline. This means that those tickets go back to " + "the pool and can be ordered by other people." + ), + ), + }, + "payment_pending_hidden": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_('Hide "payment pending" state on attendee-facing pages'), - help_text=_("The payment instructions panel will still be shown to the primary attendee, but no indication " - "of missing payment will be visible on the ticket pages of attendees who did not buy the ticket " - "themselves.") - ) - }, - 'payment_giftcard__enabled': { - 'default': 'True', - 'type': bool - }, - 'payment_resellers__restrict_to_sales_channels': { - 'default': ['resellers'], - 'type': list - }, - 'payment_term_accept_late': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Accept late payments'), - help_text=_("Accept payments for orders even when they are in 'expired' state as long as enough " - "capacity is available. No payments will ever be accepted after the 'Last date of payments' " - "configured above."), - ) - }, - 'presale_start_show_date': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "The payment instructions panel will still be shown to the primary attendee, but no indication " + "of missing payment will be visible on the ticket pages of attendees who did not buy the ticket " + "themselves." + ), + ), + }, + "payment_giftcard__enabled": {"default": "True", "type": bool}, + "payment_resellers__restrict_to_sales_channels": { + "default": ["resellers"], + "type": list, + }, + "payment_term_accept_late": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Accept late payments"), + help_text=_( + "Accept payments for orders even when they are in 'expired' state as long as enough " + "capacity is available. No payments will ever be accepted after the 'Last date of payments' " + "configured above." + ), + ), + }, + "presale_start_show_date": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show start date"), help_text=_("Show the presale start date before presale has started."), widget=forms.CheckboxInput, - ) - }, - 'tax_rate_default': { - 'default': None, - 'type': TaxRule - }, - 'invoice_generate': { - 'default': 'False', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + ), + }, + "tax_rate_default": {"default": None, "type": TaxRule}, + "invoice_generate": { + "default": "False", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=( - ('False', _('Do not generate invoices')), - ('admin', _('Only manually in admin panel')), - ('user', _('Automatically on user request')), - ('True', _('Automatically for all created orders')), - ('paid', _('Automatically on payment or when required by payment method')), + ("False", _("Do not generate invoices")), + ("admin", _("Only manually in admin panel")), + ("user", _("Automatically on user request")), + ("True", _("Automatically for all created orders")), + ( + "paid", + _("Automatically on payment or when required by payment method"), + ), ), ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Generate invoices"), widget=forms.RadioSelect, choices=( - ('False', _('Do not generate invoices')), - ('admin', _('Only manually in admin panel')), - ('user', _('Automatically on user request')), - ('True', _('Automatically for all created orders')), - ('paid', _('Automatically on payment or when required by payment method')), - ), - help_text=_("Invoices will never be automatically generated for free orders.") - ) - }, - 'invoice_reissue_after_modify': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ("False", _("Do not generate invoices")), + ("admin", _("Only manually in admin panel")), + ("user", _("Automatically on user request")), + ("True", _("Automatically for all created orders")), + ( + "paid", + _("Automatically on payment or when required by payment method"), + ), + ), + help_text=_( + "Invoices will never be automatically generated for free orders." + ), + ), + }, + "invoice_reissue_after_modify": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Automatically cancel and reissue invoice on address changes"), - help_text=_("If attendees change their invoice address on an existing order, the invoice will " - "automatically be canceled and a new invoice will be issued. This setting does not affect " - "changes made through the backend."), - ) - }, - 'invoice_generate_sales_channels': { - 'default': json.dumps(['web']), - 'type': list - }, - 'invoice_address_from': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + help_text=_( + "If attendees change their invoice address on an existing order, the invoice will " + "automatically be canceled and a new invoice will be issued. This setting does not affect " + "changes made through the backend." + ), + ), + }, + "invoice_generate_sales_channels": {"default": json.dumps(["web"]), "type": list}, + "invoice_address_from": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Address line"), - widget=forms.Textarea(attrs={ - 'rows': 2, - 'placeholder': _( - 'Albert Einstein Road 52' - ) - }), - ) - }, - 'invoice_address_from_name': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + widget=forms.Textarea( + attrs={"rows": 2, "placeholder": _("Albert Einstein Road 52")} + ), + ), + }, + "invoice_address_from_name": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Company name"), - ) - }, - 'invoice_address_from_zipcode': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( - widget=forms.TextInput(attrs={ - 'placeholder': '12345' - }), + ), + }, + "invoice_address_from_zipcode": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( + widget=forms.TextInput(attrs={"placeholder": "12345"}), label=_("ZIP code"), - ) - }, - 'invoice_address_from_city': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( - widget=forms.TextInput(attrs={ - 'placeholder': _('Random City') - }), + ), + }, + "invoice_address_from_city": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( + widget=forms.TextInput(attrs={"placeholder": _("Random City")}), label=_("City"), - ) - }, - 'invoice_address_from_country': { - 'default': '', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': lambda: dict(**country_choice_kwargs()), - 'form_kwargs': lambda: dict(label=_('Country'), **country_choice_kwargs()), - }, - 'invoice_address_from_tax_id': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + ), + }, + "invoice_address_from_country": { + "default": "", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": lambda: dict(**country_choice_kwargs()), + "form_kwargs": lambda: dict(label=_("Country"), **country_choice_kwargs()), + }, + "invoice_address_from_tax_id": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Domestic tax ID"), - help_text=_("e.g. tax number in Germany, ABN in Australia, …") - ) - }, - 'invoice_address_from_vat_id': { - 'default': '', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + help_text=_("e.g. tax number in Germany, ABN in Australia, …"), + ), + }, + "invoice_address_from_vat_id": { + "default": "", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("EU VAT ID"), - ) - }, - 'invoice_introductory_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + ), + }, + "invoice_introductory_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( widget=I18nTextarea, - widget_kwargs={'attrs': { - 'rows': 3, - 'placeholder': _( - 'e.g. With this document, we sent you the invoice for your ticket order.' - ) - }}, + widget_kwargs={ + "attrs": { + "rows": 3, + "placeholder": _( + "e.g. With this document, we sent you the invoice for your ticket order." + ), + } + }, label=_("Introductory text"), - help_text=_("Will be printed on every invoice above the invoice rows.") - ) - }, - 'invoice_additional_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + help_text=_("Will be printed on every invoice above the invoice rows."), + ), + }, + "invoice_additional_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( widget=I18nTextarea, - widget_kwargs={'attrs': { - 'rows': 3, - 'placeholder': _( - 'e.g. Thank you for your purchase! You can find more information on the event at ...' - ) - }}, + widget_kwargs={ + "attrs": { + "rows": 3, + "placeholder": _( + "e.g. Thank you for your purchase! You can find more information on the event at ..." + ), + } + }, label=_("Additional text"), - help_text=_("Will be printed on every invoice below the invoice total.") - ) - }, - 'invoice_footer_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + help_text=_("Will be printed on every invoice below the invoice total."), + ), + }, + "invoice_footer_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( widget=I18nTextarea, - widget_kwargs={'attrs': { - 'rows': 5, - 'placeholder': _( - 'e.g. your bank details, legal details like your VAT ID, registration numbers, etc.' - ) - }}, + widget_kwargs={ + "attrs": { + "rows": 5, + "placeholder": _( + "e.g. your bank details, legal details like your VAT ID, registration numbers, etc." + ), + } + }, label=_("Footer"), - help_text=_("Will be printed centered and in a smaller font at the end of every invoice page.") - ) - }, - 'invoice_language': { - 'default': '__user__', - 'type': str - }, - 'invoice_email_attachment': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Will be printed centered and in a smaller font at the end of every invoice page." + ), + ), + }, + "invoice_language": {"default": "__user__", "type": str}, + "invoice_email_attachment": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Attach invoices to emails"), - help_text=_("If invoices are automatically generated for all orders, they will be attached to the order " - "confirmation mail. If they are automatically generated on payment, they will be attached to the " - "payment confirmation mail. If they are not automatically generated, they will not be attached " - "to emails."), - ) - }, - 'show_items_outside_presale_period': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If invoices are automatically generated for all orders, they will be attached to the order " + "confirmation mail. If they are automatically generated on payment, they will be attached to the " + "payment confirmation mail. If they are not automatically generated, they will not be attached " + "to emails." + ), + ), + }, + "show_items_outside_presale_period": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show items outside presale period"), - help_text=_("Show item details before presale has started and after presale has ended"), - ) - }, - 'timezone': { - 'default': settings.TIME_ZONE, - 'type': str - }, - 'locales': { - 'default': json.dumps([settings.LANGUAGE_CODE]), - 'type': list, - 'serializer_class': ListMultipleChoiceField, - 'serializer_kwargs': dict( + help_text=_( + "Show item details before presale has started and after presale has ended" + ), + ), + }, + "timezone": {"default": settings.TIME_ZONE, "type": str}, + "locales": { + "default": json.dumps([settings.LANGUAGE_CODE]), + "type": list, + "serializer_class": ListMultipleChoiceField, + "serializer_kwargs": dict( choices=settings.LANGUAGES, required=True, ), - 'form_class': forms.MultipleChoiceField, - 'form_kwargs': dict( + "form_class": forms.MultipleChoiceField, + "form_kwargs": dict( choices=settings.LANGUAGES, widget=MultipleLanguagesWidget, required=True, label=_("Available languages"), - ) + ), }, - 'locale': { - 'default': settings.LANGUAGE_CODE, - 'type': str, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + "locale": { + "default": settings.LANGUAGE_CODE, + "type": str, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=settings.LANGUAGES, required=True, ), - 'form_class': forms.ChoiceField, - 'form_kwargs': dict( + "form_class": forms.ChoiceField, + "form_kwargs": dict( choices=settings.LANGUAGES, widget=SingleLanguageWidget, required=True, label=_("Default language"), - ) - }, - 'region': { - 'default': None, - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': lambda: dict(**country_choice_kwargs()), - 'form_kwargs': lambda: dict( - label=_('Region'), - help_text=_('Will be used to determine date and time formatting as well as default country for attendee ' - 'addresses and phone numbers. For formatting, this takes less priority than the language and ' - 'is therefore mostly relevant for languages used in different regions globally (like English).'), - **country_choice_kwargs() - ), - }, - 'show_dates_on_frontpage': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + ), + }, + "region": { + "default": None, + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": lambda: dict(**country_choice_kwargs()), + "form_kwargs": lambda: dict( + label=_("Region"), + help_text=_( + "Will be used to determine date and time formatting as well as default country for attendee " + "addresses and phone numbers. For formatting, this takes less priority than the language and " + "is therefore mostly relevant for languages used in different regions globally (like English)." + ), + **country_choice_kwargs(), + ), + }, + "show_dates_on_frontpage": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Show event times and dates on the ticket shop"), - help_text=_("If disabled, no date or time will be shown on the ticket shop's front page. This settings " - "does however not affect the display in other locations."), - ) - }, - 'show_date_to': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If disabled, no date or time will be shown on the ticket shop's front page. This settings " + "does however not affect the display in other locations." + ), + ), + }, + "show_date_to": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Show event end date"), - help_text=_("If disabled, only event's start date will be displayed to the public."), - ) - }, - 'show_times': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If disabled, only event's start date will be displayed to the public." + ), + ), + }, + "show_times": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Show dates with time"), - help_text=_("If disabled, the event's start and end date will be displayed without the time of day."), - ) - }, - 'hide_sold_out': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If disabled, the event's start and end date will be displayed without the time of day." + ), + ), + }, + "hide_sold_out": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Hide all products that are sold out"), - ) - }, - 'show_quota_left': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + ), + }, + "show_quota_left": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Show number of tickets left"), - help_text=_("Publicly show how many tickets of a certain type are still available."), - ) - }, - 'meta_noindex': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( - label=_('Ask search engines not to index the ticket shop'), - ) - }, - 'show_variations_expanded': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Publicly show how many tickets of a certain type are still available." + ), + ), + }, + "meta_noindex": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( + label=_("Ask search engines not to index the ticket shop"), + ), + }, + "show_variations_expanded": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Show variations of a product expanded by default"), - ) - }, - 'waiting_list_enabled': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + ), + }, + "waiting_list_enabled": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Enable waiting list"), - help_text=_("Once a ticket is sold out, people can add themselves to a waiting list. As soon as a ticket " - "becomes available again, it will be reserved for the first person on the waiting list and this " - "person will receive an email notification with a voucher that can be used to buy a ticket."), - ) - }, - 'waiting_list_auto': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "Once a ticket is sold out, people can add themselves to a waiting list. As soon as a ticket " + "becomes available again, it will be reserved for the first person on the waiting list and this " + "person will receive an email notification with a voucher that can be used to buy a ticket." + ), + ), + }, + "waiting_list_auto": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Automatic waiting list assignments"), help_text=_( "If ticket capacity becomes free, automatically create a voucher and send it to the first person " "on the waiting list for that product. If this is not active, mails will not be send automatically " "but you can send them manually via the control panel. If you disable the waiting list but keep " - "this option enabled, tickets will still be sent out."), + "this option enabled, tickets will still be sent out." + ), widget=forms.CheckboxInput(), - ) - }, - 'waiting_list_hours': { - 'default': '48', - 'type': int, - 'serializer_class': serializers.IntegerField, - 'form_class': forms.IntegerField, - 'form_kwargs': dict( + ), + }, + "waiting_list_hours": { + "default": "48", + "type": int, + "serializer_class": serializers.IntegerField, + "form_class": forms.IntegerField, + "form_kwargs": dict( label=_("Waiting list response time"), min_value=1, help_text=_( "If a ticket voucher is sent to a person on the waiting list, it has to be redeemed within this " - "number of hours until it expires and can be re-assigned to the next person on the list."), + "number of hours until it expires and can be re-assigned to the next person on the list." + ), widget=forms.NumberInput(), - ) - }, - 'waiting_list_names_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "waiting_list_names_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for a name"), help_text=_("Ask for a name when signing up to the waiting list."), - ) - }, - 'waiting_list_names_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "waiting_list_names_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require name"), help_text=_("Require a name when signing up to the waiting list.."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-waiting_list_names_asked'}), - ) - }, - 'waiting_list_phones_asked': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_settings-waiting_list_names_asked" + } + ), + ), + }, + "waiting_list_phones_asked": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Ask for a phone number"), help_text=_("Ask for a phone number when signing up to the waiting list."), - ) - }, - 'waiting_list_phones_required': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "waiting_list_phones_required": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Require phone number"), help_text=_("Require a phone number when signing up to the waiting list.."), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-waiting_list_phones_asked'}), - ) - }, - 'waiting_list_phones_explanation_text': { - 'default': '', - 'type': LazyI18nString, - 'form_class': I18nFormField, - 'serializer_class': I18nField, - 'form_kwargs': dict( + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_settings-waiting_list_phones_asked" + } + ), + ), + }, + "waiting_list_phones_explanation_text": { + "default": "", + "type": LazyI18nString, + "form_class": I18nFormField, + "serializer_class": I18nField, + "form_kwargs": dict( label=_("Phone number explanation"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, + widget_kwargs={"attrs": {"rows": "2"}}, help_text=_( - "If you ask for a phone number, explain why you do so and what you will use the phone number for.") - ) + "If you ask for a phone number, explain why you do so and what you will use the phone number for." + ), + ), }, - - 'ticket_download': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + "ticket_download": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Allow users to download tickets"), help_text=_("If this is off, nobody can download a ticket."), - ) - }, - 'ticket_download_date': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateTimeField, - 'serializer_class': SerializerRelativeDateTimeField, - 'form_kwargs': dict( + ), + }, + "ticket_download_date": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateTimeField, + "serializer_class": SerializerRelativeDateTimeField, + "form_kwargs": dict( label=_("Download date"), help_text=_( "Ticket download will be offered after this date. If you use the event series feature and an order " "contains tickets for multiple event dates, download of all tickets will be available if at least " - "one of the event dates allows it."), - ) - }, - 'ticket_download_addons': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + "one of the event dates allows it." + ), + ), + }, + "ticket_download_addons": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Generate tickets for add-on products"), - help_text=_('By default, tickets are only issued for products selected individually, not for add-on ' - 'products. With this option, a separate ticket is issued for every add-on product as well.'), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download', - 'data-checkbox-dependency-visual': 'on'}), - ) - }, - 'ticket_download_nonadm': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "By default, tickets are only issued for products selected individually, not for add-on " + "products. With this option, a separate ticket is issued for every add-on product as well." + ), + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_ticket_download", + "data-checkbox-dependency-visual": "on", + } + ), + ), + }, + "ticket_download_nonadm": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Generate tickets for all products"), - help_text=_('If turned off, tickets are only issued for products that are marked as an "admission ticket"' - 'in the product settings. You can also turn off ticket issuing in every product separately.'), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download', - 'data-checkbox-dependency-visual': 'on'}), - ) - }, - 'ticket_download_pending': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + 'If turned off, tickets are only issued for products that are marked as an "admission ticket"' + "in the product settings. You can also turn off ticket issuing in every product separately." + ), + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_ticket_download", + "data-checkbox-dependency-visual": "on", + } + ), + ), + }, + "ticket_download_pending": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Generate tickets for pending orders"), - help_text=_('If turned off, ticket downloads are only possible after an order has been marked as paid.'), - widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_ticket_download', - 'data-checkbox-dependency-visual': 'on'}), - ) - }, - 'ticket_download_require_validated_email': { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If turned off, ticket downloads are only possible after an order has been marked as paid." + ), + widget=forms.CheckboxInput( + attrs={ + "data-checkbox-dependency": "#id_ticket_download", + "data-checkbox-dependency-visual": "on", + } + ), + ), + }, + "ticket_download_require_validated_email": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Do not issue ticket before email address is validated"), - help_text=_("If turned on, tickets will not be offered for download directly after purchase. They will " - "be attached to the payment confirmation email (if the file size is not too large), and the " - "attendee will be able to download them from the page as soon as they clicked a link in " - "the email. Does not affect orders performed through other sales channels."), - ) - }, - 'require_registered_account_for_tickets' : { - 'default': 'False', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If turned on, tickets will not be offered for download directly after purchase. They will " + "be attached to the payment confirmation email (if the file size is not too large), and the " + "attendee will be able to download them from the page as soon as they clicked a link in " + "the email. Does not affect orders performed through other sales channels." + ), + ), + }, + "require_registered_account_for_tickets": { + "default": "False", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( label=_("Only allow registered accounts to get a ticket"), - help_text=_("If this option is turned on, only registered accounts will be allowed to purchase tickets. The 'Continue as a Guest' option will not be available for attendees."), - ) - }, - 'event_list_availability': { - 'default': 'True', - 'type': bool, - 'serializer_class': serializers.BooleanField, - 'form_class': forms.BooleanField, - 'form_kwargs': dict( - label=_('Show availability in event overviews'), - help_text=_('If checked, the list of events will show if events are sold out. This might ' - 'make for longer page loading times if you have lots of events and the shown status might be out ' - 'of date for up to two minutes.'), - required=False - ) - }, - 'event_list_type': { - 'default': 'list', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + help_text=_( + "If this option is turned on, only registered accounts will be allowed to purchase tickets. The " + "'Continue as a Guest' option will not be available for attendees." + ), + ), + }, + "event_list_availability": { + "default": "True", + "type": bool, + "serializer_class": serializers.BooleanField, + "form_class": forms.BooleanField, + "form_kwargs": dict( + label=_("Show availability in event overviews"), + help_text=_( + "If checked, the list of events will show if events are sold out. This might " + "make for longer page loading times if you have lots of events and the shown status might be out " + "of date for up to two minutes." + ), + required=False, + ), + }, + "event_list_type": { + "default": "list", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=( - ('list', _('List')), - ('week', _('Week calendar')), - ('calendar', _('Month calendar')), + ("list", _("List")), + ("week", _("Week calendar")), + ("calendar", _("Month calendar")), ) ), - 'form_kwargs': dict( - label=_('Default overview style'), - choices=( - ('list', _('List')), - ('week', _('Week calendar')), - ('calendar', _('Month calendar')), + "form_kwargs": dict( + label=_("Default overview style"), + choices=( + ("list", _("List")), + ("week", _("Week calendar")), + ("calendar", _("Month calendar")), + ), + help_text=_( + "If your event series has more than 50 dates in the future, only the month or week calendar can be " + "used." + ), + ), + }, + "event_list_available_only": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Hide all unavailable dates from calendar or list views"), + ), + }, + "allow_modifications_after_checkin": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_( + "Allow attendees to modify their information after they checked in." + ), + ), + }, + "last_order_modification_date": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateTimeField, + "serializer_class": SerializerRelativeDateTimeField, + "form_kwargs": dict( + label=_("Last date of modifications"), + help_text=_( + "The last date users can modify details of their orders, such as attendee names or " + "answers to questions. If you use the event series feature and an order contains tickets for " + "multiple event dates, the earliest date will be used." + ), + ), + }, + "change_allow_user_variation": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_( + "Customers can change the variation of the products they purchased" ), - help_text=_( - 'If your event series has more than 50 dates in the future, only the month or week calendar can be used.') ), }, - 'event_list_available_only': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Hide all unavailable dates from calendar or list views"), - ) - }, - 'allow_modifications_after_checkin': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Allow attendees to modify their information after they checked in."), - ) - }, - 'last_order_modification_date': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateTimeField, - 'serializer_class': SerializerRelativeDateTimeField, - 'form_kwargs': dict( - label=_('Last date of modifications'), - help_text=_("The last date users can modify details of their orders, such as attendee names or " - "answers to questions. If you use the event series feature and an order contains tickets for " - "multiple event dates, the earliest date will be used."), - ) - }, - 'change_allow_user_variation': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Customers can change the variation of the products they purchased"), - ) - }, - 'change_allow_user_price': { - 'default': 'gte', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + "change_allow_user_price": { + "default": "gte", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=( - ('gte', _('Only allow changes if the resulting price is higher or equal than the previous price.')), - ('gt', _('Only allow changes if the resulting price is higher than the previous price.')), - ('eq', _('Only allow changes if the resulting price is equal to the previous price.')), - ('any', _('Allow changes regardless of price, even if this results in a refund.')), + ( + "gte", + _( + "Only allow changes if the resulting price is higher or equal than the previous price." + ), + ), + ( + "gt", + _( + "Only allow changes if the resulting price is higher than the previous price." + ), + ), + ( + "eq", + _( + "Only allow changes if the resulting price is equal to the previous price." + ), + ), + ( + "any", + _( + "Allow changes regardless of price, even if this results in a refund." + ), + ), ) ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Requirement for changed prices"), choices=( - ('gte', _('Only allow changes if the resulting price is higher or equal than the previous price.')), - ('gt', _('Only allow changes if the resulting price is higher than the previous price.')), - ('eq', _('Only allow changes if the resulting price is equal to the previous price.')), - ('any', _('Allow changes regardless of price, even if this results in a refund.')), + ( + "gte", + _( + "Only allow changes if the resulting price is higher or equal than the previous price." + ), + ), + ( + "gt", + _( + "Only allow changes if the resulting price is higher than the previous price." + ), + ), + ( + "eq", + _( + "Only allow changes if the resulting price is equal to the previous price." + ), + ), + ( + "any", + _( + "Allow changes regardless of price, even if this results in a refund." + ), + ), ), widget=forms.RadioSelect, ), }, - 'change_allow_user_until': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateTimeField, - 'serializer_class': SerializerRelativeDateTimeField, - 'form_kwargs': dict( + "change_allow_user_until": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateTimeField, + "serializer_class": SerializerRelativeDateTimeField, + "form_kwargs": dict( label=_("Do not allow changes after"), - ) - }, - 'cancel_allow_user': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "cancel_allow_user": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Customers can cancel their unpaid orders"), - ) - }, - 'cancel_allow_user_until': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateTimeField, - 'serializer_class': SerializerRelativeDateTimeField, - 'form_kwargs': dict( + ), + }, + "cancel_allow_user_until": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateTimeField, + "serializer_class": SerializerRelativeDateTimeField, + "form_kwargs": dict( label=_("Do not allow cancellations after"), - ) - }, - 'cancel_allow_user_paid': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "cancel_allow_user_paid": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Customers can cancel their paid orders"), - help_text=_("Paid money will be automatically paid back if the payment method allows it. " - "Otherwise, a manual refund will be created for you to process manually."), - ) - }, - 'cancel_allow_user_paid_keep': { - 'default': '0.00', - 'type': Decimal, - 'form_class': forms.DecimalField, - 'serializer_class': serializers.DecimalField, - 'serializer_kwargs': dict( - max_digits=10, decimal_places=2 - ), - 'form_kwargs': dict( + help_text=_( + "Paid money will be automatically paid back if the payment method allows it. " + "Otherwise, a manual refund will be created for you to process manually." + ), + ), + }, + "cancel_allow_user_paid_keep": { + "default": "0.00", + "type": Decimal, + "form_class": forms.DecimalField, + "serializer_class": serializers.DecimalField, + "serializer_kwargs": dict(max_digits=10, decimal_places=2), + "form_kwargs": dict( label=_("Keep a fixed cancellation fee"), - ) - }, - 'cancel_allow_user_paid_keep_fees': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Keep payment, shipping and service fees"), - ) + ), }, - 'cancel_allow_user_paid_keep_percentage': { - 'default': '0.00', - 'type': Decimal, - 'form_class': forms.DecimalField, - 'serializer_class': serializers.DecimalField, - 'serializer_kwargs': dict( - max_digits=10, decimal_places=2 + "cancel_allow_user_paid_keep_fees": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Keep payment, shipping and service fees"), ), - 'form_kwargs': dict( + }, + "cancel_allow_user_paid_keep_percentage": { + "default": "0.00", + "type": Decimal, + "form_class": forms.DecimalField, + "serializer_class": serializers.DecimalField, + "serializer_kwargs": dict(max_digits=10, decimal_places=2), + "form_kwargs": dict( label=_("Keep a percentual cancellation fee"), - ) - }, - 'cancel_allow_user_paid_adjust_fees': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + ), + }, + "cancel_allow_user_paid_adjust_fees": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Allow attendees to voluntarily choose a lower refund"), - help_text=_("With this option enabled, your attendees can choose to get a smaller refund to support you.") - ) - }, - 'cancel_allow_user_paid_adjust_fees_explanation': { - 'default': LazyI18nString.from_gettext(gettext_noop( - 'However, if you want us to help keep the lights on here, please consider using the slider below to ' - 'request a smaller refund. Thank you!' - )), - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + help_text=_( + "With this option enabled, your attendees can choose to get a smaller refund to support you." + ), + ), + }, + "cancel_allow_user_paid_adjust_fees_explanation": { + "default": LazyI18nString.from_gettext( + gettext_noop( + "However, if you want us to help keep the lights on here, please consider using the slider below to " + "request a smaller refund. Thank you!" + ) + ), + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Voluntary lower refund explanation"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_("This text will be shown in between the explanation of how the refunds work and the slider " - "which your attendees can use to choose the amount they would like to receive. You can use it " - "e.g. to explain choosing a lower refund will help your organization.") - ) - }, - 'cancel_allow_user_paid_adjust_fees_step': { - 'default': None, - 'type': Decimal, - 'form_class': forms.DecimalField, - 'serializer_class': serializers.DecimalField, - 'serializer_kwargs': dict( - max_digits=10, decimal_places=2 - ), - 'form_kwargs': dict( - max_digits=10, decimal_places=2, + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown in between the explanation of how the refunds work and the slider " + "which your attendees can use to choose the amount they would like to receive. You can use it " + "e.g. to explain choosing a lower refund will help your organization." + ), + ), + }, + "cancel_allow_user_paid_adjust_fees_step": { + "default": None, + "type": Decimal, + "form_class": forms.DecimalField, + "serializer_class": serializers.DecimalField, + "serializer_kwargs": dict(max_digits=10, decimal_places=2), + "form_kwargs": dict( + max_digits=10, + decimal_places=2, label=_("Step size for reduction amount"), - help_text=_('By default, attendees can choose an arbitrary amount for you to keep. If you set this to e.g. ' - '10, they will only be able to choose values in increments of 10.') - ) - }, - 'cancel_allow_user_paid_require_approval': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_("Customers can only request a cancellation that needs to be approved by the event organizer " - "before the order is canceled and a refund is issued."), - ) - }, - 'cancel_allow_user_paid_refund_as_giftcard': { - 'default': 'off', - 'type': str, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + help_text=_( + "By default, attendees can choose an arbitrary amount for you to keep. If you set this to e.g. " + "10, they will only be able to choose values in increments of 10." + ), + ), + }, + "cancel_allow_user_paid_require_approval": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_( + "Customers can only request a cancellation that needs to be approved by the event organizer " + "before the order is canceled and a refund is issued." + ), + ), + }, + "cancel_allow_user_paid_refund_as_giftcard": { + "default": "off", + "type": str, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=[ - ('off', _('All refunds are issued to the original payment method')), - ('option', _('Customers can choose between a gift card and a refund to their payment method')), - ('force', _('All refunds are issued as gift cards')), + ("off", _("All refunds are issued to the original payment method")), + ( + "option", + _( + "Customers can choose between a gift card and a refund to their payment method" + ), + ), + ("force", _("All refunds are issued as gift cards")), ], ), - 'form_class': forms.ChoiceField, - 'form_kwargs': dict( - label=_('Refund method'), + "form_class": forms.ChoiceField, + "form_kwargs": dict( + label=_("Refund method"), choices=[ - ('off', _('All refunds are issued to the original payment method')), - ('option', _('Customers can choose between a gift card and a refund to their payment method')), - ('force', _('All refunds are issued as gift cards')), + ("off", _("All refunds are issued to the original payment method")), + ( + "option", + _( + "Customers can choose between a gift card and a refund to their payment method" + ), + ), + ("force", _("All refunds are issued as gift cards")), ], widget=forms.RadioSelect, # When adding a new ordering, remember to also define it in the event model - ) - }, - 'cancel_allow_user_paid_until': { - 'default': None, - 'type': RelativeDateWrapper, - 'form_class': RelativeDateTimeField, - 'serializer_class': SerializerRelativeDateTimeField, - 'form_kwargs': dict( + ), + }, + "cancel_allow_user_paid_until": { + "default": None, + "type": RelativeDateWrapper, + "form_class": RelativeDateTimeField, + "serializer_class": SerializerRelativeDateTimeField, + "form_kwargs": dict( label=_("Do not allow cancellations after"), - ) - }, - 'contact_mail': { - 'default': None, - 'type': str, - 'serializer_class': serializers.EmailField, - 'form_class': forms.EmailField, - 'form_kwargs': dict( + ), + }, + "contact_mail": { + "default": None, + "type": str, + "serializer_class": serializers.EmailField, + "form_class": forms.EmailField, + "form_kwargs": dict( label=_("Contact address"), - help_text=_("We'll show this publicly to allow attendees to contact you.") - ) - }, - 'imprint_url': { - 'default': None, - 'type': str, - 'form_class': forms.URLField, - 'form_kwargs': dict( + help_text=_("We'll show this publicly to allow attendees to contact you."), + ), + }, + "imprint_url": { + "default": None, + "type": str, + "form_class": forms.URLField, + "form_kwargs": dict( label=_("Imprint URL"), - help_text=_("This should point e.g. to a part of your website that has your contact details and legal " - "information."), - ), - 'serializer_class': serializers.URLField, - }, - 'confirm_texts': { - 'default': LazyI18nStringListBase(), - 'type': LazyI18nStringListBase, - 'serializer_class': serializers.ListField, - 'serializer_kwargs': lambda: dict(child=I18nField()), - }, - 'mail_html_renderer': { - 'default': 'classic', - 'type': str - }, - 'mail_attach_tickets': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "This should point e.g. to a part of your website that has your contact details and legal " + "information." + ), + ), + "serializer_class": serializers.URLField, + }, + "confirm_texts": { + "default": LazyI18nStringListBase(), + "type": LazyI18nStringListBase, + "serializer_class": serializers.ListField, + "serializer_kwargs": lambda: dict(child=I18nField()), + }, + "mail_html_renderer": {"default": "classic", "type": str}, + "mail_attach_tickets": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Attach ticket files"), help_text=format_lazy( - _("Tickets will never be attached if they're larger than {size} to avoid email delivery problems."), - size='4 MB' + _( + "Tickets will never be attached if they're larger than {size} to avoid email delivery problems." + ), + size="4 MB", ), - ) + ), }, - 'mail_attach_ical': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "mail_attach_ical": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Attach calendar files"), - help_text=_("If enabled, we will attach an .ics calendar file to order confirmation emails."), - ) - }, - 'mail_prefix': { - 'default': None, - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( + help_text=_( + "If enabled, we will attach an .ics calendar file to order confirmation emails." + ), + ), + }, + "mail_prefix": { + "default": None, + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( label=_("Subject prefix"), - help_text=_("This will be prepended to the subject of all outgoing emails, formatted as [prefix]. " - "Choose, for example, a short form of your event name."), - ) - }, - 'mail_bcc': { - 'default': None, - 'type': str - }, - 'mail_from': { - 'default': settings.MAIL_FROM, - 'type': str, - 'form_class': forms.EmailField, - 'serializer_class': serializers.EmailField, - 'form_kwargs': dict( + help_text=_( + "This will be prepended to the subject of all outgoing emails, formatted as [prefix]. " + "Choose, for example, a short form of your event name." + ), + ), + }, + "mail_bcc": {"default": None, "type": str}, + "mail_from": { + "default": settings.MAIL_FROM, + "type": str, + "form_class": forms.EmailField, + "serializer_class": serializers.EmailField, + "form_kwargs": dict( label=_("Sender address"), help_text=_("Sender address for outgoing emails"), - ) - }, - 'mail_from_name': { - 'default': None, - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'form_kwargs': dict( - label=_("Sender name"), - help_text=_("Sender name used in conjunction with the sender address for outgoing emails. " - "Defaults to your event name."), - ) + ), }, - 'mail_sales_channel_placed_paid': { - 'default': ['web'], - 'type': list, + "mail_from_name": { + "default": None, + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "form_kwargs": dict( + label=_("Sender name"), + help_text=_( + "Sender name used in conjunction with the sender address for outgoing emails. " + "Defaults to your event name." + ), + ), }, - 'mail_sales_channel_download_reminder': { - 'default': ['web'], - 'type': list, + "mail_sales_channel_placed_paid": { + "default": ["web"], + "type": list, }, - 'mail_text_signature': { - 'type': LazyI18nString, - 'default': "" + "mail_sales_channel_download_reminder": { + "default": ["web"], + "type": list, }, - 'mail_text_resend_link': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_signature": {"type": LazyI18nString, "default": ""}, + "mail_text_resend_link": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, you receive this message because you asked us to send you the link to your order for {event}. @@ -1494,11 +1683,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_resend_all_links': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_resend_all_links": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, somebody requested a list of your orders for {event}. The list is as follows: @@ -1506,11 +1699,15 @@ def primary_font_kwargs(): {orders} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_free_attendee': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name}, + "mail_text_order_free_attendee": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {attendee_name}, you have been registered for {event} successfully. @@ -1518,11 +1715,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_free': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_free": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, your order for {event} was successful. As you only ordered free products, no payment is required. @@ -1531,15 +1732,16 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) - }, - 'mail_send_order_free_attendee': { - 'type': bool, - 'default': 'False' +Your {event} team""" + ) + ), }, - 'mail_text_order_placed_require_approval': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_send_order_free_attendee": {"type": bool, "default": "False"}, + "mail_text_order_placed_require_approval": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we successfully received your order for {event}. Since you ordered a product that requires approval by the event organizer, we ask you to @@ -1549,11 +1751,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_placed': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_placed": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we successfully received your order for {event} with a total value of {total_with_currency}. Please complete your payment before {expire_date}. @@ -1564,15 +1770,16 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) - }, - 'mail_send_order_placed_attendee': { - 'type': bool, - 'default': 'False' +Your {event} team""" + ) + ), }, - 'mail_text_order_placed_attendee': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name}, + "mail_send_order_placed_attendee": {"type": bool, "default": "False"}, + "mail_text_order_placed_attendee": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {attendee_name}, a ticket for {event} has been ordered for you. @@ -1580,11 +1787,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_changed': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_changed": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, your order for {event} has been changed. @@ -1592,11 +1803,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_paid': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_paid": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we successfully received your payment for {event}. Thank you! @@ -1606,15 +1821,16 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) - }, - 'mail_send_order_paid_attendee': { - 'type': bool, - 'default': 'False' +Your {event} team""" + ) + ), }, - 'mail_text_order_paid_attendee': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name}, + "mail_send_order_paid_attendee": {"type": bool, "default": "False"}, + "mail_text_order_paid_attendee": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {attendee_name}, a ticket for {event} that has been ordered for you is now paid. @@ -1622,15 +1838,16 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) - }, - 'mail_days_order_expire_warning': { - 'type': int, - 'default': '3' +Your {event} team""" + ) + ), }, - 'mail_text_order_expire_warning': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_days_order_expire_warning": {"type": int, "default": "3"}, + "mail_text_order_expire_warning": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we did not yet receive a full payment for your order for {event}. Please keep in mind that we only guarantee your order if we receive @@ -1640,11 +1857,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_waiting_list': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_waiting_list": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, you submitted yourself to the waiting list for {event}, for the product {product}. @@ -1663,11 +1884,15 @@ def primary_font_kwargs(): redeem the voucher within that timeframe. Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_canceled': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_canceled": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, your order {code} for {event} has been canceled. @@ -1675,11 +1900,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_approved': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_approved": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we approved your order for {event} and will be happy to welcome you at our event. @@ -1691,11 +1920,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_approved_free': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_approved_free": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, we approved your order for {event} and will be happy to welcome you at our event. As you only ordered free products, no payment is required. @@ -1704,11 +1937,15 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_denied': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_denied": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, unfortunately, we denied your order request for {event}. @@ -1719,29 +1956,31 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_text_order_custom_mail': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_order_custom_mail": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, You can change your order details and view the status of your order at {url} Best regards, -Your {event} team""")) - }, - 'mail_days_download_reminder': { - 'type': int, - 'default': None - }, - 'mail_send_download_reminder_attendee': { - 'type': bool, - 'default': 'False' +Your {event} team""" + ) + ), }, - 'mail_text_download_reminder_attendee': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {attendee_name}, + "mail_days_download_reminder": {"type": int, "default": None}, + "mail_send_download_reminder_attendee": {"type": bool, "default": "False"}, + "mail_text_download_reminder_attendee": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {attendee_name}, you are registered for {event}. @@ -1749,11 +1988,15 @@ def primary_font_kwargs(): {url} Best regards, - Your {event} team""")) + Your {event} team""" + ) + ), }, - 'mail_text_download_reminder': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + "mail_text_download_reminder": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello, you bought a ticket for {event}. @@ -1761,16 +2004,22 @@ def primary_font_kwargs(): {url} Best regards, -Your {event} team""")) +Your {event} team""" + ) + ), }, - 'mail_subject_customer_registration': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("Activate your account at {organizer}")), + "mail_subject_customer_registration": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop("Activate your account at {organizer}") + ), }, - 'mail_text_customer_registration': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name}, - + "mail_text_customer_registration": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {name}, + thank you for signing up for an account at {organizer}! To activate your account and set a password, please click here: @@ -1783,20 +2032,28 @@ def primary_font_kwargs(): Best regards, -Your {organizer} team""")) +Your {organizer} team""" + ) + ), }, - 'mail_subject_customer_email_change': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("Confirm email address for your account at {organizer}")), + "mail_subject_customer_email_change": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop("Confirm email address for your account at {organizer}") + ), }, - 'mail_subject_customer_reset': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("Set a new password for your account at {organizer}")), + "mail_subject_customer_reset": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop("Set a new password for your account at {organizer}") + ), }, - 'mail_text_customer_email_change': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name}, - + "mail_text_customer_email_change": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {name}, + you requested to change the email address of your account at {organizer}! To confirm the change, please click here: @@ -1809,12 +2066,16 @@ def primary_font_kwargs(): Best regards, -Your {organizer} team""")) +Your {organizer} team""" + ) + ), }, - 'mail_text_customer_reset': { - 'type': LazyI18nString, - 'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name}, - + "mail_text_customer_reset": { + "type": LazyI18nString, + "default": LazyI18nString.from_gettext( + gettext_noop( + """Hello {name}, + you requested a new password for your account at {organizer}! To set a new password, please click here: @@ -1827,890 +2088,972 @@ def primary_font_kwargs(): Best regards, -Your {organizer} team""")) - }, - 'smtp_use_custom': { - 'default': 'False', - 'type': bool - }, - 'smtp_host': { - 'default': '', - 'type': str - }, - 'smtp_port': { - 'default': 587, - 'type': int - }, - 'smtp_username': { - 'default': '', - 'type': str - }, - 'smtp_password': { - 'default': '', - 'type': str - }, - 'smtp_use_tls': { - 'default': 'True', - 'type': bool - }, - 'smtp_use_ssl': { - 'default': 'False', - 'type': bool +Your {organizer} team""" + ) + ), }, - 'primary_color': { - 'default': settings.PRETIX_PRIMARY_COLOR, - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'serializer_kwargs': dict( + "smtp_use_custom": {"default": "False", "type": bool}, + "smtp_host": {"default": "", "type": str}, + "smtp_port": {"default": 587, "type": int}, + "smtp_username": {"default": "", "type": str}, + "smtp_password": {"default": "", "type": str}, + "smtp_use_tls": {"default": "True", "type": bool}, + "smtp_use_ssl": {"default": "False", "type": bool}, + "primary_color": { + "default": settings.PRETIX_PRIMARY_COLOR, + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "serializer_kwargs": dict( validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Primary color"), validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], - widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + widget=forms.TextInput(attrs={"class": "colorpickerfield"}), ), }, - 'theme_color_success': { - 'default': '#50a167', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'serializer_kwargs': dict( + "theme_color_success": { + "default": "#50a167", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "serializer_kwargs": dict( validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Accent color for success"), help_text=_("We strongly suggest to use a shade of green."), validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], - widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + widget=forms.TextInput(attrs={"class": "colorpickerfield"}), ), }, - 'theme_color_danger': { - 'default': '#c44f4f', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'serializer_kwargs': dict( + "theme_color_danger": { + "default": "#c44f4f", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "serializer_kwargs": dict( validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Accent color for errors"), help_text=_("We strongly suggest to use a shade of red."), validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], - widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) + widget=forms.TextInput(attrs={"class": "colorpickerfield"}), ), }, - 'theme_color_background': { - 'default': '#f5f5f5', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'serializer_kwargs': dict( + "theme_color_background": { + "default": "#f5f5f5", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "serializer_kwargs": dict( validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Page background color"), validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], - widget=forms.TextInput(attrs={'class': 'colorpickerfield no-contrast'}) + widget=forms.TextInput(attrs={"class": "colorpickerfield no-contrast"}), ), }, - 'hover_button_color': { - 'default': '#2185d0', - 'type': str, - 'form_class': forms.CharField, - 'serializer_class': serializers.CharField, - 'serializer_kwargs': dict( + "hover_button_color": { + "default": "#2185d0", + "type": str, + "form_class": forms.CharField, + "serializer_class": serializers.CharField, + "serializer_kwargs": dict( validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], ), - 'form_kwargs': dict( + "form_kwargs": dict( label=_("Scroll-over color"), validators=[ - RegexValidator(regex='^#[0-9a-fA-F]{6}$', - message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), + RegexValidator( + regex="^#[0-9a-fA-F]{6}$", + message=_( + "Please enter the hexadecimal code of a color, e.g. #990000." + ), + ), ], - widget=forms.TextInput(attrs={'class': 'colorpickerfield no-contrast'}) + widget=forms.TextInput(attrs={"class": "colorpickerfield no-contrast"}), ), }, - 'theme_round_borders': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "theme_round_borders": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Use round edges"), - ) - }, - 'primary_font': { - 'default': 'Open Sans', - 'type': str, - 'form_class': forms.ChoiceField, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': lambda: dict(**primary_font_kwargs()), - 'form_kwargs': lambda: dict( - label=_('Font'), - help_text=_('Only respected by modern browsers.'), - widget=FontSelect, - **primary_font_kwargs() ), }, - 'presale_css_file': { - 'default': None, - 'type': str - }, - 'presale_css_checksum': { - 'default': None, - 'type': str - }, - 'presale_widget_css_file': { - 'default': None, - 'type': str - }, - 'presale_widget_css_checksum': { - 'default': None, - 'type': str + "primary_font": { + "default": "Open Sans", + "type": str, + "form_class": forms.ChoiceField, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": lambda: dict(**primary_font_kwargs()), + "form_kwargs": lambda: dict( + label=_("Font"), + help_text=_("Only respected by modern browsers."), + widget=FontSelect, + **primary_font_kwargs(), + ), }, - 'logo_image': { - 'default': None, - 'type': File, - 'form_class': ExtFileField, - 'form_kwargs': dict( - label=_('Header image'), + "presale_css_file": {"default": None, "type": str}, + "presale_css_checksum": {"default": None, "type": str}, + "presale_widget_css_file": {"default": None, "type": str}, + "presale_widget_css_checksum": {"default": None, "type": str}, + "logo_image": { + "default": None, + "type": File, + "form_class": ExtFileField, + "form_kwargs": dict( + label=_("Header image"), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), max_size=10 * 1024 * 1024, - help_text=_('If you provide a logo image, we will by default not show your event name and date ' - 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' - 'can increase the size with the setting below. We recommend not using small details on the picture ' - 'as it will be resized on smaller screens.') - ), - 'serializer_class': UploadedFileField, - 'serializer_kwargs': dict( - allowed_types=[ - 'image/png', 'image/jpeg', 'image/gif' - ], + help_text=_( + "If you provide a logo image, we will by default not show your event name and date " + "in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You " + "can increase the size with the setting below. We recommend not using small details on the picture " + "as it will be resized on smaller screens." + ), + ), + "serializer_class": UploadedFileField, + "serializer_kwargs": dict( + allowed_types=["image/png", "image/jpeg", "image/gif"], max_size=10 * 1024 * 1024, - ) - - }, - 'logo_image_large': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Use header image in its full size'), - help_text=_('We recommend to upload a picture at least 1170 pixels wide.'), - ) - }, - 'logo_show_title': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Show event title even if a header image is present'), - help_text=_('The title will only be shown on the event front page.'), - ) - }, - 'organizer_logo_image': { - 'default': None, - 'type': File, - 'form_class': ExtFileField, - 'form_kwargs': dict( - label=_('Header image'), + ), + }, + "logo_image_large": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Use header image in its full size"), + help_text=_("We recommend to upload a picture at least 1170 pixels wide."), + ), + }, + "logo_show_title": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Show event title even if a header image is present"), + help_text=_("The title will only be shown on the event front page."), + ), + }, + "organizer_logo_image": { + "default": None, + "type": File, + "form_class": ExtFileField, + "form_kwargs": dict( + label=_("Header image"), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), max_size=10 * 1024 * 1024, - help_text=_('If you provide a logo image, we will by default not show your organization name ' - 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' - 'can increase the size with the setting below. We recommend not using small details on the picture ' - 'as it will be resized on smaller screens.') - ), - 'serializer_class': UploadedFileField, - 'serializer_kwargs': dict( - allowed_types=[ - 'image/png', 'image/jpeg', 'image/gif' - ], + help_text=_( + "If you provide a logo image, we will by default not show your organization name " + "in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You " + "can increase the size with the setting below. We recommend not using small details on the picture " + "as it will be resized on smaller screens." + ), + ), + "serializer_class": UploadedFileField, + "serializer_kwargs": dict( + allowed_types=["image/png", "image/jpeg", "image/gif"], max_size=10 * 1024 * 1024, - ) - }, - 'organizer_logo_image_large': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Use header image in its full size'), - help_text=_('We recommend to upload a picture at least 1170 pixels wide.'), - ) - }, - 'og_image': { - 'default': None, - 'type': File, - 'form_class': ExtFileField, - 'form_kwargs': dict( - label=_('Social media image'), + ), + }, + "organizer_logo_image_large": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Use header image in its full size"), + help_text=_("We recommend to upload a picture at least 1170 pixels wide."), + ), + }, + "og_image": { + "default": None, + "type": File, + "form_class": ExtFileField, + "form_kwargs": dict( + label=_("Social media image"), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), max_size=10 * 1024 * 1024, - help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. ' - 'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like ' - 'WhatsApp and Reddit only show a square preview, so we recommend to make sure it still looks good ' - 'only the center square is shown. If you do not fill this, we will use the logo given above.') - ), - 'serializer_class': UploadedFileField, - 'serializer_kwargs': dict( - allowed_types=[ - 'image/png', 'image/jpeg', 'image/gif' - ], + help_text=_( + "This picture will be used as a preview if you post links to your ticket shop on social media. " + "Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like " + "WhatsApp and Reddit only show a square preview, so we recommend to make sure it still looks good " + "only the center square is shown. If you do not fill this, we will use the logo given above." + ), + ), + "serializer_class": UploadedFileField, + "serializer_kwargs": dict( + allowed_types=["image/png", "image/jpeg", "image/gif"], max_size=10 * 1024 * 1024, - ) - }, - 'invoice_logo_image': { - 'default': None, - 'type': File, - 'form_class': ExtFileField, - 'form_kwargs': dict( - label=_('Logo image'), + ), + }, + "invoice_logo_image": { + "default": None, + "type": File, + "form_class": ExtFileField, + "form_kwargs": dict( + label=_("Logo image"), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=10 * 1024 * 1024, - help_text=_('We will show your logo with a maximal height and width of 2.5 cm.') + help_text=_( + "We will show your logo with a maximal height and width of 2.5 cm." + ), ), - 'serializer_class': UploadedFileField, - 'serializer_kwargs': dict( - allowed_types=[ - 'image/png', 'image/jpeg', 'image/gif' - ], + "serializer_class": UploadedFileField, + "serializer_kwargs": dict( + allowed_types=["image/png", "image/jpeg", "image/gif"], max_size=10 * 1024 * 1024, - ) - }, - 'frontpage_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( - label=_("Frontpage text"), - widget=I18nTextarea - ) - }, - 'event_info_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( - label=_('Info text'), + ), + }, + "frontpage_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict(label=_("Frontpage text"), widget=I18nTextarea), + }, + "event_info_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( + label=_("Info text"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, + widget_kwargs={"attrs": {"rows": "2"}}, help_text=_( - 'Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.') - ) + "Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates." + ), + ), }, - 'banner_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + "banner_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Banner text (top)"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_("This text will be shown above every page of your shop. Please only use this for " - "very important messages.") - ) - }, - 'banner_text_bottom': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown above every page of your shop. Please only use this for " + "very important messages." + ), + ), + }, + "banner_text_bottom": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Banner text (bottom)"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_("This text will be shown below every page of your shop. Please only use this for " - "very important messages.") - ) - }, - 'voucher_explanation_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown below every page of your shop. Please only use this for " + "very important messages." + ), + ), + }, + "voucher_explanation_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Voucher explanation"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_("This text will be shown next to the input for a voucher code. You can use it e.g. to explain " - "how to obtain a voucher code.") - ) - }, - 'attendee_data_explanation_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown next to the input for a voucher code. You can use it e.g. to explain " + "how to obtain a voucher code." + ), + ), + }, + "attendee_data_explanation_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Attendee data explanation"), widget=I18nTextarea, - widget_kwargs={'attrs': {'rows': '2'}}, - help_text=_( - "This text will be shown above the questions asked for every admission product. You can use it e.g. to explain " - "why you need information from them.") - ) - }, - 'checkout_success_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + help_text=_( + "This text will be shown above the questions asked for every admission product. You can use it e.g. " + "to explain" + "why you need information from them." + ), + ), + }, + "checkout_success_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Additional success message"), help_text=_( - "This message will be shown after an order has been created successfully. It will be shown in additional " - "to the default text."), - widget_kwargs={'attrs': {'rows': '2'}}, - widget=I18nTextarea - ) - }, - 'checkout_phone_helptext': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + "This message will be shown after an order has been created successfully. It will be shown in " + "additional" + "to the default text." + ), + widget_kwargs={"attrs": {"rows": "2"}}, + widget=I18nTextarea, + ), + }, + "checkout_phone_helptext": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Help text of the phone number field"), - widget_kwargs={'attrs': {'rows': '2'}}, - widget=I18nTextarea - ) - }, - 'checkout_email_helptext': { - 'default': LazyI18nString.from_gettext(gettext_noop( - 'Make sure to enter a valid email address. We will send you an order ' - 'confirmation including a link that you need to access your order later.' - )), - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( + widget_kwargs={"attrs": {"rows": "2"}}, + widget=I18nTextarea, + ), + }, + "checkout_email_helptext": { + "default": LazyI18nString.from_gettext( + gettext_noop( + "Make sure to enter a valid email address. We will send you an order " + "confirmation including a link that you need to access your order later." + ) + ), + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( label=_("Help text of the email field"), - widget_kwargs={'attrs': {'rows': '2'}}, - widget=I18nTextarea - ) - }, - 'order_import_settings': { - 'default': '{}', - 'type': dict - }, - 'organizer_info_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( - label=_('Info text'), + widget_kwargs={"attrs": {"rows": "2"}}, + widget=I18nTextarea, + ), + }, + "order_import_settings": {"default": "{}", "type": dict}, + "organizer_info_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( + label=_("Info text"), widget=I18nTextarea, help_text=_( - 'Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.') - ) - }, - 'event_team_provisioning': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Allow creating a new team during event creation'), - help_text=_( - 'Users that do not have access to all events under this organizer, must select one of their teams ' - 'to have access to the created event. This setting allows users to create an event-specified team' - ' on-the-fly, even when they do not have \"Can change teams and permissions\" permission.'), - ) - }, - 'update_check_ack': { - 'default': 'False', - 'type': bool - }, - 'update_check_email': { - 'default': '', - 'type': str - }, - # here is the default setting for the updates check - 'update_check_perform': { - 'default': 'False', - 'type': bool - }, - 'update_check_result': { - 'default': None, - 'type': dict - }, - 'update_check_result_warning': { - 'default': 'False', - 'type': bool - }, - 'update_check_last': { - 'default': None, - 'type': datetime - }, - 'update_check_id': { - 'default': None, - 'type': str - }, - 'banner_message': { - 'default': '', - 'type': LazyI18nString - }, - 'banner_message_detail': { - 'default': '', - 'type': LazyI18nString - }, - 'opencagedata_apikey': { - 'default': None, - 'type': str - }, - 'mapquest_apikey': { - 'default': None, - 'type': str - }, - 'leaflet_tiles': { - 'default': None, - 'type': str - }, - 'leaflet_tiles_attribution': { - 'default': None, - 'type': str - }, - 'frontpage_subevent_ordering': { - 'default': 'date_ascending', - 'type': str, - 'serializer_class': serializers.ChoiceField, - 'serializer_kwargs': dict( + "Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates." + ), + ), + }, + "event_team_provisioning": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Allow creating a new team during event creation"), + help_text=_( + "Users that do not have access to all events under this organizer, must select one of their teams " + "to have access to the created event. This setting allows users to create an event-specified team" + ' on-the-fly, even when they do not have "Can change teams and permissions" permission.' + ), + ), + }, + "update_check_ack": {"default": "False", "type": bool}, + "update_check_email": {"default": "", "type": str}, + # here is the default setting for the updates check + "update_check_perform": {"default": "False", "type": bool}, + "update_check_result": {"default": None, "type": dict}, + "update_check_result_warning": {"default": "False", "type": bool}, + "update_check_last": {"default": None, "type": datetime}, + "update_check_id": {"default": None, "type": str}, + "banner_message": {"default": "", "type": LazyI18nString}, + "banner_message_detail": {"default": "", "type": LazyI18nString}, + "opencagedata_apikey": {"default": None, "type": str}, + "mapquest_apikey": {"default": None, "type": str}, + "leaflet_tiles": {"default": None, "type": str}, + "leaflet_tiles_attribution": {"default": None, "type": str}, + "frontpage_subevent_ordering": { + "default": "date_ascending", + "type": str, + "serializer_class": serializers.ChoiceField, + "serializer_kwargs": dict( choices=[ - ('date_ascending', _('Event start time')), - ('date_descending', _('Event start time (descending)')), - ('name_ascending', _('Name')), - ('name_descending', _('Name (descending)')), + ("date_ascending", _("Event start time")), + ("date_descending", _("Event start time (descending)")), + ("name_ascending", _("Name")), + ("name_descending", _("Name (descending)")), ], ), - 'form_class': forms.ChoiceField, - 'form_kwargs': dict( - label=pgettext('subevent', 'Date ordering'), + "form_class": forms.ChoiceField, + "form_kwargs": dict( + label=pgettext("subevent", "Date ordering"), choices=[ - ('date_ascending', _('Event start time')), - ('date_descending', _('Event start time (descending)')), - ('name_ascending', _('Name')), - ('name_descending', _('Name (descending)')), + ("date_ascending", _("Event start time")), + ("date_descending", _("Event start time (descending)")), + ("name_ascending", _("Name")), + ("name_descending", _("Name (descending)")), ], # When adding a new ordering, remember to also define it in the event model - ) - }, - 'organizer_link_back': { - 'default': 'False', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( - label=_('Link back to organizer overview on all event pages'), - ) - }, - 'organizer_homepage_text': { - 'default': '', - 'type': LazyI18nString, - 'serializer_class': I18nField, - 'form_class': I18nFormField, - 'form_kwargs': dict( - label=_('Homepage text'), + ), + }, + "organizer_link_back": { + "default": "False", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( + label=_("Link back to organizer overview on all event pages"), + ), + }, + "organizer_homepage_text": { + "default": "", + "type": LazyI18nString, + "serializer_class": I18nField, + "form_class": I18nFormField, + "form_kwargs": dict( + label=_("Homepage text"), widget=I18nTextarea, - help_text=_('This will be displayed on the organizer homepage.') - ) - }, - 'name_scheme': { - 'default': 'full', - 'type': str - }, - 'giftcard_length': { - 'default': settings.ENTROPY['giftcard_secret'], - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( - label=_('Length of gift card codes'), - help_text=_( - 'The system generates by default {}-character long gift card codes. However, if a different length ' - 'is required, it can be set here.'.format(settings.ENTROPY['giftcard_secret'])), - ) - }, - 'giftcard_expiry_years': { - 'default': None, - 'type': int, - 'form_class': forms.IntegerField, - 'serializer_class': serializers.IntegerField, - 'form_kwargs': dict( - label=_('Validity of gift card codes in years'), - help_text=_('If you set a number here, gift cards will by default expire at the end of the year after this ' - 'many years. If you keep it empty, gift cards do not have an explicit expiry date.'), - ) - }, - 'privacy_policy': { - 'default': None, - 'type': LazyI18nString, - 'form_class': I18nURLFormField, - 'form_kwargs': dict( + help_text=_("This will be displayed on the organizer homepage."), + ), + }, + "name_scheme": {"default": "full", "type": str}, + "giftcard_length": { + "default": settings.ENTROPY["giftcard_secret"], + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( + label=_("Length of gift card codes"), + help_text=_( + "The system generates by default {}-character long gift card codes. However, if a different length " + "is required, it can be set here.".format( + settings.ENTROPY["giftcard_secret"] + ) + ), + ), + }, + "giftcard_expiry_years": { + "default": None, + "type": int, + "form_class": forms.IntegerField, + "serializer_class": serializers.IntegerField, + "form_kwargs": dict( + label=_("Validity of gift card codes in years"), + help_text=_( + "If you set a number here, gift cards will by default expire at the end of the year after this " + "many years. If you keep it empty, gift cards do not have an explicit expiry date." + ), + ), + }, + "privacy_policy": { + "default": None, + "type": LazyI18nString, + "form_class": I18nURLFormField, + "form_kwargs": dict( label=_("Privacy Policy URL"), - help_text=_("This should link to a section of your website that explains " - "how you use the data gathered in your ticket shop."), + help_text=_( + "This should link to a section of your website that explains " + "how you use the data gathered in your ticket shop." + ), widget=I18nTextInput, ), - 'serializer_class': I18nURLField, - }, - 'schedule_link': { - 'default': None, - 'type': str, - 'form_class': forms.URLField, - 'serializer_class': serializers.URLField, - 'form_kwargs': dict( + "serializer_class": I18nURLField, + }, + "schedule_link": { + "default": None, + "type": str, + "form_class": forms.URLField, + "serializer_class": serializers.URLField, + "form_kwargs": dict( label=_("Schedule URL"), help_text=_("This should point to your session schedule."), ), }, - 'session_link': { - 'default': None, - 'type': str, - 'form_class': forms.URLField, - 'serializer_class': serializers.URLField, - 'form_kwargs': dict( + "session_link": { + "default": None, + "type": str, + "form_class": forms.URLField, + "serializer_class": serializers.URLField, + "form_kwargs": dict( label=_("Session URL"), help_text=_("This should point to your session list."), ), }, - 'speaker_link': { - 'default': None, - 'type': str, - 'form_class': forms.URLField, - 'serializer_class': serializers.URLField, - 'form_kwargs': dict( + "speaker_link": { + "default": None, + "type": str, + "form_class": forms.URLField, + "serializer_class": serializers.URLField, + "form_kwargs": dict( label=_("Speaker URL"), help_text=_("This should point to your speakers."), ), }, - 'seating_choice': { - 'default': 'True', - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + "seating_choice": { + "default": "True", + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Customers can choose their own seats"), - help_text=_("If disabled, you will need to manually assign seats in the backend. Note that this can mean " - "people will not know their seat after their purchase and it might not be written on their " - "ticket."), - ), - 'type': bool, - }, - 'seating_minimal_distance': { - 'default': '0', - 'type': float - }, - 'seating_allow_blocked_seats_for_channel': { - 'default': [], - 'type': list - }, - 'seating_distance_within_row': { - 'default': 'False', - 'type': bool - }, - 'checkout_show_copy_answers_button': { - 'default': 'True', - 'type': bool, - 'form_class': forms.BooleanField, - 'serializer_class': serializers.BooleanField, - 'form_kwargs': dict( + help_text=_( + "If disabled, you will need to manually assign seats in the backend. Note that this can mean " + "people will not know their seat after their purchase and it might not be written on their " + "ticket." + ), + ), + "type": bool, + }, + "seating_minimal_distance": {"default": "0", "type": float}, + "seating_allow_blocked_seats_for_channel": {"default": [], "type": list}, + "seating_distance_within_row": {"default": "False", "type": bool}, + "checkout_show_copy_answers_button": { + "default": "True", + "type": bool, + "form_class": forms.BooleanField, + "serializer_class": serializers.BooleanField, + "form_kwargs": dict( label=_("Show button to copy user input from other products"), ), - } + }, } CSS_SETTINGS = { - 'primary_color', 'theme_color_success', 'theme_color_danger', 'primary_font', - 'theme_color_background', 'theme_round_borders', 'hover_button_color' + "primary_color", + "theme_color_success", + "theme_color_danger", + "primary_font", + "theme_color_background", + "theme_round_borders", + "hover_button_color", } -TITLE_GROUP = OrderedDict([ - ('english_common', (_('Most common English titles'), ( - 'Mr', - 'Ms', - 'Mrs', - 'Miss', - 'Mx', - 'Dr', - 'Professor', - 'Sir', - ))), - ('german_common', (_('Most common German titles'), ( - 'Dr.', - 'Prof.', - 'Prof. Dr.', - ))) -]) +TITLE_GROUP = OrderedDict( + [ + ( + "english_common", + ( + _("Most common English titles"), + ( + "Mr", + "Ms", + "Mrs", + "Miss", + "Mx", + "Dr", + "Professor", + "Sir", + ), + ), + ), + ( + "german_common", + ( + _("Most common German titles"), + ( + "Dr.", + "Prof.", + "Prof. Dr.", + ), + ), + ), + ] +) NAME_SALUTION = [ pgettext_lazy("person_name_salutation", "Ms"), pgettext_lazy("person_name_salutation", "Mr"), ] -NAME_SCHEMES = OrderedDict([ - ('given_family', { - 'fields': ( - # field_name, label, weight for widget width - ('given_name', _('Given name'), 1), - ('family_name', _('Family name'), 1), - ), - 'concatenation': lambda d: ' '.join(str(p) for p in [d.get('given_name', ''), d.get('family_name', '')] if p), - 'sample': { - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'given_family', - }, - }), - ('title_given_family', { - 'fields': ( - ('title', pgettext_lazy('person_name', 'Title'), 1), - ('given_name', _('Given name'), 2), - ('family_name', _('Family name'), 2), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in [d.get('title', ''), d.get('given_name', ''), d.get('family_name', '')] if p - ), - 'sample': { - 'title': pgettext_lazy('person_name_sample', 'Dr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'title_given_family', - }, - }), - ('title_given_family', { - 'fields': ( - ('title', pgettext_lazy('person_name', 'Title'), 1), - ('given_name', _('Given name'), 2), - ('family_name', _('Family name'), 2), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in [d.get('title', ''), d.get('given_name', ''), d.get('family_name', '')] if p - ), - 'sample': { - 'title': pgettext_lazy('person_name_sample', 'Dr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'title_given_family', - }, - }), - ('given_middle_family', { - 'fields': ( - ('given_name', _('First name'), 2), - ('middle_name', _('Middle name'), 1), - ('family_name', _('Family name'), 2), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in [d.get('given_name', ''), d.get('middle_name', ''), d.get('family_name', '')] if p - ), - 'sample': { - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'middle_name': 'M', - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'given_middle_family', - }, - }), - ('title_given_middle_family', { - 'fields': ( - ('title', pgettext_lazy('person_name', 'Title'), 1), - ('given_name', _('First name'), 2), - ('middle_name', _('Middle name'), 1), - ('family_name', _('Family name'), 1), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in [d.get('title', ''), d.get('given_name'), d.get('middle_name'), d.get('family_name')] if p - ), - 'sample': { - 'title': pgettext_lazy('person_name_sample', 'Dr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'middle_name': 'M', - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'title_given_middle_family', - }, - }), - ('family_given', { - 'fields': ( - ('family_name', _('Family name'), 1), - ('given_name', _('Given name'), 1), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in [d.get('family_name', ''), d.get('given_name', '')] if p - ), - 'sample': { - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'family_given', - }, - }), - ('family_nospace_given', { - 'fields': ( - ('given_name', _('Given name'), 1), - ('family_name', _('Family name'), 1), - ), - 'concatenation': lambda d: ''.join( - str(p) for p in [d.get('family_name', ''), d.get('given_name', '')] if p - ), - 'sample': { - 'given_name': '泽东', - 'family_name': '毛', - '_scheme': 'family_nospace_given', - }, - }), - ('family_comma_given', { - 'fields': ( - ('given_name', _('Given name'), 1), - ('family_name', _('Family name'), 1), - ), - 'concatenation': lambda d: ( - str(d.get('family_name', '')) + - str((', ' if d.get('family_name') and d.get('given_name') else '')) + - str(d.get('given_name', '')) - ), - 'sample': { - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'family_comma_given', - }, - }), - ('full', { - 'fields': ( - ('full_name', _('Name'), 1), - ), - 'concatenation': lambda d: str(d.get('full_name', '')), - 'sample': { - 'full_name': pgettext_lazy('person_name_sample', 'John Doe'), - '_scheme': 'full', - }, - }), - ('calling_full', { - 'fields': ( - ('calling_name', _('Calling name'), 1), - ('full_name', _('Full name'), 2), - ), - 'concatenation': lambda d: str(d.get('full_name', '')), - 'sample': { - 'full_name': pgettext_lazy('person_name_sample', 'John Doe'), - 'calling_name': pgettext_lazy('person_name_sample', 'John'), - '_scheme': 'calling_full', - }, - }), - ('full_transcription', { - 'fields': ( - ('full_name', _('Full name'), 1), - ('latin_transcription', _('Latin transcription'), 2), - ), - 'concatenation': lambda d: str(d.get('full_name', '')), - 'sample': { - 'full_name': '庄司', - 'latin_transcription': 'Shōji', - '_scheme': 'full_transcription', - }, - }), - ('salutation_given_family', { - 'fields': ( - ('salutation', pgettext_lazy('person_name', 'Salutation'), 1), - ('given_name', _('Given name'), 2), - ('family_name', _('Family name'), 2), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in (d.get(key, '') for key in ["given_name", "family_name"]) if p - ), - 'sample': { - 'salutation': pgettext_lazy('person_name_sample', 'Mr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'salutation_given_family', - }, - }), - ('salutation_title_given_family', { - 'fields': ( - ('salutation', pgettext_lazy('person_name', 'Salutation'), 1), - ('title', pgettext_lazy('person_name', 'Title'), 1), - ('given_name', _('Given name'), 2), - ('family_name', _('Family name'), 2), - ), - 'concatenation': lambda d: ' '.join( - str(p) for p in (d.get(key, '') for key in ["title", "given_name", "family_name"]) if p - ), - 'sample': { - 'salutation': pgettext_lazy('person_name_sample', 'Mr'), - 'title': pgettext_lazy('person_name_sample', 'Dr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - '_scheme': 'salutation_title_given_family', - }, - }), - ('salutation_title_given_family_degree', { - 'fields': ( - ('salutation', pgettext_lazy('person_name', 'Salutation'), 1), - ('title', pgettext_lazy('person_name', 'Title'), 1), - ('given_name', _('Given name'), 2), - ('family_name', _('Family name'), 2), - ('degree', pgettext_lazy('person_name', 'Degree (after name)'), 2), - ), - 'concatenation': lambda d: ( - ' '.join( - str(p) for p in (d.get(key, '') for key in ["title", "given_name", "family_name"]) if p - ) + - str((', ' if d.get('degree') else '')) + - str(d.get('degree', '')) - ), - 'sample': { - 'salutation': pgettext_lazy('person_name_sample', 'Mr'), - 'title': pgettext_lazy('person_name_sample', 'Dr'), - 'given_name': pgettext_lazy('person_name_sample', 'John'), - 'family_name': pgettext_lazy('person_name_sample', 'Doe'), - 'degree': pgettext_lazy('person_name_sample', 'MA'), - '_scheme': 'salutation_title_given_family_degree', - }, - }), -]) +NAME_SCHEMES = OrderedDict( + [ + ( + "given_family", + { + "fields": ( + # field_name, label, weight for widget width + ("given_name", _("Given name"), 1), + ("family_name", _("Family name"), 1), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [d.get("given_name", ""), d.get("family_name", "")] + if p + ), + "sample": { + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "given_family", + }, + }, + ), + ( + "title_given_family", + { + "fields": ( + ("title", pgettext_lazy("person_name", "Title"), 1), + ("given_name", _("Given name"), 2), + ("family_name", _("Family name"), 2), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [ + d.get("title", ""), + d.get("given_name", ""), + d.get("family_name", ""), + ] + if p + ), + "sample": { + "title": pgettext_lazy("person_name_sample", "Dr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "title_given_family", + }, + }, + ), + ( + "title_given_family", + { + "fields": ( + ("title", pgettext_lazy("person_name", "Title"), 1), + ("given_name", _("Given name"), 2), + ("family_name", _("Family name"), 2), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [ + d.get("title", ""), + d.get("given_name", ""), + d.get("family_name", ""), + ] + if p + ), + "sample": { + "title": pgettext_lazy("person_name_sample", "Dr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "title_given_family", + }, + }, + ), + ( + "given_middle_family", + { + "fields": ( + ("given_name", _("First name"), 2), + ("middle_name", _("Middle name"), 1), + ("family_name", _("Family name"), 2), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [ + d.get("given_name", ""), + d.get("middle_name", ""), + d.get("family_name", ""), + ] + if p + ), + "sample": { + "given_name": pgettext_lazy("person_name_sample", "John"), + "middle_name": "M", + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "given_middle_family", + }, + }, + ), + ( + "title_given_middle_family", + { + "fields": ( + ("title", pgettext_lazy("person_name", "Title"), 1), + ("given_name", _("First name"), 2), + ("middle_name", _("Middle name"), 1), + ("family_name", _("Family name"), 1), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [ + d.get("title", ""), + d.get("given_name"), + d.get("middle_name"), + d.get("family_name"), + ] + if p + ), + "sample": { + "title": pgettext_lazy("person_name_sample", "Dr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "middle_name": "M", + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "title_given_middle_family", + }, + }, + ), + ( + "family_given", + { + "fields": ( + ("family_name", _("Family name"), 1), + ("given_name", _("Given name"), 1), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in [d.get("family_name", ""), d.get("given_name", "")] + if p + ), + "sample": { + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "family_given", + }, + }, + ), + ( + "family_nospace_given", + { + "fields": ( + ("given_name", _("Given name"), 1), + ("family_name", _("Family name"), 1), + ), + "concatenation": lambda d: "".join( + str(p) + for p in [d.get("family_name", ""), d.get("given_name", "")] + if p + ), + "sample": { + "given_name": "泽东", + "family_name": "毛", + "_scheme": "family_nospace_given", + }, + }, + ), + ( + "family_comma_given", + { + "fields": ( + ("given_name", _("Given name"), 1), + ("family_name", _("Family name"), 1), + ), + "concatenation": lambda d: ( + str(d.get("family_name", "")) + + str( + (", " if d.get("family_name") and d.get("given_name") else "") + ) + + str(d.get("given_name", "")) + ), + "sample": { + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "family_comma_given", + }, + }, + ), + ( + "full", + { + "fields": (("full_name", _("Name"), 1),), + "concatenation": lambda d: str(d.get("full_name", "")), + "sample": { + "full_name": pgettext_lazy("person_name_sample", "John Doe"), + "_scheme": "full", + }, + }, + ), + ( + "calling_full", + { + "fields": ( + ("calling_name", _("Calling name"), 1), + ("full_name", _("Full name"), 2), + ), + "concatenation": lambda d: str(d.get("full_name", "")), + "sample": { + "full_name": pgettext_lazy("person_name_sample", "John Doe"), + "calling_name": pgettext_lazy("person_name_sample", "John"), + "_scheme": "calling_full", + }, + }, + ), + ( + "full_transcription", + { + "fields": ( + ("full_name", _("Full name"), 1), + ("latin_transcription", _("Latin transcription"), 2), + ), + "concatenation": lambda d: str(d.get("full_name", "")), + "sample": { + "full_name": "庄司", + "latin_transcription": "Shōji", + "_scheme": "full_transcription", + }, + }, + ), + ( + "salutation_given_family", + { + "fields": ( + ("salutation", pgettext_lazy("person_name", "Salutation"), 1), + ("given_name", _("Given name"), 2), + ("family_name", _("Family name"), 2), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in (d.get(key, "") for key in ["given_name", "family_name"]) + if p + ), + "sample": { + "salutation": pgettext_lazy("person_name_sample", "Mr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "salutation_given_family", + }, + }, + ), + ( + "salutation_title_given_family", + { + "fields": ( + ("salutation", pgettext_lazy("person_name", "Salutation"), 1), + ("title", pgettext_lazy("person_name", "Title"), 1), + ("given_name", _("Given name"), 2), + ("family_name", _("Family name"), 2), + ), + "concatenation": lambda d: " ".join( + str(p) + for p in ( + d.get(key, "") for key in ["title", "given_name", "family_name"] + ) + if p + ), + "sample": { + "salutation": pgettext_lazy("person_name_sample", "Mr"), + "title": pgettext_lazy("person_name_sample", "Dr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "_scheme": "salutation_title_given_family", + }, + }, + ), + ( + "salutation_title_given_family_degree", + { + "fields": ( + ("salutation", pgettext_lazy("person_name", "Salutation"), 1), + ("title", pgettext_lazy("person_name", "Title"), 1), + ("given_name", _("Given name"), 2), + ("family_name", _("Family name"), 2), + ("degree", pgettext_lazy("person_name", "Degree (after name)"), 2), + ), + "concatenation": lambda d: ( + " ".join( + str(p) + for p in ( + d.get(key, "") + for key in ["title", "given_name", "family_name"] + ) + if p + ) + + str((", " if d.get("degree") else "")) + + str(d.get("degree", "")) + ), + "sample": { + "salutation": pgettext_lazy("person_name_sample", "Mr"), + "title": pgettext_lazy("person_name_sample", "Dr"), + "given_name": pgettext_lazy("person_name_sample", "John"), + "family_name": pgettext_lazy("person_name_sample", "Doe"), + "degree": pgettext_lazy("person_name_sample", "MA"), + "_scheme": "salutation_title_given_family_degree", + }, + }, + ), + ] +) COUNTRIES_WITH_STATE = { # Source: http://www.bitboost.com/ref/international-address-formats.html # This is not a list of countries that *have* states, this is a list of countries where states # are actually *used* in postal addresses. This is obviously not complete and opinionated. # Country: [(List of subdivision types as defined by pycountry), (short or long form to be used)] - 'AU': (['State', 'Territory'], 'short'), - 'BR': (['State'], 'short'), - 'CA': (['Province', 'Territory'], 'short'), + "AU": (["State", "Territory"], "short"), + "BR": (["State"], "short"), + "CA": (["Province", "Territory"], "short"), # 'CN': (['Province', 'Autonomous region', 'Munincipality'], 'long'), - 'MY': (['State'], 'long'), - 'MX': (['State', 'Federal District'], 'short'), - 'US': (['State', 'Outlying area', 'District'], 'short'), + "MY": (["State"], "long"), + "MX": (["State", "Federal District"], "short"), + "US": (["State", "Outlying area", "District"], "short"), } diff --git a/src/pretix/base/configurations/lazy_i18n_string_list_base.py b/src/pretix/base/configurations/lazy_i18n_string_list_base.py index 815557e46..bd19001d3 100644 --- a/src/pretix/base/configurations/lazy_i18n_string_list_base.py +++ b/src/pretix/base/configurations/lazy_i18n_string_list_base.py @@ -1,5 +1,6 @@ -from collections import UserList import json +from collections import UserList + from i18nfield.strings import LazyI18nString diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index 684673c15..ce2347b6a 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -7,6 +7,7 @@ from itertools import groupby from smtplib import SMTPResponseException +from css_inline import inline as inline_css from django.conf import settings from django.core.mail.backends.smtp import EmailBackend from django.db.models import Count @@ -14,7 +15,8 @@ from django.template.loader import get_template from django.utils.timezone import now from django.utils.translation import get_language, gettext_lazy as _ -from css_inline import inline as inline_css +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Attachment, Bcc, Mail from pretix.base.i18n import ( LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber, @@ -25,15 +27,13 @@ register_html_mail_renderers, register_mail_placeholders, ) from pretix.base.templatetags.rich_text import markdown_compile_email -import os -from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Mail, Attachment, Bcc logger = logging.getLogger('pretix.base.email') class SendGridEmail(): api_key = "" + def __init__(self, api_key): self.api_key = api_key @@ -49,9 +49,9 @@ def test(self, from_addr): def bytes_to_base64_string(self, value: bytes) -> str: import base64 return base64.b64encode(value).decode('ASCII') - + def build_attachment(self, input): - + attachment = Attachment() attachment.file_content = self.bytes_to_base64_string(input[1]) attachment.file_type = input[2] @@ -60,7 +60,6 @@ def build_attachment(self, input): # attachment.content_id = "Balance Sheet" return attachment - def send_messages(self, emails): for email in emails: html_content = None @@ -377,8 +376,9 @@ def get_best_name(position_or_address, parts=False): @receiver(register_mail_placeholders, dispatch_uid="pretixbase_register_mail_placeholders") def base_placeholders(sender, **kwargs): - from pretix.multidomain.urlreverse import build_absolute_uri - from pretix.multidomain.urlreverse import build_join_video_url + from pretix.multidomain.urlreverse import ( + build_absolute_uri, build_join_video_url, + ) ph = [ SimpleFunctionalMailTextPlaceholder( diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 90a8839f9..49fb71dfc 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -57,33 +57,44 @@ logger = logging.getLogger(__name__) - -REQUIRED_NAME_PARTS = ['salutation', 'given_name', 'family_name', 'full_name'] +REQUIRED_NAME_PARTS = ["salutation", "given_name", "family_name", "full_name"] class NamePartsWidget(forms.MultiWidget): widget = forms.TextInput autofill_map = { - 'given_name': 'given-name', - 'family_name': 'family-name', - 'middle_name': 'additional-name', - 'title': 'honorific-prefix', - 'full_name': 'name', - 'calling_name': 'nickname', + "given_name": "given-name", + "family_name": "family-name", + "middle_name": "additional-name", + "title": "honorific-prefix", + "full_name": "name", + "calling_name": "nickname", } - def __init__(self, scheme: dict, field: forms.Field, attrs=None, titles: list=None): + def __init__( + self, scheme: dict, field: forms.Field, attrs=None, titles: list = None + ): widgets = [] self.scheme = scheme self.field = field self.titles = titles - for fname, label, size in self.scheme['fields']: + for fname, label, size in self.scheme["fields"]: a = copy.copy(attrs) or {} - a['data-fname'] = fname - if fname == 'title' and self.titles: - widgets.append(Select(attrs=a, choices=[('', '')] + [(d, d) for d in self.titles[1]])) - elif fname == 'salutation': - widgets.append(Select(attrs=a, choices=[('', '---')] + [(s, s) for s in PERSON_NAME_SALUTATIONS])) + a["data-fname"] = fname + if fname == "title" and self.titles: + widgets.append( + Select( + attrs=a, choices=[("", "")] + [(d, d) for d in self.titles[1]] + ) + ) + elif fname == "salutation": + widgets.append( + Select( + attrs=a, + choices=[("", "---")] + + [(s, s) for s in PERSON_NAME_SALUTATIONS], + ) + ) else: widgets.append(self.widget(attrs=a)) super().__init__(widgets, attrs) @@ -92,11 +103,11 @@ def decompress(self, value): if value is None: return None data = [] - for i, field in enumerate(self.scheme['fields']): + for i, field in enumerate(self.scheme["fields"]): fname, label, size = field data.append(value.get(fname, "")) - if '_legacy' in value and not data[-1]: - data[-1] = value.get('_legacy', '') + if "_legacy" in value and not data[-1]: + data[-1] = value.get("_legacy", "") return data def render(self, name: str, value, attrs=None, renderer=None) -> str: @@ -104,9 +115,9 @@ def render(self, name: str, value, attrs=None, renderer=None) -> str: value = self.decompress(value) output = [] final_attrs = self.build_attrs(attrs or {}) - if 'required' in final_attrs: - del final_attrs['required'] - id_ = final_attrs.get('id', None) + if "required" in final_attrs: + del final_attrs["required"] + id_ = final_attrs.get("id", None) for i, widget in enumerate(self.widgets): try: widget_value = value[i] @@ -115,25 +126,33 @@ def render(self, name: str, value, attrs=None, renderer=None) -> str: if id_: these_attrs = dict( final_attrs, - id='%s_%s' % (id_, i), - title=self.scheme['fields'][i][1], - placeholder=self.scheme['fields'][i][1], + id="%s_%s" % (id_, i), + title=self.scheme["fields"][i][1], + placeholder=self.scheme["fields"][i][1], ) - if self.scheme['fields'][i][0] in REQUIRED_NAME_PARTS: + if self.scheme["fields"][i][0] in REQUIRED_NAME_PARTS: if self.field.required: - these_attrs['required'] = 'required' - these_attrs.pop('data-no-required-attr', None) - these_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip() - these_attrs['data-size'] = self.scheme['fields'][i][2] + these_attrs["required"] = "required" + these_attrs.pop("data-no-required-attr", None) + these_attrs["autocomplete"] = ( + self.attrs.get("autocomplete", "") + + " " + + self.autofill_map.get(self.scheme["fields"][i][0], "off") + ).strip() + these_attrs["data-size"] = self.scheme["fields"][i][2] if len(self.widgets) > 1: - these_attrs['aria-label'] = self.scheme['fields'][i][1] + these_attrs["aria-label"] = self.scheme["fields"][i][1] else: these_attrs = final_attrs - output.append(widget.render(name + '_%s' % i, widget_value, these_attrs, renderer=renderer)) + output.append( + widget.render( + name + "_%s" % i, widget_value, these_attrs, renderer=renderer + ) + ) return mark_safe(self.format_output(output)) def format_output(self, rendered_widgets) -> str: - return '
%s
' % ''.join(rendered_widgets) + return '
%s
' % "".join(rendered_widgets) class NamePartsFormField(forms.MultiValueField): @@ -141,71 +160,79 @@ class NamePartsFormField(forms.MultiValueField): def compress(self, data_list) -> dict: data = {} - data['_scheme'] = self.scheme_name + data["_scheme"] = self.scheme_name for i, value in enumerate(data_list): - data[self.scheme['fields'][i][0]] = value or '' + data[self.scheme["fields"][i][0]] = value or "" return data def __init__(self, *args, **kwargs): fields = [] defaults = { - 'widget': self.widget, - 'max_length': kwargs.pop('max_length', None), + "widget": self.widget, + "max_length": kwargs.pop("max_length", None), } - self.scheme_name = kwargs.pop('scheme') - self.titles = kwargs.pop('titles') + self.scheme_name = kwargs.pop("scheme") + self.titles = kwargs.pop("titles") self.scheme = PERSON_NAME_SCHEMES.get(self.scheme_name) if self.titles: self.scheme_titles = PERSON_NAME_TITLE_GROUPS.get(self.titles) else: self.scheme_titles = None - self.one_required = kwargs.get('required', True) - require_all_fields = kwargs.pop('require_all_fields', False) - kwargs['required'] = False - kwargs['widget'] = (kwargs.get('widget') or self.widget)( - scheme=self.scheme, titles=self.scheme_titles, field=self, **kwargs.pop('widget_kwargs', {}) + self.one_required = kwargs.get("required", True) + require_all_fields = kwargs.pop("require_all_fields", False) + kwargs["required"] = False + kwargs["widget"] = (kwargs.get("widget") or self.widget)( + scheme=self.scheme, + titles=self.scheme_titles, + field=self, + **kwargs.pop("widget_kwargs", {}), ) defaults.update(**kwargs) - for fname, label, size in self.scheme['fields']: - defaults['label'] = label - if fname == 'title' and self.scheme_titles: + for fname, label, size in self.scheme["fields"]: + defaults["label"] = label + if fname == "title" and self.scheme_titles: d = dict(defaults) - d.pop('max_length', None) + d.pop("max_length", None) field = forms.ChoiceField( - **d, - choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]] + **d, choices=[("", "")] + [(d, d) for d in self.scheme_titles[1]] ) - elif fname == 'salutation': + elif fname == "salutation": d = dict(defaults) - d.pop('max_length', None) + d.pop("max_length", None) field = forms.ChoiceField( **d, - choices=[('', '---')] + [(s, s) for s in PERSON_NAME_SALUTATIONS] + choices=[("", "---")] + [(s, s) for s in PERSON_NAME_SALUTATIONS], ) else: field = forms.CharField(**defaults) field.part_name = fname fields.append(field) - super().__init__( - fields=fields, require_all_fields=False, *args, **kwargs - ) + super().__init__(fields=fields, require_all_fields=False, *args, **kwargs) self.require_all_fields = require_all_fields self.required = self.one_required def clean(self, value) -> dict: value = super().clean(value) if self.one_required and (not value or not any(v for v in value.values())): - raise forms.ValidationError(self.error_messages['required'], code='required') + raise forms.ValidationError( + self.error_messages["required"], code="required" + ) if self.one_required: - for k, label, size in self.scheme['fields']: + for k, label, size in self.scheme["fields"]: if k in REQUIRED_NAME_PARTS and not value.get(k): - raise forms.ValidationError(self.error_messages['required'], code='required') + raise forms.ValidationError( + self.error_messages["required"], code="required" + ) if self.require_all_fields and not all(v for v in value): - raise forms.ValidationError(self.error_messages['incomplete'], code='required') + raise forms.ValidationError( + self.error_messages["incomplete"], code="required" + ) if sum(len(v) for v in value if v) > 250: - raise forms.ValidationError(_('Please enter a shorter name.'), code='max_length') + raise forms.ValidationError( + _("Please enter a shorter name."), code="max_length" + ) return value @@ -215,7 +242,9 @@ class WrappedPhonePrefixSelect(Select): def __init__(self, initial=None): choices = [("", "---------")] - language = get_babel_locale() # changed from default implementation that used the django locale + language = ( + get_babel_locale() + ) # changed from default implementation that used the django locale locale = Locale(translation.to_locale(language)) for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items(): prefix = "+%d" % prefix @@ -225,7 +254,12 @@ def __init__(self, initial=None): country_name = locale.territories.get(country_code) if country_name: choices.append((prefix, "{} {}".format(country_name, prefix))) - super().__init__(choices=sorted(choices, key=lambda item: item[1]), attrs={'aria-label': pgettext_lazy('phonenumber', 'International area code')}) + super().__init__( + choices=sorted(choices, key=lambda item: item[1]), + attrs={ + "aria-label": pgettext_lazy("phonenumber", "International area code") + }, + ) def render(self, name, value, *args, **kwargs): return super().render(name, value or self.initial, *args, **kwargs) @@ -249,8 +283,10 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget): def __init__(self, attrs=None, initial=None): attrs = { - 'aria-label': pgettext_lazy('phonenumber', 'Phone number (without international area code)'), - 'placeholder': 'Phone' + "aria-label": pgettext_lazy( + "phonenumber", "Phone number (without international area code)" + ), + "placeholder": "Phone", } widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput(attrs=attrs)) super(PhoneNumberPrefixWidget, self).__init__(widgets, attrs) @@ -260,7 +296,7 @@ def render(self, name, value, attrs=None, renderer=None): return mark_safe(self.format_output(output)) def format_output(self, rendered_widgets) -> str: - return '
%s
' % ''.join(rendered_widgets) + return '
%s
' % "".join(rendered_widgets) def decompress(self, value): """ @@ -270,16 +306,13 @@ def decompress(self, value): silently deleting data. """ if value: - if type(value) == PhoneNumber: + if isinstance(value, PhoneNumber): if value.country_code and value.national_number: return [ "+%d" % value.country_code, national_significant_number(value), ] - return [ - None, - str(value) - ] + return [None, str(value)] elif "." in value: return value.split(".") else: @@ -289,7 +322,9 @@ def decompress(self, value): def value_from_datadict(self, data, files, name): # In contrast to defualt implementation, do not silently fail if a number without # country prefix is entered - values = super(PhoneNumberPrefixWidget, self).value_from_datadict(data, files, name) + values = super(PhoneNumberPrefixWidget, self).value_from_datadict( + data, files, name + ) if values[1]: return "%s.%s" % tuple(values) return "" @@ -302,8 +337,8 @@ def guess_country(event): country = event.settings.region or event.settings.invoice_address_from_country if not country: valid_countries = countries.countries - if '-' in locale: - parts = locale.split('-') + if "-" in locale: + parts = locale.split("-") # TODO: does this actually work? if parts[1].upper() in valid_countries: country = Country(parts[1].upper()) @@ -316,7 +351,7 @@ def guess_country(event): class QuestionCheckboxSelectMultiple(forms.CheckboxSelectMultiple): - option_template_name = 'pretixbase/forms/widgets/checkbox_option_with_links.html' + option_template_name = "pretixbase/forms/widgets/checkbox_option_with_links.html" class MinDateValidator(MinValueValidator): @@ -324,7 +359,9 @@ def __call__(self, value): try: return super().__call__(value) except ValidationError as e: - e.params['limit_value'] = date_format(e.params['limit_value'], 'SHORT_DATE_FORMAT') + e.params["limit_value"] = date_format( + e.params["limit_value"], "SHORT_DATE_FORMAT" + ) raise e @@ -333,7 +370,10 @@ def __call__(self, value): try: return super().__call__(value) except ValidationError as e: - e.params['limit_value'] = date_format(e.params['limit_value'].astimezone(get_current_timezone()), 'SHORT_DATETIME_FORMAT') + e.params["limit_value"] = date_format( + e.params["limit_value"].astimezone(get_current_timezone()), + "SHORT_DATETIME_FORMAT", + ) raise e @@ -343,7 +383,9 @@ def __call__(self, value): try: return super().__call__(value) except ValidationError as e: - e.params['limit_value'] = date_format(e.params['limit_value'], 'SHORT_DATE_FORMAT') + e.params["limit_value"] = date_format( + e.params["limit_value"], "SHORT_DATE_FORMAT" + ) raise e @@ -352,7 +394,10 @@ def __call__(self, value): try: return super().__call__(value) except ValidationError as e: - e.params['limit_value'] = date_format(e.params['limit_value'].astimezone(get_current_timezone()), 'SHORT_DATETIME_FORMAT') + e.params["limit_value"] = date_format( + e.params["limit_value"].astimezone(get_current_timezone()), + "SHORT_DATETIME_FORMAT", + ) raise e @@ -370,117 +415,151 @@ def __init__(self, *args, **kwargs): :param cartpos: The cart position the form should be for :param event: The event this belongs to """ - cartpos = self.cartpos = kwargs.pop('cartpos', None) - orderpos = self.orderpos = kwargs.pop('orderpos', None) + cartpos = self.cartpos = kwargs.pop("cartpos", None) + orderpos = self.orderpos = kwargs.pop("orderpos", None) pos = cartpos or orderpos item = pos.item questions = pos.item.questions_to_ask - event = kwargs.pop('event') - self.all_optional = kwargs.pop('all_optional', False) + event = kwargs.pop("event") + self.all_optional = kwargs.pop("all_optional", False) super().__init__(*args, **kwargs) add_fields = {} if item.admission and event.settings.attendee_names_asked: - add_fields['attendee_name_parts'] = NamePartsFormField( + add_fields["attendee_name_parts"] = NamePartsFormField( max_length=255, - required=event.settings.attendee_names_required and not self.all_optional, + required=event.settings.attendee_names_required + and not self.all_optional, scheme=event.settings.name_scheme, titles=event.settings.name_scheme_titles, - label=_('Attendee name'), - initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts), + label=_("Attendee name"), + initial=( + cartpos.attendee_name_parts + if cartpos + else orderpos.attendee_name_parts + ), ) if item.admission and event.settings.attendee_emails_asked: - add_fields['attendee_email'] = forms.EmailField( - required=event.settings.attendee_emails_required and not self.all_optional, - label=_('Attendee email'), - initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email), - widget=forms.EmailInput( - attrs={ - 'autocomplete': 'email' - } - ) + add_fields["attendee_email"] = forms.EmailField( + required=event.settings.attendee_emails_required + and not self.all_optional, + label=_("Attendee email"), + initial=( + cartpos.attendee_email if cartpos else orderpos.attendee_email + ), + widget=forms.EmailInput(attrs={"autocomplete": "email"}), ) if item.admission and event.settings.attendee_company_asked: - add_fields['company'] = forms.CharField( - required=event.settings.attendee_company_required and not self.all_optional, - label=_('Company'), + add_fields["company"] = forms.CharField( + required=event.settings.attendee_company_required + and not self.all_optional, + label=_("Company"), max_length=255, initial=(cartpos.company if cartpos else orderpos.company), ) if item.admission and event.settings.attendee_addresses_asked: - add_fields['street'] = forms.CharField( - required=event.settings.attendee_addresses_required and not self.all_optional, - label=_('Address'), - widget=forms.Textarea(attrs={ - 'rows': 2, - 'placeholder': _('Street and Number'), - 'autocomplete': 'street-address' - }), + add_fields["street"] = forms.CharField( + required=event.settings.attendee_addresses_required + and not self.all_optional, + label=_("Address"), + widget=forms.Textarea( + attrs={ + "rows": 2, + "placeholder": _("Street and Number"), + "autocomplete": "street-address", + } + ), initial=(cartpos.street if cartpos else orderpos.street), ) - add_fields['zipcode'] = forms.CharField( - required=event.settings.attendee_addresses_required and not self.all_optional, + add_fields["zipcode"] = forms.CharField( + required=event.settings.attendee_addresses_required + and not self.all_optional, max_length=30, - label=_('ZIP code'), + label=_("ZIP code"), initial=(cartpos.zipcode if cartpos else orderpos.zipcode), - widget=forms.TextInput(attrs={ - 'autocomplete': 'postal-code', - }), + widget=forms.TextInput( + attrs={ + "autocomplete": "postal-code", + } + ), ) - add_fields['city'] = forms.CharField( - required=event.settings.attendee_addresses_required and not self.all_optional, - label=_('City'), + add_fields["city"] = forms.CharField( + required=event.settings.attendee_addresses_required + and not self.all_optional, + label=_("City"), max_length=255, initial=(cartpos.city if cartpos else orderpos.city), - widget=forms.TextInput(attrs={ - 'autocomplete': 'address-level2', - }), + widget=forms.TextInput( + attrs={ + "autocomplete": "address-level2", + } + ), ) - country = (cartpos.country if cartpos else orderpos.country) or guess_country(event) - add_fields['country'] = CountryField( - countries=CachedCountries - ).formfield( - required=event.settings.attendee_addresses_required and not self.all_optional, - label=_('Country'), + country = ( + cartpos.country if cartpos else orderpos.country + ) or guess_country(event) + add_fields["country"] = CountryField(countries=CachedCountries).formfield( + required=event.settings.attendee_addresses_required + and not self.all_optional, + label=_("Country"), initial=country, - widget=forms.Select(attrs={ - 'autocomplete': 'country', - }), + widget=forms.Select( + attrs={ + "autocomplete": "country", + } + ), + ) + c = [("", pgettext_lazy("address", "Select state"))] + fprefix = ( + str(self.prefix) + "-" + if self.prefix is not None and self.prefix != "-" + else "" ) - c = [('', pgettext_lazy('address', 'Select state'))] - fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else '' cc = None state = None - if fprefix + 'country' in self.data: - cc = str(self.data[fprefix + 'country']) + if fprefix + "country" in self.data: + cc = str(self.data[fprefix + "country"]) elif country: cc = str(country) if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS: types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc] - statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types] - c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1]) - state = (cartpos.state if cartpos else orderpos.state) - elif fprefix + 'state' in self.data: + statelist = [ + s + for s in pycountry.subdivisions.get(country_code=cc) + if s.type in types + ] + c += sorted( + [(s.code[3:], s.name) for s in statelist], key=lambda s: s[1] + ) + state = cartpos.state if cartpos else orderpos.state + elif fprefix + "state" in self.data: self.data = self.data.copy() - del self.data[fprefix + 'state'] + del self.data[fprefix + "state"] - add_fields['state'] = forms.ChoiceField( - label=pgettext_lazy('address', 'State'), + add_fields["state"] = forms.ChoiceField( + label=pgettext_lazy("address", "State"), required=False, choices=c, initial=state, - widget=forms.Select(attrs={ - 'autocomplete': 'address-level1', - }), + widget=forms.Select( + attrs={ + "autocomplete": "address-level1", + } + ), ) - add_fields['state'].widget.is_required = True + add_fields["state"].widget.is_required = True field_positions = list( [ - (n, event.settings.system_question_order.get(n if n != 'state' else 'country', 0)) + ( + n, + event.settings.system_question_order.get( + n if n != "state" else "country", 0 + ), + ) for n in add_fields.keys() ] ) @@ -491,99 +570,137 @@ def __init__(self, *args, **kwargs): initial = answers[0] if answers else None tz = pytz.timezone(event.settings.timezone) help_text = rich_text(q.help_text) - label = mark_safe(q.question) # django-bootstrap3 calls mark_safe + label = mark_safe(q.question) # django-bootstrap3 calls mark_safe required = q.required and not self.all_optional if q.type == Question.TYPE_BOOLEAN: if q.required: - widget = forms.CheckboxInput(attrs={'required': 'required'}) + widget = forms.CheckboxInput(attrs={"required": "required"}) else: widget = forms.CheckboxInput() initialbool = (initial.answer == "True") if initial else False field = forms.BooleanField( - label=label, required=required, + label=label, + required=required, help_text=help_text, - initial=initialbool, widget=widget, + initial=initialbool, + widget=widget, ) elif q.type == Question.TYPE_NUMBER: field = forms.DecimalField( - label=label, required=required, - min_value=q.valid_number_min or Decimal('0.00'), + label=label, + required=required, + min_value=q.valid_number_min or Decimal("0.00"), max_value=q.valid_number_max, help_text=help_text, initial=initial.answer if initial else None, - widget=forms.NumberInput(attrs={'placeholder': 'Your answer'}), + widget=forms.NumberInput(attrs={"placeholder": "Your answer"}), ) elif q.type == Question.TYPE_STRING: field = forms.CharField( - label=label, required=required, + label=label, + required=required, help_text=help_text, initial=initial.answer if initial else None, - widget=forms.TextInput(attrs={'placeholder': 'Your answer'}), + widget=forms.TextInput(attrs={"placeholder": "Your answer"}), ) elif q.type == Question.TYPE_TEXT: field = forms.CharField( - label=label, required=required, + label=label, + required=required, help_text=help_text, - widget=forms.Textarea(attrs={ - 'placeholder': 'Your answer' - }), + widget=forms.Textarea(attrs={"placeholder": "Your answer"}), initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_COUNTRYCODE: field = CountryField( countries=CachedCountries, - blank=True, null=True, blank_label=' ', + blank=True, + null=True, + blank_label=" ", ).formfield( - label=label, required=required, + label=label, + required=required, help_text=help_text, widget=forms.Select, - empty_label=' ', - initial=initial.answer if initial else (guess_country(event) if required else None), + empty_label=" ", + initial=( + initial.answer + if initial + else (guess_country(event) if required else None) + ), ) elif q.type == Question.TYPE_CHOICE: field = forms.ModelChoiceField( queryset=q.options, - label=label, required=required, + label=label, + required=required, help_text=help_text, widget=forms.Select, - to_field_name='identifier', - empty_label='', + to_field_name="identifier", + empty_label="", initial=initial.options.first() if initial else None, ) elif q.type == Question.TYPE_CHOICE_MULTIPLE: field = forms.ModelMultipleChoiceField( queryset=q.options, - label=label, required=required, + label=label, + required=required, help_text=help_text, - to_field_name='identifier', + to_field_name="identifier", widget=QuestionCheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = ExtFileField( - label=label, required=required, + label=label, + required=required, help_text=help_text, initial=initial.file if initial else None, - widget=UploadedFileWidget(position=pos, event=event, answer=initial), + widget=UploadedFileWidget( + position=pos, event=event, answer=initial + ), ext_whitelist=( - ".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg", - ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages", - ".bmp", ".tif", ".tiff" + ".png", + ".jpg", + ".gif", + ".jpeg", + ".pdf", + ".txt", + ".docx", + ".gif", + ".svg", + ".pptx", + ".ppt", + ".doc", + ".xlsx", + ".xls", + ".jfif", + ".heic", + ".heif", + ".pages", + ".bmp", + ".tif", + ".tiff", ), max_size=10 * 1024 * 1024, ) elif q.type == Question.TYPE_DATE: attrs = {} if q.valid_date_min: - attrs['data-min'] = q.valid_date_min.isoformat() + attrs["data-min"] = q.valid_date_min.isoformat() if q.valid_date_max: - attrs['data-max'] = q.valid_date_max.isoformat() + attrs["data-max"] = q.valid_date_max.isoformat() field = forms.DateField( - label=label, required=required, + label=label, + required=required, help_text=help_text, - initial=dateutil.parser.parse(initial.answer).date() if initial and initial.answer else None, + initial=( + dateutil.parser.parse(initial.answer).date() + if initial and initial.answer + else None + ), widget=DatePickerWidget(attrs), ) if q.valid_date_min: @@ -592,18 +709,30 @@ def __init__(self, *args, **kwargs): field.validators.append(MaxDateValidator(q.valid_date_max)) elif q.type == Question.TYPE_TIME: field = forms.TimeField( - label=label, required=required, + label=label, + required=required, help_text=help_text, - initial=dateutil.parser.parse(initial.answer).time() if initial and initial.answer else None, - widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')), + initial=( + dateutil.parser.parse(initial.answer).time() + if initial and initial.answer + else None + ), + widget=TimePickerWidget( + time_format=get_format_without_seconds("TIME_INPUT_FORMATS") + ), ) elif q.type == Question.TYPE_DATETIME: field = SplitDateTimeField( - label=label, required=required, + label=label, + required=required, help_text=help_text, - initial=dateutil.parser.parse(initial.answer).astimezone(tz) if initial and initial.answer else None, + initial=( + dateutil.parser.parse(initial.answer).astimezone(tz) + if initial and initial.answer + else None + ), widget=SplitDateTimePickerWidget( - time_format=get_format_without_seconds('TIME_INPUT_FORMATS'), + time_format=get_format_without_seconds("TIME_INPUT_FORMATS"), min_date=q.valid_datetime_min, max_date=q.valid_datetime_max, ), @@ -620,37 +749,48 @@ def __init__(self, *args, **kwargs): if str(default_country) in values: default_prefix = prefix try: - initial = PhoneNumber().from_string(initial.answer) if initial else "+{}.".format(default_prefix) + initial = ( + PhoneNumber().from_string(initial.answer) + if initial + else "+{}.".format(default_prefix) + ) except NumberParseException: initial = None field = PhoneNumberField( - label=label, required=required, + label=label, + required=required, help_text=help_text, initial=initial, - widget=WrappedPhoneNumberPrefixWidget() + widget=WrappedPhoneNumberPrefixWidget(), ) elif q.type == Question.TYPE_DESCRIPTION: field = forms.CharField( label=label, widget=forms.Textarea(), initial=mark_safe(q.description), - required=False + required=False, ) - field.widget.attrs['type'] = 'description' + field.widget.attrs["type"] = "description" field.question = q if answers: field.answer = answers[0] if q.dependency_question_id: - field.widget.attrs['data-question-dependency'] = q.dependency_question_id - field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values)) - if q.type != 'M': - field.widget.attrs['required'] = q.required and not self.all_optional + field.widget.attrs["data-question-dependency"] = ( + q.dependency_question_id + ) + field.widget.attrs["data-question-dependency-values"] = escapejson_attr( + json.dumps(q.dependency_values) + ) + if q.type != "M": + field.widget.attrs["required"] = ( + q.required and not self.all_optional + ) field._required = q.required and not self.all_optional field.required = False - add_fields['question_%s' % q.id] = field - field_positions.append(('question_%s' % q.id, q.position)) + add_fields["question_%s" % q.id] = field + field_positions.append(("question_%s" % q.id, q.position)) field_positions.sort(key=lambda e: e[1]) for fname, p in field_positions: @@ -661,49 +801,71 @@ def __init__(self, *args, **kwargs): for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): self.fields[key] = value - value.initial = data.get('question_form_data', {}).get(key) + value.initial = data.get("question_form_data", {}).get(key) for k, v in self.fields.items(): - if v.widget.attrs.get('autocomplete') or k == 'attendee_name_parts': - v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + v.widget.attrs.get('autocomplete', '') + if v.widget.attrs.get("autocomplete") or k == "attendee_name_parts": + v.widget.attrs["autocomplete"] = "section-{} ".format( + self.prefix + ) + v.widget.attrs.get("autocomplete", "") def clean(self): d = super().clean() - if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS: - if not d.get('state'): - self.add_error('state', _('This field is required.')) - - question_cache = {f.question.pk: f.question for f in self.fields.values() if getattr(f, 'question', None)} + if ( + d.get("city") + and d.get("country") + and str(d["country"]) in COUNTRIES_WITH_STATE_IN_ADDRESS + ): + if not d.get("state"): + self.add_error("state", _("This field is required.")) + + question_cache = { + f.question.pk: f.question + for f in self.fields.values() + if getattr(f, "question", None) + } def question_is_visible(parentid, qvals): if parentid not in question_cache: return False parentq = question_cache[parentid] - if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_values): + if parentq.dependency_question_id and not question_is_visible( + parentq.dependency_question_id, parentq.dependency_values + ): return False - if 'question_%d' % parentid not in d: + if "question_%d" % parentid not in d: return False - dval = d.get('question_%d' % parentid) + dval = d.get("question_%d" % parentid) return ( - ('True' in qvals and dval) - or ('False' in qvals and not dval) + ("True" in qvals and dval) + or ("False" in qvals and not dval) or (isinstance(dval, QuestionOption) and dval.identifier in qvals) - or (isinstance(dval, (list, QuerySet)) and any(qval in [o.identifier for o in dval] for qval in qvals)) + or ( + isinstance(dval, (list, QuerySet)) + and any(qval in [o.identifier for o in dval] for qval in qvals) + ) ) def question_is_required(q): - return ( - q.required and - (not q.dependency_question_id or question_is_visible(q.dependency_question_id, q.dependency_values)) + return q.required and ( + not q.dependency_question_id + or question_is_visible(q.dependency_question_id, q.dependency_values) ) if not self.all_optional: for q in question_cache.values(): - answer = d.get('question_%d' % q.pk) - field = self['question_%d' % q.pk] - if question_is_required(q) and not answer and answer != 0 and not field.errors: - raise ValidationError({'question_%d' % q.pk: [_('This field is required.')]}) + answer = d.get("question_%d" % q.pk) + field = self["question_%d" % q.pk] + if ( + question_is_required(q) + and not answer + and answer != 0 + and not field.errors + ): + raise ValidationError( + {"question_%d" % q.pk: [_("This field is required.")]} + ) return d @@ -713,183 +875,268 @@ class BaseInvoiceAddressForm(forms.ModelForm): class Meta: model = InvoiceAddress - fields = ('is_business', 'company', 'name_parts', 'street', 'zipcode', 'city', 'country', 'state', - 'vat_id', 'internal_reference', 'beneficiary', 'custom_field') + fields = ( + "is_business", + "company", + "name_parts", + "street", + "zipcode", + "city", + "country", + "state", + "vat_id", + "internal_reference", + "beneficiary", + "custom_field", + ) widgets = { - 'is_business': BusinessBooleanRadio, - 'street': forms.Textarea(attrs={ - 'rows': 2, - 'placeholder': _('Street and Number'), - 'autocomplete': 'street-address' - }), - 'beneficiary': forms.Textarea(attrs={'rows': 3}), - 'country': forms.Select(attrs={ - 'autocomplete': 'country', - }), - 'zipcode': forms.TextInput(attrs={ - 'autocomplete': 'postal-code', - }), - 'city': forms.TextInput(attrs={ - 'autocomplete': 'address-level2', - }), - 'company': forms.TextInput(attrs={ - 'data-display-dependency': '#id_is_business_1', - 'autocomplete': 'organization', - }), - 'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-in-eu': ','.join(EU_COUNTRIES)}), - 'internal_reference': forms.TextInput, - } - labels = { - 'is_business': '' + "is_business": BusinessBooleanRadio, + "street": forms.Textarea( + attrs={ + "rows": 2, + "placeholder": _("Street and Number"), + "autocomplete": "street-address", + } + ), + "beneficiary": forms.Textarea(attrs={"rows": 3}), + "country": forms.Select( + attrs={ + "autocomplete": "country", + } + ), + "zipcode": forms.TextInput( + attrs={ + "autocomplete": "postal-code", + } + ), + "city": forms.TextInput( + attrs={ + "autocomplete": "address-level2", + } + ), + "company": forms.TextInput( + attrs={ + "data-display-dependency": "#id_is_business_1", + "autocomplete": "organization", + } + ), + "vat_id": forms.TextInput( + attrs={ + "data-display-dependency": "#id_is_business_1", + "data-countries-in-eu": ",".join(EU_COUNTRIES), + } + ), + "internal_reference": forms.TextInput, } + labels = {"is_business": ""} def __init__(self, *args, **kwargs): - self.event = event = kwargs.pop('event') - self.request = kwargs.pop('request', None) - self.validate_vat_id = kwargs.pop('validate_vat_id') - self.all_optional = kwargs.pop('all_optional', False) + self.event = event = kwargs.pop("event") + self.request = kwargs.pop("request", None) + self.validate_vat_id = kwargs.pop("validate_vat_id") + self.all_optional = kwargs.pop("all_optional", False) - kwargs.setdefault('initial', {}) - if not kwargs.get('instance') or not kwargs['instance'].country: - kwargs['initial']['country'] = guess_country(self.event) + kwargs.setdefault("initial", {}) + if not kwargs.get("instance") or not kwargs["instance"].country: + kwargs["initial"]["country"] = guess_country(self.event) super().__init__(*args, **kwargs) if not event.settings.invoice_address_vatid: - del self.fields['vat_id'] + del self.fields["vat_id"] - self.fields['country'].choices = CachedCountries() + self.fields["country"].choices = CachedCountries() - c = [('', pgettext_lazy('address', 'Select state'))] - fprefix = self.prefix + '-' if self.prefix else '' + c = [("", pgettext_lazy("address", "Select state"))] + fprefix = self.prefix + "-" if self.prefix else "" cc = None - if fprefix + 'country' in self.data: - cc = str(self.data[fprefix + 'country']) - elif 'country' in self.initial: - cc = str(self.initial['country']) + if fprefix + "country" in self.data: + cc = str(self.data[fprefix + "country"]) + elif "country" in self.initial: + cc = str(self.initial["country"]) elif self.instance and self.instance.country: cc = str(self.instance.country) if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS: types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc] - statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types] + statelist = [ + s + for s in pycountry.subdivisions.get(country_code=cc) + if s.type in types + ] c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1]) - elif fprefix + 'state' in self.data: + elif fprefix + "state" in self.data: self.data = self.data.copy() - del self.data[fprefix + 'state'] + del self.data[fprefix + "state"] - self.fields['state'] = forms.ChoiceField( - label=pgettext_lazy('address', 'State'), + self.fields["state"] = forms.ChoiceField( + label=pgettext_lazy("address", "State"), required=False, choices=c, - widget=forms.Select(attrs={ - 'autocomplete': 'address-level1', - }), + widget=forms.Select( + attrs={ + "autocomplete": "address-level1", + } + ), ) - self.fields['state'].widget.is_required = True + self.fields["state"].widget.is_required = True # Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected. - if cc and not is_eu_country(cc) and fprefix + 'vat_id' in self.data: + if cc and not is_eu_country(cc) and fprefix + "vat_id" in self.data: self.data = self.data.copy() - del self.data[fprefix + 'vat_id'] + del self.data[fprefix + "vat_id"] if not event.settings.invoice_address_required or self.all_optional: for k, f in self.fields.items(): f.required = False f.widget.is_required = False - if 'required' in f.widget.attrs: - del f.widget.attrs['required'] + if "required" in f.widget.attrs: + del f.widget.attrs["required"] elif event.settings.invoice_address_company_required and not self.all_optional: - self.initial['is_business'] = True + self.initial["is_business"] = True - self.fields['is_business'].widget = BusinessBooleanRadio(require_business=True) - self.fields['company'].required = True - self.fields['company'].widget.is_required = True - self.fields['company'].widget.attrs['required'] = 'required' - del self.fields['company'].widget.attrs['data-display-dependency'] + self.fields["is_business"].widget = BusinessBooleanRadio( + require_business=True + ) + self.fields["company"].required = True + self.fields["company"].widget.is_required = True + self.fields["company"].widget.attrs["required"] = "required" + del self.fields["company"].widget.attrs["data-display-dependency"] - self.fields['name_parts'] = NamePartsFormField( + self.fields["name_parts"] = NamePartsFormField( max_length=255, required=event.settings.invoice_name_required and not self.all_optional, scheme=event.settings.name_scheme, titles=event.settings.name_scheme_titles, - label=_('Name'), - initial=(self.instance.name_parts if self.instance else self.instance.name_parts), + label=_("Name"), + initial=( + self.instance.name_parts if self.instance else self.instance.name_parts + ), ) - if event.settings.invoice_address_required and not event.settings.invoice_address_company_required and not self.all_optional: + if ( + event.settings.invoice_address_required + and not event.settings.invoice_address_company_required + and not self.all_optional + ): if not event.settings.invoice_name_required: - self.fields['name_parts'].widget.attrs['data-required-if'] = '#id_is_business_0' - self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1' - self.fields['company'].widget.attrs['data-required-if'] = '#id_is_business_1' + self.fields["name_parts"].widget.attrs[ + "data-required-if" + ] = "#id_is_business_0" + self.fields["name_parts"].widget.attrs["data-no-required-attr"] = "1" + self.fields["company"].widget.attrs[ + "data-required-if" + ] = "#id_is_business_1" if not event.settings.invoice_address_beneficiary: - del self.fields['beneficiary'] + del self.fields["beneficiary"] if event.settings.invoice_address_custom_field: - self.fields['custom_field'].label = event.settings.invoice_address_custom_field + self.fields["custom_field"].label = ( + event.settings.invoice_address_custom_field + ) else: - del self.fields['custom_field'] + del self.fields["custom_field"] for k, v in self.fields.items(): - if v.widget.attrs.get('autocomplete') or k == 'name_parts': - v.widget.attrs['autocomplete'] = 'section-invoice billing ' + v.widget.attrs.get('autocomplete', '') + if v.widget.attrs.get("autocomplete") or k == "name_parts": + v.widget.attrs["autocomplete"] = ( + "section-invoice billing " + v.widget.attrs.get("autocomplete", "") + ) def clean(self): data = self.cleaned_data - if not data.get('is_business'): - data['company'] = '' - data['vat_id'] = '' - if data.get('is_business') and not is_eu_country(data.get('country')): - data['vat_id'] = '' + if not data.get("is_business"): + data["company"] = "" + data["vat_id"] = "" + if data.get("is_business") and not is_eu_country(data.get("country")): + data["vat_id"] = "" if self.event.settings.invoice_address_required: - if data.get('is_business') and not data.get('company'): - raise ValidationError(_('You need to provide a company name.')) - if not data.get('is_business') and not data.get('name_parts'): - raise ValidationError(_('You need to provide your name.')) + if data.get("is_business") and not data.get("company"): + raise ValidationError(_("You need to provide a company name.")) + if not data.get("is_business") and not data.get("name_parts"): + raise ValidationError(_("You need to provide your name.")) - if 'vat_id' in self.changed_data or not data.get('vat_id'): + if "vat_id" in self.changed_data or not data.get("vat_id"): self.instance.vat_id_validated = False - if data.get('city') and data.get('country') and str(data['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS: - if not data.get('state'): - self.add_error('state', _('This field is required.')) - - self.instance.name_parts = data.get('name_parts') - - if all( - not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts') - ) and len(data.get('name_parts', {})) == 1: + if ( + data.get("city") + and data.get("country") + and str(data["country"]) in COUNTRIES_WITH_STATE_IN_ADDRESS + ): + if not data.get("state"): + self.add_error("state", _("This field is required.")) + + self.instance.name_parts = data.get("name_parts") + + if ( + all( + not v + for k, v in data.items() + if k not in ("is_business", "country", "name_parts") + ) + and len(data.get("name_parts", {})) == 1 + ): # Do not save the country if it is the only field set -- we don't know the user even checked it! - self.cleaned_data['country'] = '' - - if data.get('vat_id') and is_eu_country(data.get('country')) and data.get('vat_id')[:2] != cc_to_vat_prefix(str(data.get('country'))): - raise ValidationError(_('Your VAT ID does not match the selected country.')) - - if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data: + self.cleaned_data["country"] = "" + + if ( + data.get("vat_id") + and is_eu_country(data.get("country")) + and data.get("vat_id")[:2] != cc_to_vat_prefix(str(data.get("country"))) + ): + raise ValidationError(_("Your VAT ID does not match the selected country.")) + + if ( + self.validate_vat_id + and self.instance.vat_id_validated + and "vat_id" not in self.changed_data + ): pass - elif self.validate_vat_id and data.get('is_business') and is_eu_country(data.get('country')) and data.get('vat_id'): + elif ( + self.validate_vat_id + and data.get("is_business") + and is_eu_country(data.get("country")) + and data.get("vat_id") + ): try: - result = vat_moss.id.validate(data.get('vat_id')) + result = vat_moss.id.validate(data.get("vat_id")) if result: country_code, normalized_id, company_name = result self.instance.vat_id_validated = True self.instance.vat_id = normalized_id except (vat_moss.errors.InvalidError, ValueError): - raise ValidationError(_('This VAT ID is not valid. Please re-check your input.')) + raise ValidationError( + _("This VAT ID is not valid. Please re-check your input.") + ) except vat_moss.errors.WebServiceUnavailableError: - logger.exception('VAT ID checking failed for country {}'.format(data.get('country'))) + logger.exception( + "VAT ID checking failed for country {}".format(data.get("country")) + ) self.instance.vat_id_validated = False if self.request and self.vat_warning: - messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of ' - 'your country is currently not available. We will therefore ' - 'need to charge VAT on your invoice. You can get the tax amount ' - 'back via the VAT reimbursement process.')) + messages.warning( + self.request, + _( + "Your VAT ID could not be checked, as the VAT checking service of " + "your country is currently not available. We will therefore " + "need to charge VAT on your invoice. You can get the tax amount " + "back via the VAT reimbursement process." + ), + ) except (vat_moss.errors.WebServiceError, HTTPError): - logger.exception('VAT ID checking failed for country {}'.format(data.get('country'))) + logger.exception( + "VAT ID checking failed for country {}".format(data.get("country")) + ) self.instance.vat_id_validated = False if self.request and self.vat_warning: - messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of ' - 'your country returned an incorrect result. We will therefore ' - 'need to charge VAT on your invoice. Please contact support to ' - 'resolve this manually.')) + messages.warning( + self.request, + _( + "Your VAT ID could not be checked, as the VAT checking service of " + "your country returned an incorrect result. We will therefore " + "need to charge VAT on your invoice. Please contact support to " + "resolve this manually." + ), + ) else: self.instance.vat_id_validated = False @@ -898,7 +1145,7 @@ class BaseInvoiceNameForm(BaseInvoiceAddressForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for f in list(self.fields.keys()): - if f != 'name_parts': + if f != "name_parts": del self.fields[f] @@ -913,7 +1160,7 @@ def get_country_from_request(request, event): g = GeoIP2() try: res = g.country(get_client_ip(request)) - country_code = res.get('country_code') + country_code = res.get("country_code") if country_code and len(country_code) == 2: return Country(country_code) except AddressNotFoundError: diff --git a/src/pretix/base/i18n.py b/src/pretix/base/i18n.py index cb0be9732..e13580e02 100644 --- a/src/pretix/base/i18n.py +++ b/src/pretix/base/i18n.py @@ -120,7 +120,7 @@ def language(lng, region=None): translation.activate(_lng) -class LazyLocaleException(Exception): +class LazyLocaleException(Exception): # NOQA: N818 def __init__(self, *args): self.msg = args[0] self.msgargs = args[1] if len(args) > 1 else None diff --git a/src/pretix/base/management/commands/create_customer_account.py b/src/pretix/base/management/commands/create_customer_account.py index fd9e4db1a..af62fbde6 100644 --- a/src/pretix/base/management/commands/create_customer_account.py +++ b/src/pretix/base/management/commands/create_customer_account.py @@ -4,7 +4,7 @@ from django_scopes import scopes_disabled from pretix.base.i18n import get_language_without_region -from pretix.base.models import Organizer, Event, Order, Customer +from pretix.base.models import Customer, Event, Order, Organizer class Command(BaseCommand): @@ -42,7 +42,7 @@ def handle(self, *args, **options): if not customer: name_parts_data = { "_scheme": "full", - "full_name":order.email.split("@")[0] + "full_name": order.email.split("@")[0] } customer = organizer.customers.create( email=order.email, diff --git a/src/pretix/base/migrations/0001_initial.py b/src/pretix/base/migrations/0001_initial.py index 6dce774a9..a633581d0 100644 --- a/src/pretix/base/migrations/0001_initial.py +++ b/src/pretix/base/migrations/0001_initial.py @@ -1,13 +1,16 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 +import uuid from decimal import Decimal -from django.conf import settings -from django.db import migrations, models + import django.db.models.deletion import django.db.models.manager import django.utils.timezone import i18nfield.fields import phonenumber_field.modelfields +from django.conf import settings +from django.db import migrations, models + import pretix.base.models.auth import pretix.base.models.base import pretix.base.models.devices @@ -19,7 +22,6 @@ import pretix.base.models.organizer import pretix.base.models.vouchers import pretix.helpers.countries -import uuid class Migration(migrations.Migration): diff --git a/src/pretix/base/migrations/0002_question_description.py b/src/pretix/base/migrations/0002_question_description.py index a6d4bc690..7419719be 100644 --- a/src/pretix/base/migrations/0002_question_description.py +++ b/src/pretix/base/migrations/0002_question_description.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.13 on 2024-06-25 06:46 -from django.db import migrations, models import i18nfield.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/base/migrations/0003_eventfooterlinkmodel_organizerfooterlinkmodel.py b/src/pretix/base/migrations/0003_eventfooterlinkmodel_organizerfooterlinkmodel.py index 2b05eee35..476828291 100644 --- a/src/pretix/base/migrations/0003_eventfooterlinkmodel_organizerfooterlinkmodel.py +++ b/src/pretix/base/migrations/0003_eventfooterlinkmodel_organizerfooterlinkmodel.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.13 on 2024-06-26 04:36 -from django.db import migrations, models import django.db.models.deletion import i18nfield.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/base/models/__init__.py b/src/pretix/base/models/__init__.py index 521e9f814..3e584fd40 100644 --- a/src/pretix/base/models/__init__.py +++ b/src/pretix/base/models/__init__.py @@ -1,8 +1,8 @@ from ..settings import GlobalSettingsObject_SettingsStore from .auth import U2FDevice, User, WebAuthnDevice -from .customers import Customer from .base import CachedFile, LoggedModel, cachedfile_name from .checkin import Checkin, CheckinList +from .customers import Customer from .devices import Device, Gate from .event import ( Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue, diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py index a162a3c84..04655c7f2 100644 --- a/src/pretix/base/models/checkin.py +++ b/src/pretix/base/models/checkin.py @@ -1,7 +1,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.db import models -from django.db.models import Exists, F, Max, OuterRef, Q, Subquery, JSONField +from django.db.models import Exists, F, JSONField, 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 @@ -185,7 +185,7 @@ def validate_rules(cls, rules, seen_nonbool=False, depth=0): return if operator in ('or', 'and') and seen_nonbool: - raise ValidationError(f'You cannot use OR/AND logic on a level below a comparison operator.') + raise ValidationError('You cannot use OR/AND logic on a level below a comparison operator.') for v in values: cls.validate_rules(v, seen_nonbool=seen_nonbool or operator not in ('or', 'and'), depth=depth + 1) diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 99872adcc..aa6aeaa16 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -1,5 +1,3 @@ -import string -import uuid import secrets from django.conf import settings diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index eaec57309..c90ca7c9a 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -559,23 +559,23 @@ def get_mail_backend(self, timeout=None, force_custom=False): if self.settings.email_vendor == 'sendgrid': return SendGridEmail(api_key=self.settings.send_grid_api_key) return CustomSMTPBackend(host=self.settings.smtp_host, - port=self.settings.smtp_port, - username=self.settings.smtp_username, - password=self.settings.smtp_password, - use_tls=self.settings.smtp_use_tls, - use_ssl=self.settings.smtp_use_ssl, - fail_silently=False, timeout=timeout) + port=self.settings.smtp_port, + username=self.settings.smtp_username, + password=self.settings.smtp_password, + use_tls=self.settings.smtp_use_tls, + use_ssl=self.settings.smtp_use_ssl, + fail_silently=False, timeout=timeout) elif gs.settings.email_vendor is not None: if gs.settings.email_vendor == 'sendgrid': return SendGridEmail(api_key=gs.settings.send_grid_api_key) else: CustomSMTPBackend(host=gs.settings.smtp_host, - port=gs.settings.smtp_port, - username=gs.settings.smtp_username, - password=gs.settings.smtp_password, - use_tls=gs.settings.smtp_use_tls, - use_ssl=gs.settings.smtp_use_ssl, - fail_silently=False, timeout=timeout) + port=gs.settings.smtp_port, + username=gs.settings.smtp_username, + password=gs.settings.smtp_password, + use_tls=gs.settings.smtp_use_tls, + use_ssl=gs.settings.smtp_use_ssl, + fail_silently=False, timeout=timeout) else: return get_connection(fail_silently=False) @@ -706,7 +706,7 @@ def copy_data_from(self, other): for q in self.questions.filter(dependency_question__isnull=False): q.dependency_question = question_map[q.dependency_question_id] q.save(update_fields=['dependency_question']) - + # Copy event footer link for footerLink in EventFooterLinkModel.objects.filter(event=other): footerLink.pk = None @@ -1485,9 +1485,9 @@ class EventFooterLinkModel(models.Model): """ FooterLink model - support show link for event's footer """ - event = models.ForeignKey('Event', - on_delete=models.CASCADE, - related_name='footer_links') + event = models.ForeignKey('Event', + on_delete=models.CASCADE, + related_name='footer_links') label = I18nCharField( max_length=255, verbose_name=_("Link's text"), diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 78ac38f57..35cbfc310 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -1040,7 +1040,7 @@ class Question(LoggedModel): question = I18nTextField( verbose_name=_("Question") ) - description = I18nTextField( + description = I18nTextField( verbose_name=_("Description"), default="", null=True, @@ -1474,7 +1474,7 @@ def availability( _cache['_count_waitinglist'] = count_waitinglist return res - class QuotaExceededException(Exception): + class QuotaExceededException(Exception): # NOQA: N818 pass @staticmethod diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index eec1c9fff..5f584a54b 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -3,13 +3,12 @@ import hmac import json import logging +import secrets import string -import uuid from collections import Counter from datetime import datetime, time, timedelta from decimal import Decimal from typing import Any, Dict, List, Union -import secrets import dateutil import pycountry diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index c55f4fa35..2e91fa698 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -11,10 +11,11 @@ from django.utils.functional import cached_property from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.translation import gettext_lazy as _ +from i18nfield.fields import I18nCharField from pretix.base.models.base import LoggedModel from pretix.base.validators import OrganizerSlugBanlistValidator -from i18nfield.fields import I18nCharField + from ..settings import settings_hierarkey from .auth import User @@ -419,13 +420,14 @@ def get_events_with_permission(self, permission, request=None): else: return self.team.organizer.events.none() + class OrganizerFooterLinkModel(models.Model): """ FooterLink model - support show link for organizer's footer """ - organizer = models.ForeignKey('Organizer', - on_delete=models.CASCADE, - related_name='footer_links') + organizer = models.ForeignKey('Organizer', + on_delete=models.CASCADE, + related_name='footer_links') label = I18nCharField( max_length=255, diff --git a/src/pretix/base/models/tax.py b/src/pretix/base/models/tax.py index fb94ed022..dd03f325b 100644 --- a/src/pretix/base/models/tax.py +++ b/src/pretix/base/models/tax.py @@ -137,7 +137,7 @@ class TaxRule(LoggedModel): class Meta: ordering = ('event', 'rate', 'id') - class SaleNotAllowed(Exception): + class SaleNotAllowed(Exception): # NOQA: N818 pass def allow_delete(self): diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py index d0bc6861e..bad828f38 100644 --- a/src/pretix/base/models/waitinglist.py +++ b/src/pretix/base/models/waitinglist.py @@ -19,7 +19,7 @@ from .items import Item, ItemVariation -class WaitingListException(Exception): +class WaitingListException(Exception): # NOQA: N818 pass diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index fc3d9d24b..83f53ad63 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -3,8 +3,8 @@ import logging from collections import OrderedDict from decimal import ROUND_HALF_UP, Decimal -from typing import Any, Dict, Union from enum import Enum +from typing import Any, Dict, Union import pytz from django import forms @@ -54,6 +54,7 @@ class WalletQueries: (WalletType.GOOGLEPAY.value, pgettext_lazy('payment', 'Google Pay')), ) + class PaymentProviderForm(Form): def clean(self): cleaned_data = super().clean() @@ -856,7 +857,7 @@ def matching_id(self, payment: OrderPayment): return None -class PaymentException(Exception): +class PaymentException(Exception): # NOQA: N818 pass diff --git a/src/pretix/base/pdf.py b/src/pretix/base/pdf.py index 167badea4..22e902dd5 100644 --- a/src/pretix/base/pdf.py +++ b/src/pretix/base/pdf.py @@ -1,12 +1,12 @@ import copy import hashlib import itertools +import json import logging import os import subprocess import tempfile import uuid -import json from collections import OrderedDict from functools import partial from io import BytesIO @@ -554,9 +554,9 @@ def _draw_poweredby(self, canvas: Canvas, op: OrderPosition, o: dict): def _draw_barcodearea(self, canvas: Canvas, op: OrderPosition, o: dict): content = o.get('content', 'secret') content_dict = { - 'event': str(op.event), - 'ticket': op.secret, - 'lead': op.pseudonymization_id + 'event': str(op.event), + 'ticket': op.secret, + 'lead': op.pseudonymization_id } content = json.dumps(content_dict) level = 'H' diff --git a/src/pretix/base/plugins.py b/src/pretix/base/plugins.py index 18d425c90..9e658aaeb 100644 --- a/src/pretix/base/plugins.py +++ b/src/pretix/base/plugins.py @@ -1,8 +1,7 @@ -import os -import sys import importlib.metadata import logging - +import os +import sys from enum import Enum from typing import List @@ -12,12 +11,14 @@ logger = logging.getLogger(__name__) + class PluginType(Enum): """ Plugin type classification. THIS IS DEPRECATED, DO NOT USE ANY MORE. This is only not removed yet as external plugins might have references to this enum. """ + RESTRICTION = 1 PAYMENT = 2 ADMINFEATURE = 3 @@ -30,21 +31,24 @@ def get_all_plugins(event=None) -> List[type]: """ plugins = [] for app in apps.get_app_configs(): - if hasattr(app, 'PretixPluginMeta'): + if hasattr(app, "PretixPluginMeta"): meta = app.PretixPluginMeta meta.module = app.name meta.app = app if app.name in settings.PRETIX_PLUGINS_EXCLUDE: continue - if hasattr(app, 'is_available') and event: + if hasattr(app, "is_available") and event: if not app.is_available(event): continue plugins.append(meta) return sorted( plugins, - key=lambda m: (0 if m.module.startswith('pretix.') else 1, str(m.name).lower().replace('pretix ', '')) + key=lambda m: ( + 0 if m.module.startswith("pretix.") else 1, + str(m.name).lower().replace("pretix ", ""), + ), ) @@ -53,32 +57,37 @@ class PluginConfig(AppConfig): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if not hasattr(self, 'PretixPluginMeta'): - raise ImproperlyConfigured("A pretix plugin config should have a PretixPluginMeta inner class.") - - if hasattr(self.PretixPluginMeta, 'compatibility') and not os.environ.get("PRETIX_IGNORE_CONFLICTS") == "True": + if not hasattr(self, "PretixPluginMeta"): + raise ImproperlyConfigured( + "A pretix plugin config should have a PretixPluginMeta inner class." + ) + + if ( + hasattr(self.PretixPluginMeta, "compatibility") + and not os.environ.get("PRETIX_IGNORE_CONFLICTS") == "True" + ): self.check_compatibility() def check_compatibility(self): """ - Checks for compatibility of the plugin based on specified version requirements. - - This method verifies if the currently installed versions of required packages match - the versions specified in the plugin's compatibility requirements. If a version - mismatch is found or a required package is not installed, it prints an error message - and exits the program. - - Steps: - 1. Iterates over the compatibility requirements specified in `self.PretixPluginMeta.compatibility`. - 2. For each requirement, it splits the package name and the required version. - 3. Fetches the installed version of the package using `importlib.metadata.version`. - 4. Compares the installed version with the required version. - 5. If a mismatch is found, prints an error message and exits the program. - 6. If a required package is not found, catches the `PackageNotFoundError`, prints an error message, - and exits the program. - - Raises: - SystemExit: If a version conflict or missing package is detected, the program exits. + Checks for compatibility of the plugin based on specified version requirements. + + This method verifies if the currently installed versions of required packages match + the versions specified in the plugin's compatibility requirements. If a version + mismatch is found or a required package is not installed, it prints an error message + and exits the program. + + Steps: + 1. Iterates over the compatibility requirements specified in `self.PretixPluginMeta.compatibility`. + 2. For each requirement, it splits the package name and the required version. + 3. Fetches the installed version of the package using `importlib.metadata.version`. + 4. Compares the installed version with the required version. + 5. If a mismatch is found, prints an error message and exits the program. + 6. If a required package is not found, catches the `PackageNotFoundError`, prints an error message, + and exits the program. + + Raises: + SystemExit: If a version conflict or missing package is detected, the program exits. """ try: for requirement in self.PretixPluginMeta.compatibility: @@ -86,8 +95,14 @@ def check_compatibility(self): installed_version = importlib.metadata.version(package_name) if installed_version != required_version: logger.error("Incompatible plugins found!") - logger.error("Plugin %s requires you to have %s==%s, but you installed %s==%s", - self.name, package_name, required_version, package_name, installed_version) + logger.error( + "Plugin %s requires you to have %s==%s, but you installed %s==%s", + self.name, + package_name, + required_version, + package_name, + installed_version, + ) sys.exit(1) except importlib.metadata.PackageNotFoundError as e: logger.exception(f"Package not found: {e}") diff --git a/src/pretix/base/secretgenerators/pretix_sig1_pb2.py b/src/pretix/base/secretgenerators/pretix_sig1_pb2.py index 3b091849a..e7214be88 100644 --- a/src/pretix/base/secretgenerators/pretix_sig1_pb2.py +++ b/src/pretix/base/secretgenerators/pretix_sig1_pb2.py @@ -2,26 +2,27 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: pretix_sig1.proto """Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database +from google.protobuf import ( + descriptor as _descriptor, descriptor_pool as _descriptor_pool, + symbol_database as _symbol_database, +) from google.protobuf.internal import builder as _builder + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11pretix_sig1.proto\"I\n\x06Ticket\x12\x0c\n\x04seed\x18\x01 \x01(\t\x12\x0c\n\x04item\x18\x02 \x01(\x03\x12\x11\n\tvariation\x18\x03 \x01(\x03\x12\x10\n\x08subevent\x18\x04 \x01(\x03\x42\x33\n#eu.pretix.libpretixsync.crypto.sig1B\x0cTicketProtosb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x11pretix_sig1.proto\"I\n\x06Ticket\x12\x0c\n\x04seed\x18\x01 \x01(\t\x12\x0c\n\x04item\x18\x02 \x01(\x03\x12\x11\n\tvariation\x18\x03 \x01(\x03\x12\x10\n\x08subevent\x18\x04 \x01(\x03\x42\x33\n#eu.pretix.libpretixsync.crypto.sig1B\x0cTicketProtosb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'pretix_sig1_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n#eu.pretix.libpretixsync.crypto.sig1B\014TicketProtos' - _globals['_TICKET']._serialized_start=21 - _globals['_TICKET']._serialized_end=94 + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n#eu.pretix.libpretixsync.crypto.sig1B\014TicketProtos' + _globals['_TICKET']._serialized_start = 21 + _globals['_TICKET']._serialized_end = 94 # @@protoc_insertion_point(module_scope) diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index a50fd4bcd..581f8b783 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -150,22 +150,22 @@ def operation_to_expression(self, rule): return Value(dateutil.parser.parse(values[1])) elif values[0] == 'date_from': return Coalesce( - F(f'subevent__date_from'), - F(f'order__event__date_from'), + F('subevent__date_from'), + F('order__event__date_from'), ) elif values[0] == 'date_to': return Coalesce( - F(f'subevent__date_to'), - F(f'subevent__date_from'), - F(f'order__event__date_to'), - F(f'order__event__date_from'), + F('subevent__date_to'), + F('subevent__date_from'), + F('order__event__date_to'), + F('order__event__date_from'), ) elif values[0] == 'date_admission': return Coalesce( - F(f'subevent__date_admission'), - F(f'subevent__date_from'), - F(f'order__event__date_admission'), - F(f'order__event__date_from'), + F('subevent__date_admission'), + F('subevent__date_from'), + F('order__event__date_admission'), + F('order__event__date_from'), ) else: raise ValueError(f'Unknown time type {values[0]}') diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py index aaec7bab5..72d971e2e 100644 --- a/src/pretix/base/services/invoices.py +++ b/src/pretix/base/services/invoices.py @@ -329,7 +329,7 @@ def invoice_pdf(*args, **kwargs): invoice_pdf_task.apply_async(args=args, kwargs=kwargs) -class DummyRollbackException(Exception): +class DummyRollbackException(Exception): # NOQA: N818 pass diff --git a/src/pretix/base/services/locking.py b/src/pretix/base/services/locking.py index 10e8c8605..68de69c0d 100644 --- a/src/pretix/base/services/locking.py +++ b/src/pretix/base/services/locking.py @@ -39,11 +39,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): return False -class LockTimeoutException(Exception): +class LockTimeoutException(Exception): # NOQA: N818 pass -class LockReleaseException(Exception): +class LockReleaseException(Exception): # NOQA: N818 pass diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py index 2d541b2b6..0569225f7 100644 --- a/src/pretix/base/services/mail.py +++ b/src/pretix/base/services/mail.py @@ -30,7 +30,8 @@ from pretix.base.email import ClassicMailRenderer from pretix.base.i18n import language from pretix.base.models import ( - CachedFile, Event, Invoice, InvoiceAddress, Order, OrderPosition, User, Organizer, Customer, + CachedFile, Customer, Event, Invoice, InvoiceAddress, Order, OrderPosition, + Organizer, User, ) from pretix.base.services.invoices import invoice_pdf_task from pretix.base.services.tasks import TransactionAwareTask @@ -50,7 +51,7 @@ def __missing__(self, key): return key -class SendMailException(Exception): +class SendMailException(Exception): # NOQA: N818 pass @@ -132,10 +133,10 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La content_plain = body_plain = render_mail(template, context) subject = str(subject).format_map(TolerantDict(context)) sender = ( - sender or - (event.settings.get('mail_from') if event else settings.MAIL_FROM) or - (organizer.settings.get('mail_from') if organizer else settings.MAIL_FROM) or - settings.MAIL_FROM + sender or + (event.settings.get('mail_from') if event else settings.MAIL_FROM) or + (organizer.settings.get('mail_from') if organizer else settings.MAIL_FROM) or + settings.MAIL_FROM ) if event: sender_name = event.settings.mail_from_name or str(event.name) @@ -312,15 +313,15 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st with scopes_disabled(): event = Event.objects.get(id=event) backend = event.get_mail_backend() - cm = lambda: scope(organizer=event.organizer) # noqa + def cm(): return scope(organizer=event.organizer) # noqa elif organizer: with scopes_disabled(): organizer = Organizer.objects.get(id=organizer) backend = organizer.get_mail_backend() - cm = lambda: scope(organizer=organizer) # noqa + def cm(): return scope(organizer=organizer) # noqa else: backend = get_connection(fail_silently=False) - cm = lambda: scopes_disabled() # noqa + def cm(): return scopes_disabled() # noqa with cm(): if customer: customer = Customer.objects.get(pk=customer) diff --git a/src/pretix/base/services/notifications.py b/src/pretix/base/services/notifications.py index 34b4b48f8..b08ffd126 100644 --- a/src/pretix/base/services/notifications.py +++ b/src/pretix/base/services/notifications.py @@ -1,8 +1,8 @@ +from css_inline import inline as inline_css from django.conf import settings from django.template.loader import get_template from django.utils.timezone import override from django_scopes import scope, scopes_disabled -from css_inline import inline as inline_css from pretix.base.i18n import language from pretix.base.models import LogEntry, NotificationSetting, User @@ -74,9 +74,9 @@ def notify(logentry_ids: list): def send_notification(logentry_id: int, action_type: str, user_id: int, method: str): logentry = LogEntry.all.get(id=logentry_id) if logentry.event: - sm = lambda: scope(organizer=logentry.event.organizer) # noqa + def sm(): return scope(organizer=logentry.event.organizer) # noqa else: - sm = lambda: scopes_disabled() # noqa + def sm(): return scopes_disabled() # noqa with sm(): user = User.objects.get(id=user_id) types = get_all_notification_types(logentry.event) diff --git a/src/pretix/base/services/tickets.py b/src/pretix/base/services/tickets.py index 9374f6e50..13767ada7 100644 --- a/src/pretix/base/services/tickets.py +++ b/src/pretix/base/services/tickets.py @@ -68,7 +68,7 @@ def generate(model: str, pk: int, provider: str): return generate_orderposition(pk, provider) -class DummyRollbackException(Exception): +class DummyRollbackException(Exception): # NOQA: N818 pass diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index dfd64fdee..9f7af46cf 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -7,8 +7,11 @@ from django.utils.translation import gettext_lazy as _ from hierarkey.models import GlobalSettingsBase, Hierarkey from i18nfield.strings import LazyI18nString -from pretix.base.configurations import (DEFAULT_SETTINGS, CSS_SETTINGS, TITLE_GROUP, NAME_SALUTION, NAME_SCHEMES, - COUNTRIES_WITH_STATE, LazyI18nStringListBase) + +from pretix.base.configurations import ( + COUNTRIES_WITH_STATE, CSS_SETTINGS, DEFAULT_SETTINGS, NAME_SALUTION, + NAME_SCHEMES, TITLE_GROUP, LazyI18nStringListBase, +) from pretix.base.reldate import RelativeDateWrapper diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py index 4a1594a5c..6255d57f0 100644 --- a/src/pretix/base/templatetags/rich_text.py +++ b/src/pretix/base/templatetags/rich_text.py @@ -158,7 +158,6 @@ def extendMarkdown(self, md, *args, **kwargs): md.parser.blockprocessors.deregister('quote') - def markdown_compile(source, snippet=False): tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS exts = [ diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py index 7454a7ee2..0f569763c 100644 --- a/src/pretix/control/forms/__init__.py +++ b/src/pretix/control/forms/__init__.py @@ -371,4 +371,3 @@ def clean(self): if data.get('smtp_use_tls') and data.get('smtp_use_ssl'): raise ValidationError(_('SSL and STARTTLS can not be enabled at the same time.')) return data - diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index e6bc3838e..62cf3eeea 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -5,7 +5,9 @@ from django.core.exceptions import ValidationError from django.core.validators import validate_email from django.db.models import Q -from django.forms import CheckboxSelectMultiple, formset_factory, inlineformset_factory +from django.forms import ( + CheckboxSelectMultiple, formset_factory, inlineformset_factory, +) from django.urls import reverse from django.utils.html import escape from django.utils.safestring import mark_safe @@ -21,14 +23,16 @@ from pretix.base.email import get_available_placeholders from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm from pretix.base.models import Event, Organizer, TaxRule, Team -from pretix.base.models.event import EventMetaValue, SubEvent, EventFooterLinkModel +from pretix.base.models.event import ( + EventFooterLinkModel, EventMetaValue, SubEvent, +) from pretix.base.reldate import RelativeDateField, RelativeDateTimeField from pretix.base.settings import ( PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_event_settings, ) from pretix.control.forms import ( - MultipleLanguagesWidget, SlugWidget, SplitDateTimeField, - SplitDateTimePickerWidget, SMTPSettingsMixin, + MultipleLanguagesWidget, SlugWidget, SMTPSettingsMixin, SplitDateTimeField, + SplitDateTimePickerWidget, ) from pretix.control.forms.widgets import Select2 from pretix.helpers.countries import CachedCountries @@ -1000,11 +1004,10 @@ class MailSettingsForm(SMTPSettingsMixin, SettingsForm): ) smtp_select = [ - + ('sendgrid', _("SendGrid")), ('smtp', _("SMTP"))] - email_vendor = forms.ChoiceField( label=_(""), required=True, @@ -1494,4 +1497,4 @@ def __init__(self, *args, **kwargs): EventFooterLinkForm, formset=BaseEventFooterLink, can_order=False, can_delete=True, extra=0 -) \ No newline at end of file +) diff --git a/src/pretix/control/forms/global_settings.py b/src/pretix/control/forms/global_settings.py index f74f1cdd1..e57f92c29 100644 --- a/src/pretix/control/forms/global_settings.py +++ b/src/pretix/control/forms/global_settings.py @@ -1,6 +1,7 @@ from collections import OrderedDict from django import forms +from django.conf import settings from django.utils.translation import gettext_lazy as _ from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput @@ -8,8 +9,6 @@ from pretix.base.settings import GlobalSettingsObject from pretix.base.signals import register_global_settings -from django.conf import settings - class GlobalSettingsForm(SettingsForm): auto_fields = [ @@ -17,7 +16,6 @@ class GlobalSettingsForm(SettingsForm): 'mail_from' ] - def _setting_default(self): """ Load default email setting form .cfg file if not set @@ -40,7 +38,7 @@ def _setting_default(self): def __init__(self, *args, **kwargs): self.obj = GlobalSettingsObject() - self._setting_default() + self._setting_default() super().__init__(*args, obj=self.obj, **kwargs) smtp_select = [ @@ -126,7 +124,7 @@ def __init__(self, *args, **kwargs): label=_("Use STARTTLS"), help_text=_("Commonly enabled on port 587."), required=False - + )), ('smtp_use_ssl', forms.BooleanField( label=_("Use SSL"), diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index e5f7d6733..d8c8d33a6 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -146,12 +146,13 @@ class Meta: 'dependency_question': SafeModelChoiceField, } + class DescriptionForm(QuestionForm): question = I18nFormField( label=_("Description Title"), widget_kwargs={'attrs': {'rows': 2}}, widget=I18nTextarea, - + ) description = I18nFormField( label=_("Description"), @@ -163,12 +164,14 @@ class DescriptionForm(QuestionForm): def removeDesOption(self): # just override parent 's function pass + def __init__(self, *args, **kwargs): kwargs['initial'] = { "type": "DES", } super().__init__(*args, **kwargs) + class QuestionOptionForm(I18nModelForm): class Meta: model = QuestionOption diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py index 2fcfc1d4a..1345da943 100644 --- a/src/pretix/control/forms/orders.py +++ b/src/pretix/control/forms/orders.py @@ -511,6 +511,7 @@ def __init__(self, *args, **kwargs): else: del self.fields['customer'] + class OrderLocaleForm(forms.ModelForm): locale = forms.ChoiceField() diff --git a/src/pretix/control/forms/organizer_forms/__init__.py b/src/pretix/control/forms/organizer_forms/__init__.py index 596e8e2cd..7bcbf9ec2 100644 --- a/src/pretix/control/forms/organizer_forms/__init__.py +++ b/src/pretix/control/forms/organizer_forms/__init__.py @@ -1,17 +1,18 @@ -from .base_organizer_footer_link_form_set import BaseOrganizerFooterLink -from .customer_update_form import CustomerUpdateForm -from .device_form import DeviceForm -from .event_meta_property_form import EventMetaPropertyForm -from .gate_form import GateForm -from .gift_card_create_form import GiftCardCreateForm -from .gift_card_update_form import GiftCardUpdateForm -from .mail_settings_form import MailSettingsForm -from .organizer_delete_form import OrganizerDeleteForm -from .organizer_footer_link_form import OrganizerFooterLinkForm -from .organizer_form import OrganizerForm -from .organizer_settings_form import OrganizerSettingsForm -from .organizer_update_form import OrganizerUpdateForm -from .sso_client_form import SSOClientForm -from .sso_provider_form import SSOProviderForm -from .team_form import TeamForm -from .web_hook_form import WebHookForm +from .base_organizer_footer_link_form_set import \ + BaseOrganizerFooterLink # NOQA: F401 +from .customer_update_form import CustomerUpdateForm # NOQA: F401 +from .device_form import DeviceForm # NOQA: F401 +from .event_meta_property_form import EventMetaPropertyForm # NOQA: F401 +from .gate_form import GateForm # NOQA: F401 +from .gift_card_create_form import GiftCardCreateForm # NOQA: F401 +from .gift_card_update_form import GiftCardUpdateForm # NOQA: F401 +from .mail_settings_form import MailSettingsForm # NOQA: F401 +from .organizer_delete_form import OrganizerDeleteForm # NOQA: F401 +from .organizer_footer_link_form import OrganizerFooterLinkForm # NOQA: F401 +from .organizer_form import OrganizerForm # NOQA: F401 +from .organizer_settings_form import OrganizerSettingsForm # NOQA: F401 +from .organizer_update_form import OrganizerUpdateForm # NOQA: F401 +from .sso_client_form import SSOClientForm # NOQA: F401 +from .sso_provider_form import SSOProviderForm # NOQA: F401 +from .team_form import TeamForm # NOQA: F401 +from .web_hook_form import WebHookForm # NOQA: F401 diff --git a/src/pretix/control/forms/organizer_forms/web_hook_form.py b/src/pretix/control/forms/organizer_forms/web_hook_form.py index 2d396260b..ad6dd234a 100644 --- a/src/pretix/control/forms/organizer_forms/web_hook_form.py +++ b/src/pretix/control/forms/organizer_forms/web_hook_form.py @@ -1,6 +1,6 @@ from django import forms from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _, pgettext_lazy +from django.utils.translation import pgettext_lazy from django_scopes.forms import SafeModelMultipleChoiceField from pretix.api.models import WebHook diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index e5196c831..bd4760ff3 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -1,11 +1,10 @@ -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from django.views.generic.base import RedirectView from pretix.control.views import ( auth, checkin, dashboards, event, geo, global_settings, item, main, oauth, - orderimport, orders, organizer, pdf, search, shredder, subevents, - typeahead, user, users, vouchers, waitinglist, organizer_views + orderimport, orders, organizer, organizer_views, pdf, search, shredder, + subevents, typeahead, user, users, vouchers, waitinglist, ) from pretix.control.views.auth import CustomAuthorizationView @@ -109,28 +108,29 @@ url(r'^organizer/(?P[^/]+)/customer/(?P[^/]+)/anonymize$', organizer_views.customer_view.CustomerAnonymizeView.as_view(), name='organizer.customer.anonymize'), url(r'^organizer/(?P[^/]+)/ssoproviders$', organizer_views.sso_provider_view.SSOProviderListView.as_view(), - name='organizer.ssoproviders'), + name='organizer.ssoproviders'), url(r'^organizer/(?P[^/]+)/ssoprovider/add$', organizer_views.sso_provider_view.SSOProviderCreateView.as_view(), - name='organizer.ssoprovider.add'), + name='organizer.ssoprovider.add'), url(r'^organizer/(?P[^/]+)/ssoprovider/(?P[^/]+)/edit$', - organizer_views.sso_provider_view.SSOProviderUpdateView.as_view(), - name='organizer.ssoprovider.edit'), + organizer_views.sso_provider_view.SSOProviderUpdateView.as_view(), + name='organizer.ssoprovider.edit'), url(r'^organizer/(?P[^/]+)/ssoprovider/(?P[^/]+)/delete$', - organizer_views.sso_provider_view.SSOProviderDeleteView.as_view(), - name='organizer.ssoprovider.delete'), + organizer_views.sso_provider_view.SSOProviderDeleteView.as_view(), + name='organizer.ssoprovider.delete'), url(r'^organizer/(?P[^/]+)/ssoclients$', organizer_views.sso_provider_view.SSOClientListView.as_view(), - name='organizer.ssoclients'), + name='organizer.ssoclients'), url(r'^organizer/(?P[^/]+)/ssoclient/add$', organizer_views.sso_provider_view.SSOClientCreateView.as_view(), - name='organizer.ssoclient.add'), + name='organizer.ssoclient.add'), url(r'^organizer/(?P[^/]+)/ssoclient/(?P[^/]+)/edit$', - organizer_views.sso_provider_view.SSOClientUpdateView.as_view(), - name='organizer.ssoclient.edit'), + organizer_views.sso_provider_view.SSOClientUpdateView.as_view(), + name='organizer.ssoclient.edit'), url(r'^organizer/(?P[^/]+)/ssoclient/(?P[^/]+)/delete$', - organizer_views.sso_provider_view.SSOClientDeleteView.as_view(), - name='organizer.ssoclient.delete'), + organizer_views.sso_provider_view.SSOClientDeleteView.as_view(), + name='organizer.ssoclient.delete'), url(r'^organizer/(?P[^/]+)/giftcards$', organizer_views.gift_card_view.GiftCardListView.as_view(), name='organizer.giftcards'), url(r'^organizer/(?P[^/]+)/giftcard/add$', organizer_views.gift_card_view.GiftCardCreateView.as_view(), name='organizer.giftcard.add'), - url(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/$', organizer_views.gift_card_view.GiftCardDetailView.as_view(), name='organizer.giftcard'), + url(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/$', + organizer_views.gift_card_view.GiftCardDetailView.as_view(), name='organizer.giftcard'), url(r'^organizer/(?P[^/]+)/giftcard/(?P[^/]+)/edit$', organizer_views.gift_card_view.GiftCardUpdateView.as_view(), name='organizer.giftcard.edit'), url(r'^organizer/(?P[^/]+)/webhooks$', organizer_views.web_hook_view.WebHookListView.as_view(), name='organizer.webhooks'), diff --git a/src/pretix/control/views/auth.py b/src/pretix/control/views/auth.py index e1e91b374..d531ddc25 100644 --- a/src/pretix/control/views/auth.py +++ b/src/pretix/control/views/auth.py @@ -242,7 +242,7 @@ def invite(request, token): return render(request, 'pretixcontrol/auth/invite.html', ctx) -class RepeatedResetDenied(Exception): +class RepeatedResetDenied(Exception): # NOQA: N818 pass @@ -498,6 +498,7 @@ class CustomAuthorizationView(AuthorizationView): """ Override the AuthorizationView to set a JWT cookie after successful login. """ + def get(self, request, *args, **kwargs): # Call the parent method to handle the standard authorization flow response = super().get(request, *args, **kwargs) diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index cf33beaf9..94ea0bc28 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -1,11 +1,10 @@ import json -import operator import re from collections import OrderedDict from decimal import Decimal from io import BytesIO from itertools import groupby -from urllib.parse import urlsplit, urlparse +from urllib.parse import urlparse, urlsplit import qrcode import qrcode.image.svg @@ -14,7 +13,6 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.files import File -from django.utils.http import url_has_allowed_host_and_scheme from django.db import transaction from django.db.models import ProtectedError from django.forms import inlineformset_factory @@ -25,6 +23,7 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.functional import cached_property +from django.utils.http import url_has_allowed_host_and_scheme from django.utils.timezone import now from django.utils.translation import gettext, gettext_lazy as _ from django.views.generic import DeleteView, FormView, ListView @@ -32,10 +31,10 @@ from django.views.generic.detail import SingleObjectMixin from i18nfield.strings import LazyI18nString from i18nfield.utils import I18nJSONEncoder -from pretix.base.configurations import LazyI18nStringListBase from pytz import timezone from pretix.base.channels import get_all_sales_channels +from pretix.base.configurations import LazyI18nStringListBase from pretix.base.email import get_available_placeholders from pretix.base.models import ( Event, LogEntry, Order, RequiredAction, TaxRule, Voucher, @@ -46,8 +45,8 @@ from pretix.base.signals import register_ticket_outputs from pretix.base.templatetags.rich_text import markdown_compile_email from pretix.control.forms.event import ( - CancelSettingsForm, CommentForm, ConfirmTextFormset, EventDeleteForm, EventFooterLink, - EventMetaValueForm, EventSettingsForm, EventUpdateForm, + CancelSettingsForm, CommentForm, ConfirmTextFormset, EventDeleteForm, + EventFooterLink, EventMetaValueForm, EventSettingsForm, EventUpdateForm, InvoiceSettingsForm, ItemMetaPropertyForm, MailSettingsForm, PaymentSettingsForm, ProviderForm, QuickSetupForm, QuickSetupProductFormSet, TaxRuleForm, TaxRuleLineFormSet, @@ -56,7 +55,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.views.user import RecentAuthenticationRequiredMixin from pretix.helpers.database import rolledback_transaction -from pretix.multidomain.urlreverse import get_event_domain, build_absolute_uri +from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain from pretix.presale.style import regenerate_css from ...base.i18n import language @@ -272,10 +271,10 @@ def save_confirm_texts_formset(self, obj): @cached_property def footer_links_formset(self): - return EventFooterLink(self.request.POST if self.request.method == "POST" else None, - event=self.object, - prefix="footer-links", - instance=self.object) + return EventFooterLink(self.request.POST if self.request.method == "POST" else None, + event=self.object, + prefix="footer-links", + instance=self.object) def save_footer_links_formset(self, obj): self.footer_links_formset.save() diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index 8ffeb7bdb..3ff76dbf9 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -35,10 +35,10 @@ from pretix.base.services.tickets import invalidate_cache from pretix.base.signals import quota_availability from pretix.control.forms.item import ( - CategoryForm, DescriptionForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm, - ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemUpdateForm, - ItemVariationForm, ItemVariationsFormSet, QuestionForm, QuestionOptionForm, - QuotaForm, + CategoryForm, DescriptionForm, ItemAddOnForm, ItemAddOnsFormSet, + ItemBundleForm, ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, + ItemUpdateForm, ItemVariationForm, ItemVariationsFormSet, QuestionForm, + QuestionOptionForm, QuotaForm, ) from pretix.control.permissions import ( EventPermissionRequiredMixin, event_permission_required, @@ -450,9 +450,11 @@ def get_success_url(self) -> str: 'event': self.request.event.slug, }) + class DescriptionDelete(QuestionDelete): template_name = 'pretixcontrol/items/desciption_delete.html' + class QuestionMixin: @cached_property def formset(self): @@ -639,10 +641,12 @@ def form_invalid(self, form): messages.error(self.request, _('We could not save your changes. See below for details.')) return super().form_invalid(form) + class DescriptionUpdate(QuestionUpdate): form_class = DescriptionForm template_name = 'pretixcontrol/items/description_edit.html' + class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView): model = Question form_class = QuestionForm @@ -683,10 +687,12 @@ def form_valid(self, form): return ret + class DescriptionCreate(QuestionCreate): form_class = DescriptionForm template_name = 'pretixcontrol/items/description_edit.html' + class QuotaList(PaginationMixin, ListView): model = Quota context_object_name = 'quotas' diff --git a/src/pretix/control/views/main.py b/src/pretix/control/views/main.py index f44a5b62f..9e1c1ee3e 100644 --- a/src/pretix/control/views/main.py +++ b/src/pretix/control/views/main.py @@ -217,7 +217,6 @@ def done(self, form_list, form_dict, **kwargs): except KeyError: copy_data = None - with transaction.atomic(), language(basics_data['locale']): event = form_dict['basics'].instance event.organizer = foundation_data['organizer'] diff --git a/src/pretix/control/views/organizer_views/__init__.py b/src/pretix/control/views/organizer_views/__init__.py index 2761f7c74..b42ce2b8f 100644 --- a/src/pretix/control/views/organizer_views/__init__.py +++ b/src/pretix/control/views/organizer_views/__init__.py @@ -1,10 +1,15 @@ -from .customer_view import CustomerDetailView -from .device_view import DeviceListView -from .gate_view import GateListView -from .gift_card_view import GiftCardDetailView -from .mail_settings_preview import MailSettingsPreview -from .organizer_detail_view_mixin import OrganizerDetailViewMixin -from .organizer_view import OrganizerTeamView -from .sso_provider_view import SSOProviderListView -from .team_view import TeamListView, TeamUpdateView, TeamCreateView, TeamMemberView, TeamDeleteView -from .web_hook_view import WebHookCreateView, WebHookListView, WebHookUpdateView +from .customer_view import CustomerDetailView # noqa +from .device_view import DeviceListView # noqa +from .gate_view import GateListView # noqa +from .gift_card_view import GiftCardDetailView # noqa +from .mail_settings_preview import MailSettingsPreview # noqa +from .organizer_detail_view_mixin import OrganizerDetailViewMixin # noqa +from .organizer_view import OrganizerTeamView # noqa +from .sso_provider_view import SSOProviderListView # noqa +from .team_view import ( # noqa + TeamCreateView, TeamDeleteView, TeamListView, TeamMemberView, + TeamUpdateView, +) +from .web_hook_view import ( # noqa + WebHookCreateView, WebHookListView, WebHookUpdateView, +) diff --git a/src/pretix/control/views/organizer_views/device_view.py b/src/pretix/control/views/organizer_views/device_view.py index 0c75b64f2..7342188a2 100644 --- a/src/pretix/control/views/organizer_views/device_view.py +++ b/src/pretix/control/views/organizer_views/device_view.py @@ -2,7 +2,7 @@ from django.conf import settings from django.contrib import messages -from django.http import HttpResponseBadRequest, JsonResponse +from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.functional import cached_property diff --git a/src/pretix/control/views/organizer_views/gift_card_view.py b/src/pretix/control/views/organizer_views/gift_card_view.py index 5cd68dc99..042039c51 100644 --- a/src/pretix/control/views/organizer_views/gift_card_view.py +++ b/src/pretix/control/views/organizer_views/gift_card_view.py @@ -210,47 +210,6 @@ def form_valid(self, form): )) -class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView): - template_name = 'pretixcontrol/organizers/giftcard_create.html' - permission = 'can_manage_gift_cards' - form_class = GiftCardCreateForm - success_url = 'invalid' - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - any_event = self.request.organizer.events.first() - kwargs['initial'] = { - 'currency': any_event.currency if any_event else settings.DEFAULT_CURRENCY, - 'secret': gen_giftcard_secret(self.request.organizer.settings.giftcard_length) - } - kwargs['organizer'] = self.request.organizer - return kwargs - - @transaction.atomic() - def post(self, request, *args, **kwargs): - return super().post(request, *args, **kwargs) - - def form_valid(self, form): - messages.success(self.request, _('The gift card has been created and can now be used.')) - form.instance.issuer = self.request.organizer - super().form_valid(form) - form.instance.transactions.create( - value=form.cleaned_data['value'] - ) - form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={}) - if form.cleaned_data['value']: - form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={ - 'value': form.cleaned_data['value'] - }) - return redirect(reverse( - 'control:organizer.giftcard', - kwargs={ - 'organizer': self.request.organizer.slug, - 'giftcard': self.object.pk - } - )) - - class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView): template_name = 'pretixcontrol/organizers/giftcard_edit.html' permission = 'can_manage_gift_cards' diff --git a/src/pretix/control/views/organizer_views/organizer_view.py b/src/pretix/control/views/organizer_views/organizer_view.py index 6a14b743f..c973b6138 100644 --- a/src/pretix/control/views/organizer_views/organizer_view.py +++ b/src/pretix/control/views/organizer_views/organizer_view.py @@ -1,5 +1,5 @@ from django.contrib import messages -from django.core.exceptions import PermissionDenied, ValidationError +from django.core.exceptions import PermissionDenied from django.core.files import File from django.db import transaction from django.db.models import Max, Min, Prefetch, ProtectedError @@ -17,7 +17,7 @@ from pretix.base.models.organizer import Organizer, Team from pretix.base.settings import SETTINGS_AFFECTING_CSS from pretix.control.forms.filter import EventFilterForm, OrganizerFilterForm -from pretix.control.forms.organizer import BaseOrganizerFooterLink, OrganizerFooterLink +from pretix.control.forms.organizer import OrganizerFooterLink from pretix.control.forms.organizer_forms import ( MailSettingsForm, OrganizerDeleteForm, OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, diff --git a/src/pretix/control/views/organizer_views/sso_provider_view.py b/src/pretix/control/views/organizer_views/sso_provider_view.py index f15e77b9c..61da46b03 100644 --- a/src/pretix/control/views/organizer_views/sso_provider_view.py +++ b/src/pretix/control/views/organizer_views/sso_provider_view.py @@ -23,7 +23,7 @@ class SSOProviderListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM def get_queryset(self): return self.request.organizer.sso_providers.all() - + class SSOProviderCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView): model = CustomerSSOProvider @@ -56,7 +56,7 @@ def form_valid(self, form): def form_invalid(self, form): messages.error(self.request, _('Your changes could not be saved.')) return super().form_invalid(form) - + class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView): model = CustomerSSOProvider @@ -97,7 +97,7 @@ def form_valid(self, form): def form_invalid(self, form): messages.error(self.request, _('Your changes could not be saved.')) return super().form_invalid(form) - + class SSOProviderDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView): model = CustomerSSOProvider @@ -127,7 +127,7 @@ def delete(self, request, *args, **kwargs): self.object.delete() messages.success(request, _('The selected object has been deleted.')) return redirect(success_url) - + class SSOClientListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): model = CustomerSSOClient @@ -137,7 +137,7 @@ class SSOClientListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix def get_queryset(self): return self.request.organizer.sso_clients.all() - + class SSOClientCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView): model = CustomerSSOClient @@ -176,7 +176,7 @@ def form_valid(self, form): def form_invalid(self, form): messages.error(self.request, _('Your changes could not be saved.')) return super().form_invalid(form) - + class SSOClientUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView): model = CustomerSSOClient @@ -225,7 +225,7 @@ def form_valid(self, form): def form_invalid(self, form): messages.error(self.request, _('Your changes could not be saved.')) return super().form_invalid(form) - + class SSOClientDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DeleteView): model = CustomerSSOClient diff --git a/src/pretix/control/views/pdf.py b/src/pretix/control/views/pdf.py index 4be9348aa..b01da2a7e 100644 --- a/src/pretix/control/views/pdf.py +++ b/src/pretix/control/views/pdf.py @@ -17,7 +17,7 @@ from django.utils.timezone import now from django.utils.translation import gettext as _ from django.views.generic import TemplateView -from pypdf import PdfReader, PdfWriter +from pypdf import PdfWriter from reportlab.lib.units import mm from pretix.base.i18n import language diff --git a/src/pretix/control/views/subevents.py b/src/pretix/control/views/subevents.py index fc076aaca..05e42bee3 100644 --- a/src/pretix/control/views/subevents.py +++ b/src/pretix/control/views/subevents.py @@ -139,7 +139,7 @@ def form_valid(self, form): if not self.object.allow_delete(): messages.error(self.request, pgettext_lazy('subevent', 'A date can not be deleted if orders already have been ' - 'placed.')) + 'placed.')) return HttpResponseRedirect(self.get_success_url()) else: self.object.log_action('pretix.subevent.deleted', user=self.request.user) @@ -1100,7 +1100,6 @@ def save_list_formset(self, log_entries): if to_delete_list_ids: CheckinList.objects.filter(id__in=to_delete_list_ids).delete() - def save_quota_formset(self, log_entries): if not self.quota_formset.has_changed(): return @@ -1194,7 +1193,6 @@ def save_quota_formset(self, log_entries): if to_delete_quota_ids: Quota.objects.filter(id__in=to_delete_quota_ids).delete() - def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['subevents'] = self.get_queryset() @@ -1346,7 +1344,7 @@ def post(self, request, *args, **kwargs): form.is_valid() and self.quota_formset.is_valid() and (not self.list_formset or self.list_formset.is_valid()) and - all(f.is_valid() for f in self.itemvar_forms)and + all(f.is_valid() for f in self.itemvar_forms) and all(f.is_valid() for f in self.meta_forms) ) if is_valid: diff --git a/src/pretix/eventyay_common/context.py b/src/pretix/eventyay_common/context.py index 0cf15931f..44c15de82 100644 --- a/src/pretix/eventyay_common/context.py +++ b/src/pretix/eventyay_common/context.py @@ -2,8 +2,7 @@ from django.conf import settings from django.db.models import Q -from django.urls import Resolver404, get_script_prefix, resolve -from django.urls import reverse +from django.urls import Resolver404, get_script_prefix, resolve, reverse from django.utils.translation import gettext_lazy as _ from pretix.base.models.auth import StaffSession diff --git a/src/pretix/eventyay_common/urls.py b/src/pretix/eventyay_common/urls.py index 8dcb4cfac..981bf8d51 100644 --- a/src/pretix/eventyay_common/urls.py +++ b/src/pretix/eventyay_common/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path as url, include +from django.urls import include, re_path as url from pretix.eventyay_common.views import dashboards, event, organizer, team diff --git a/src/pretix/eventyay_common/views/dashboards.py b/src/pretix/eventyay_common/views/dashboards.py index 45713d1ee..773398e38 100644 --- a/src/pretix/eventyay_common/views/dashboards.py +++ b/src/pretix/eventyay_common/views/dashboards.py @@ -9,4 +9,3 @@ def organiser_dashboard(request): 'video_component': '#', } return render(request, 'eventyay_common/dashboard/dashboard.html', context) - diff --git a/src/pretix/eventyay_common/views/event.py b/src/pretix/eventyay_common/views/event.py index fdd33ce49..97df3e65d 100644 --- a/src/pretix/eventyay_common/views/event.py +++ b/src/pretix/eventyay_common/views/event.py @@ -1,8 +1,8 @@ from django.conf import settings from django.contrib import messages from django.db import transaction -from django.db.models import Prefetch, Min, Max, F -from django.db.models.functions import Greatest, Coalesce +from django.db.models import F, Max, Min, Prefetch +from django.db.models.functions import Coalesce, Greatest from django.shortcuts import redirect from django.urls import reverse from django.utils.functional import cached_property @@ -12,10 +12,12 @@ from pretix.base.forms import SafeSessionWizardView from pretix.base.i18n import language -from pretix.base.models import Event, EventMetaValue, Quota, Organizer +from pretix.base.models import Event, EventMetaValue, Organizer, Quota from pretix.base.services import tickets from pretix.base.services.quotas import QuotaAvailability -from pretix.control.forms.event import EventWizardFoundationForm, EventWizardBasicsForm, EventUpdateForm +from pretix.control.forms.event import ( + EventUpdateForm, EventWizardBasicsForm, EventWizardFoundationForm, +) from pretix.control.forms.filter import EventFilterForm from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.views import PaginationMixin, UpdateView diff --git a/src/pretix/eventyay_common/views/organizer.py b/src/pretix/eventyay_common/views/organizer.py index 00cbf4f33..8deb15c8f 100644 --- a/src/pretix/eventyay_common/views/organizer.py +++ b/src/pretix/eventyay_common/views/organizer.py @@ -11,10 +11,11 @@ from pretix.base.models import Organizer, Team from pretix.control.forms.filter import OrganizerFilterForm -from pretix.control.views import PaginationMixin, CreateView, UpdateView -from ..tasks import send_organizer_webhook +from pretix.control.views import CreateView, PaginationMixin, UpdateView + from ...control.forms.organizer_forms import OrganizerForm, OrganizerUpdateForm from ...control.permissions import OrganizerPermissionRequiredMixin +from ..tasks import send_organizer_webhook logger = logging.getLogger(__name__) diff --git a/src/pretix/eventyay_common/views/team.py b/src/pretix/eventyay_common/views/team.py index 84a7fdaca..4217ddebc 100644 --- a/src/pretix/eventyay_common/views/team.py +++ b/src/pretix/eventyay_common/views/team.py @@ -4,13 +4,14 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.translation import gettext_lazy -from django.views.generic import ListView, UpdateView, DeleteView, CreateView +from django.views.generic import CreateView, DeleteView, ListView, UpdateView from pretix.base.models import Team from pretix.control.views.organizer import OrganizerDetailViewMixin -from ..tasks import send_team_webhook + from ...control.forms.organizer_forms import TeamForm from ...control.permissions import OrganizerPermissionRequiredMixin +from ..tasks import send_team_webhook class TeamListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): diff --git a/src/pretix/helpers/apps.py b/src/pretix/helpers/apps.py index 4637af7cb..2ead6c5a0 100644 --- a/src/pretix/helpers/apps.py +++ b/src/pretix/helpers/apps.py @@ -1,5 +1,6 @@ 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 b74eb1162..0a229eaff 100644 --- a/src/pretix/helpers/database.py +++ b/src/pretix/helpers/database.py @@ -6,7 +6,7 @@ from django.utils.functional import lazy -class DummyRollbackException(Exception): +class DummyRollbackException(Exception): # NOQA: N818 pass @@ -107,7 +107,9 @@ def as_sql(self, compiler, connection): params = lhs_params + rhs_params return '%s <> %s' % (lhs, rhs), params + def _of_self(): return ("self",) if connection.features.has_select_for_update_of else () + OF_SELF = lazy(_of_self, tuple)() diff --git a/src/pretix/helpers/http.py b/src/pretix/helpers/http.py index 82c91c9c7..88bb6c8c4 100644 --- a/src/pretix/helpers/http.py +++ b/src/pretix/helpers/http.py @@ -1,5 +1,7 @@ from django.conf import settings -from django.http import StreamingHttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect +from django.http import ( + HttpResponsePermanentRedirect, HttpResponseRedirect, StreamingHttpResponse, +) class ChunkBasedFileResponse(StreamingHttpResponse): @@ -20,6 +22,7 @@ def get_client_ip(request): ip = x_forwarded_for.split(',')[0] return ip + def redirect_to_url(to, permanent=False): redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect return redirect_class(to) diff --git a/src/pretix/helpers/jsonlogic.py b/src/pretix/helpers/jsonlogic.py index 9fbc624ce..9d849717b 100644 --- a/src/pretix/helpers/jsonlogic.py +++ b/src/pretix/helpers/jsonlogic.py @@ -41,7 +41,7 @@ def soft_equals(a, b): def hard_equals(a, b): """Implements the '===' operator.""" - if type(a) != type(b): + if not isinstance(a, type(b)): return False return a == b diff --git a/src/pretix/helpers/security.py b/src/pretix/helpers/security.py index 798202d96..73d168d1d 100644 --- a/src/pretix/helpers/security.py +++ b/src/pretix/helpers/security.py @@ -4,11 +4,11 @@ from django.conf import settings -class SessionInvalid(Exception): +class SessionInvalid(Exception): # NOQA: N818 pass -class SessionReauthRequired(Exception): +class SessionReauthRequired(Exception): # NOQA: N818 pass diff --git a/src/pretix/helpers/templatetags/jsonfield.py b/src/pretix/helpers/templatetags/jsonfield.py index b0274d2e8..4a4440734 100644 --- a/src/pretix/helpers/templatetags/jsonfield.py +++ b/src/pretix/helpers/templatetags/jsonfield.py @@ -2,9 +2,11 @@ # https://github.com/raphaelm/django-jsonfallback 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) + "}" @@ -20,7 +22,7 @@ def sqlite_compile_json_path(key_transforms): path.append(key_transform) return ''.join(path) - + class JSONExtract(Expression): def __init__(self, expression, *path, output_field=JSONField(), **extra): super().__init__(output_field=output_field) @@ -28,7 +30,6 @@ def __init__(self, expression, *path, output_field=JSONField(), **extra): 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 diff --git a/src/pretix/helpers/u2f.py b/src/pretix/helpers/u2f.py index e80ff824f..67f9ac226 100644 --- a/src/pretix/helpers/u2f.py +++ b/src/pretix/helpers/u2f.py @@ -1,5 +1,4 @@ from base64 import urlsafe_b64decode, urlsafe_b64encode -from typing import Union from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import load_der_public_key diff --git a/src/pretix/multidomain/__init__.py b/src/pretix/multidomain/__init__.py index 4cf57646c..58e68e499 100644 --- a/src/pretix/multidomain/__init__.py +++ b/src/pretix/multidomain/__init__.py @@ -1,6 +1,7 @@ from django.urls import URLPattern from django.urls.resolvers import RegexPattern + 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 index c48cd280b..92bad9922 100644 --- a/src/pretix/multidomain/apps.py +++ b/src/pretix/multidomain/apps.py @@ -1,5 +1,6 @@ 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 82ea5e014..6394312d7 100644 --- a/src/pretix/multidomain/event_domain_urlconf.py +++ b/src/pretix/multidomain/event_domain_urlconf.py @@ -1,8 +1,7 @@ import importlib.util from django.apps import apps -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import event_patterns, locale_patterns diff --git a/src/pretix/multidomain/maindomain_urlconf.py b/src/pretix/multidomain/maindomain_urlconf.py index 83391aa66..e57fa3def 100644 --- a/src/pretix/multidomain/maindomain_urlconf.py +++ b/src/pretix/multidomain/maindomain_urlconf.py @@ -1,8 +1,7 @@ import importlib.util from django.apps import apps -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from django.views.generic import TemplateView from pretix.multidomain.plugin_handler import plugin_event_urls diff --git a/src/pretix/multidomain/migrations/0001_initial.py b/src/pretix/multidomain/migrations/0001_initial.py index e414f4969..dfa3e7e59 100644 --- a/src/pretix/multidomain/migrations/0001_initial.py +++ b/src/pretix/multidomain/migrations/0001_initial.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/multidomain/organizer_domain_urlconf.py b/src/pretix/multidomain/organizer_domain_urlconf.py index 61f16efd5..4fbcc2507 100644 --- a/src/pretix/multidomain/organizer_domain_urlconf.py +++ b/src/pretix/multidomain/organizer_domain_urlconf.py @@ -1,8 +1,7 @@ import importlib.util from django.apps import apps -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from pretix.multidomain.plugin_handler import plugin_event_urls from pretix.presale.urls import ( diff --git a/src/pretix/multidomain/templatetags/eventurl.py b/src/pretix/multidomain/templatetags/eventurl.py index 6b7011789..c0da3214f 100644 --- a/src/pretix/multidomain/templatetags/eventurl.py +++ b/src/pretix/multidomain/templatetags/eventurl.py @@ -1,11 +1,11 @@ from django import template +from django.conf import settings from django.template import TemplateSyntaxError from django.template.base import kwarg_re from django.template.defaulttags import URLNode from django.urls import NoReverseMatch from django.utils.encoding import smart_str from django.utils.html import conditional_escape -from django.conf import settings from pretix.multidomain.urlreverse import build_absolute_uri diff --git a/src/pretix/multidomain/urlreverse.py b/src/pretix/multidomain/urlreverse.py index 74d50dd85..24371f905 100644 --- a/src/pretix/multidomain/urlreverse.py +++ b/src/pretix/multidomain/urlreverse.py @@ -1,8 +1,8 @@ -import logging import datetime -import jwt +import logging from urllib.parse import urljoin, urlsplit +import jwt from django.conf import settings from django.db.models import Q from django.urls import reverse @@ -13,6 +13,7 @@ logger = logging.getLogger(__name__) + def get_event_domain(event, fallback=False, return_info=False): assert isinstance(event, Event) suffix = ('_fallback' if fallback else '') + ('_info' if return_info else '') diff --git a/src/pretix/plugins/badges/apps.py b/src/pretix/plugins/badges/apps.py index fe4155a57..25b568793 100644 --- a/src/pretix/plugins/badges/apps.py +++ b/src/pretix/plugins/badges/apps.py @@ -1,9 +1,10 @@ -import os import json +import os from django.apps import AppConfig -from django.utils.translation import gettext, gettext_lazy as _ from django.core.files.base import ContentFile +from django.utils.translation import gettext, gettext_lazy as _ + from pretix import __version__ as version diff --git a/src/pretix/plugins/badges/exporters.py b/src/pretix/plugins/badges/exporters.py index aef82e84c..d056548f9 100644 --- a/src/pretix/plugins/badges/exporters.py +++ b/src/pretix/plugins/badges/exporters.py @@ -30,6 +30,7 @@ from ...helpers.templatetags.jsonfield import JSONExtract + def _renderer(event, layout): if layout is None: return None diff --git a/src/pretix/plugins/badges/migrations/0001_initial.py b/src/pretix/plugins/badges/migrations/0001_initial.py index 2c5abd72a..f3683a13b 100644 --- a/src/pretix/plugins/badges/migrations/0001_initial.py +++ b/src/pretix/plugins/badges/migrations/0001_initial.py @@ -1,6 +1,7 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 from django.db import migrations, models + import pretix.base.models.base import pretix.plugins.badges.models diff --git a/src/pretix/plugins/badges/migrations/0002_initial.py b/src/pretix/plugins/badges/migrations/0002_initial.py index b0610286e..e3f37afa1 100644 --- a/src/pretix/plugins/badges/migrations/0002_initial.py +++ b/src/pretix/plugins/badges/migrations/0002_initial.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/plugins/badges/models.py b/src/pretix/plugins/badges/models.py index bb83944f9..0b44ed65a 100644 --- a/src/pretix/plugins/badges/models.py +++ b/src/pretix/plugins/badges/models.py @@ -1,11 +1,11 @@ -import string import json +import string +from io import BytesIO from django.db import models from django.utils.crypto import get_random_string from django.utils.translation import gettext_lazy as _ from pypdf import PdfReader -from io import BytesIO from pretix.base.models import LoggedModel @@ -41,9 +41,8 @@ class BadgeLayout(LoggedModel): ) size = models.TextField( - default='[{"width": 148, "height": 105, "orientation": "landscape"}]' + default='[{"width": 148, "height": 105, "orientation": "landscape"}]' ) - background = models.FileField(null=True, blank=True, upload_to=bg_name, max_length=255) @@ -61,8 +60,8 @@ def save(self, *args, **kwargs): buffer.seek(0) reader = PdfReader(buffer) page = reader.pages[0] - width = round(float(page.mediabox.width)*0.352777778, 2) - height = round(float(page.mediabox.height)*0.352777778, 2) + width = round(float(page.mediabox.width) * 0.352777778, 2) + height = round(float(page.mediabox.height) * 0.352777778, 2) orientation = "portrait" if height > width else "landscape" self.size = json.dumps([{"width": width, "height": height, "orientation": orientation}]) super().save(*args, **kwargs) diff --git a/src/pretix/plugins/banktransfer/migrations/0002_initial.py b/src/pretix/plugins/banktransfer/migrations/0002_initial.py index dffb17db0..226211806 100644 --- a/src/pretix/plugins/banktransfer/migrations/0002_initial.py +++ b/src/pretix/plugins/banktransfer/migrations/0002_initial.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/plugins/paypal/migrations/0002_initial.py b/src/pretix/plugins/paypal/migrations/0002_initial.py index 6d40c60fa..de8bf7ec4 100644 --- a/src/pretix/plugins/paypal/migrations/0002_initial.py +++ b/src/pretix/plugins/paypal/migrations/0002_initial.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/pretix/plugins/paypal/urls.py b/src/pretix/plugins/paypal/urls.py index 8eba945f9..3857517a5 100644 --- a/src/pretix/plugins/paypal/urls.py +++ b/src/pretix/plugins/paypal/urls.py @@ -1,5 +1,4 @@ -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from pretix.multidomain import event_url diff --git a/src/pretix/plugins/ticketoutputpdf/exporters.py b/src/pretix/plugins/ticketoutputpdf/exporters.py index 081003caa..9efa34bab 100644 --- a/src/pretix/plugins/ticketoutputpdf/exporters.py +++ b/src/pretix/plugins/ticketoutputpdf/exporters.py @@ -18,7 +18,6 @@ from pretix.base.settings import PERSON_NAME_SCHEMES from ...helpers.templatetags.jsonfield import JSONExtract - from .ticketoutput import PdfTicketOutput diff --git a/src/pretix/plugins/ticketoutputpdf/migrations/0001_initial.py b/src/pretix/plugins/ticketoutputpdf/migrations/0001_initial.py index 6e0bd16db..0b57dbbf3 100644 --- a/src/pretix/plugins/ticketoutputpdf/migrations/0001_initial.py +++ b/src/pretix/plugins/ticketoutputpdf/migrations/0001_initial.py @@ -1,7 +1,8 @@ # Generated by Django 4.2.11 on 2024-05-08 01:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models + import pretix.base.models.base import pretix.plugins.ticketoutputpdf.models diff --git a/src/pretix/presale/checkoutflowstep/__init__.py b/src/pretix/presale/checkoutflowstep/__init__.py index 7df484f1f..d3137edd0 100644 --- a/src/pretix/presale/checkoutflowstep/__init__.py +++ b/src/pretix/presale/checkoutflowstep/__init__.py @@ -1,7 +1,7 @@ -from .add_ons_step import AddOnsStep -from .base_checkout_flow_step import BaseCheckoutFlowStep -from .confirm_step import ConfirmStep -from .customer_step import CustomerStep -from .payment_step import PaymentStep -from .questions_step import QuestionsStep -from .template_flow_step import TemplateFlowStep +from .add_ons_step import AddOnsStep # NOQA: F401 +from .base_checkout_flow_step import BaseCheckoutFlowStep # NOQA: F401 +from .confirm_step import ConfirmStep # NOQA: F401 +from .customer_step import CustomerStep # NOQA: F401 +from .payment_step import PaymentStep # NOQA: F401 +from .questions_step import QuestionsStep # NOQA: F401 +from .template_flow_step import TemplateFlowStep # NOQA: F401 diff --git a/src/pretix/presale/checkoutflowstep/base_checkout_flow_step.py b/src/pretix/presale/checkoutflowstep/base_checkout_flow_step.py index 599cfc0a7..62dd46a07 100644 --- a/src/pretix/presale/checkoutflowstep/base_checkout_flow_step.py +++ b/src/pretix/presale/checkoutflowstep/base_checkout_flow_step.py @@ -1,7 +1,8 @@ from django.http import HttpResponseNotAllowed -from django.utils.translation import pgettext_lazy from django.utils.functional import cached_property +from django.utils.translation import pgettext_lazy from django_scopes import scopes_disabled + from pretix.base.models import InvoiceAddress from pretix.multidomain.urlreverse import eventreverse from pretix.presale.views.cart import cart_session diff --git a/src/pretix/presale/checkoutflowstep/confirm_step.py b/src/pretix/presale/checkoutflowstep/confirm_step.py index 68abd1d83..cde8df020 100644 --- a/src/pretix/presale/checkoutflowstep/confirm_step.py +++ b/src/pretix/presale/checkoutflowstep/confirm_step.py @@ -39,9 +39,9 @@ def is_completed(self, request, warn=False): @cached_property def address_asked(self): return ( - self.request.event.settings.invoice_address_asked - and (not self.request.event.settings.invoice_address_not_asked_free or not get_cart_is_free( - self.request)) + self.request.event.settings.invoice_address_asked + and (not self.request.event.settings.invoice_address_not_asked_free or not get_cart_is_free( + self.request)) ) def get_context_data(self, **kwargs): diff --git a/src/pretix/presale/checkoutflowstep/customer_step.py b/src/pretix/presale/checkoutflowstep/customer_step.py index 41f4cc0ed..ea2b36806 100644 --- a/src/pretix/presale/checkoutflowstep/customer_step.py +++ b/src/pretix/presale/checkoutflowstep/customer_step.py @@ -5,7 +5,9 @@ from pretix.base.models import Customer from pretix.helpers.http import redirect_to_url -from pretix.presale.forms.customer_forms import AuthenticationForm, RegistrationForm +from pretix.presale.forms.customer_forms import ( + AuthenticationForm, RegistrationForm, +) from pretix.presale.utils import customer_login from pretix.presale.views import CartMixin from pretix.presale.views.questions import QuestionsViewMixin diff --git a/src/pretix/presale/checkoutflowstep/questions_step.py b/src/pretix/presale/checkoutflowstep/questions_step.py index 6ec033dbc..e02591de2 100644 --- a/src/pretix/presale/checkoutflowstep/questions_step.py +++ b/src/pretix/presale/checkoutflowstep/questions_step.py @@ -5,16 +5,18 @@ from django.core.validators import EmailValidator from django.shortcuts import redirect from django.utils.functional import cached_property -from django.utils.translation import ( - gettext_lazy as _, pgettext_lazy, -) +from django.utils.translation import gettext_lazy as _, pgettext_lazy + from pretix.base.models import TaxRule from pretix.base.services.cart import update_tax_rates from pretix.presale.checkoutflowstep.template_flow_step import TemplateFlowStep - -from pretix.presale.forms.checkout import ContactForm, InvoiceNameForm, InvoiceAddressForm -from pretix.presale.signals import checkout_all_optional, contact_form_fields_overrides, question_form_fields_overrides, \ - question_form_fields +from pretix.presale.forms.checkout import ( + ContactForm, InvoiceAddressForm, InvoiceNameForm, +) +from pretix.presale.signals import ( + checkout_all_optional, contact_form_fields_overrides, question_form_fields, + question_form_fields_overrides, +) from pretix.presale.views import CartMixin, get_cart_is_free from pretix.presale.views.cart import get_or_create_cart_id from pretix.presale.views.questions import QuestionsViewMixin @@ -51,8 +53,8 @@ def contact_form(self): wd = self.cart_session.get('widget_data', {}) initial = { 'email': ( - self.cart_session.get('email', '') or - wd.get('email', '') + self.cart_session.get('email', '') or + wd.get('email', '') ), 'phone': wd.get('phone', None) } @@ -162,9 +164,9 @@ def invoice_form(self): @cached_property def address_asked(self): return ( - self.request.event.settings.invoice_address_asked - and (not self.request.event.settings.invoice_address_not_asked_free or not get_cart_is_free( - self.request)) + self.request.event.settings.invoice_address_asked + and (not self.request.event.settings.invoice_address_not_asked_free or not get_cart_is_free( + self.request)) ) def post(self, request): @@ -250,16 +252,16 @@ def question_is_visible(parentid, qvals): if parentid not in answ: return False return ( - ('True' in qvals and answ[parentid].answer == 'True') - or ('False' in qvals and answ[parentid].answer == 'False') - or (any(qval in [o.identifier for o in answ[parentid].options.all()] for qval in qvals)) + ('True' in qvals and answ[parentid].answer == 'True') + or ('False' in qvals and answ[parentid].answer == 'False') + or (any(qval in [o.identifier for o in answ[parentid].options.all()] for qval in qvals)) ) def question_is_required(q): return ( - q.required and - (not q.dependency_question_id or question_is_visible(q.dependency_question_id, - q.dependency_values)) + q.required and + (not q.dependency_question_id or question_is_visible(q.dependency_question_id, + q.dependency_values)) ) for q in cp.item.questions_to_ask: diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py index a09ed51fd..23700cb1f 100644 --- a/src/pretix/presale/context.py +++ b/src/pretix/presale/context.py @@ -83,7 +83,7 @@ def _default_context(request): _footer += response else: _footer.append(response) - + # Append footer links to the _footer list _footer += request.event.cache.get_or_set( 'footer_links', # The cache key diff --git a/src/pretix/presale/forms/customer.py b/src/pretix/presale/forms/customer.py index 0fb4f6c53..8714d608b 100644 --- a/src/pretix/presale/forms/customer.py +++ b/src/pretix/presale/forms/customer.py @@ -1,4 +1,5 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator + class TokenGenerator(PasswordResetTokenGenerator): key_salt = "$2a$12$xoUY1cRjQ0gWdF/LI8rmV.Ex5pWuhF5d.sgUJsAV0Ki7CUXZCPn8y" diff --git a/src/pretix/presale/forms/customer_forms/__init__.py b/src/pretix/presale/forms/customer_forms/__init__.py index 045a90dc0..84432979f 100644 --- a/src/pretix/presale/forms/customer_forms/__init__.py +++ b/src/pretix/presale/forms/customer_forms/__init__.py @@ -1,6 +1,7 @@ -from .authentication_form import AuthenticationForm -from .change_info_form import ChangeInfoForm -from .password_form import ( +from .authentication_form import AuthenticationForm # noqa +from .change_info_form import ChangeInfoForm # noqa +from .password_form import ( # noqa ChangePasswordForm, ResetPasswordForm, SetPasswordForm, ) -from .registration_form import RegistrationForm + +from .registration_form import RegistrationForm # noqa diff --git a/src/pretix/presale/forms/customer_forms/password_form.py b/src/pretix/presale/forms/customer_forms/password_form.py index 0d86f6117..b98ed0429 100644 --- a/src/pretix/presale/forms/customer_forms/password_form.py +++ b/src/pretix/presale/forms/customer_forms/password_form.py @@ -70,8 +70,7 @@ def clean_email(self): if 'email' not in self.cleaned_data: return try: - self.customer = self.request.organizer.customers.get(email=self.cleaned_data['email'].lower() - , provider__isnull=True) + self.customer = self.request.organizer.customers.get(email=self.cleaned_data['email'].lower(), provider__isnull=True) return self.customer.email except Customer.DoesNotExist: raise forms.ValidationError(self.error_messages['unknown'], code='unknown') diff --git a/src/pretix/presale/forms/renderers.py b/src/pretix/presale/forms/renderers.py index f53ef8b08..05411c2d3 100644 --- a/src/pretix/presale/forms/renderers.py +++ b/src/pretix/presale/forms/renderers.py @@ -38,7 +38,7 @@ def render_label(content, label_for=None, label_class=None, label_title='', labe # usually checkboxes have overall empty labels and special labels per checkbox # => remove for-attribute as well as "required"-text appended to label if 'for' in attrs: - del(attrs['for']) + del (attrs['for']) else: opt += ', {}'.format(pgettext('form', 'required')) if not optional else '' diff --git a/src/pretix/presale/style.py b/src/pretix/presale/style.py index 35145c4ee..00e3924ae 100644 --- a/src/pretix/presale/style.py +++ b/src/pretix/presale/style.py @@ -58,7 +58,7 @@ def static(path): if object.settings.get('theme_color_background'): sassrules.append('$body-bg: {};'.format(object.settings.get('theme_color_background'))) if object.settings.get('hover_button_color'): - sassrules.append('$hover-button-color: {};'.format(object.settings.get('hover_button_color'))) + sassrules.append('$hover-button-color: {};'.format(object.settings.get('hover_button_color'))) if not object.settings.get('theme_round_borders'): sassrules.append('$border-radius-base: 0;') sassrules.append('$border-radius-large: 0;') diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 4cd7f2037..db9710d64 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -1,5 +1,4 @@ -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from django.views.decorators.csrf import csrf_exempt import pretix.presale.views.cart @@ -137,9 +136,9 @@ name='organizer.widget.productlist'), url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'), url(r'^account/login/(?P[0-9]+)/$', pretix.presale.views.customer.SSOLoginView.as_view(), - name='organizer.customer.login'), + name='organizer.customer.login'), url(r'^account/login/(?P[0-9]+)/return$', pretix.presale.views.customer.SSOLoginReturnView.as_view(), - name='organizer.customer.login.return'), + name='organizer.customer.login.return'), url(r'^account/login$', pretix.presale.views.customer_view.LoginView.as_view(), name='organizer.customer.login'), url(r'^account/logout$', pretix.presale.views.customer_view.LogoutView.as_view(), name='organizer.customer.logout'), url(r'^account/register$', pretix.presale.views.customer_view.RegistrationView.as_view(), @@ -159,15 +158,15 @@ url(r'^account/$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profile'), url(r'^account/order/$', pretix.presale.views.customer.OrderView.as_view(), name='organizer.customer.order'), url(r'^oauth2/v1/authorize$', pretix.presale.views.open_id_connect.AuthorizeView.as_view(), - name='organizer.oauth2.v1.authorize'), + name='organizer.oauth2.v1.authorize'), url(r'^oauth2/v1/token$', pretix.presale.views.open_id_connect.TokenView.as_view(), - name='organizer.oauth2.v1.token'), + name='organizer.oauth2.v1.token'), url(r'^oauth2/v1/userinfo$', pretix.presale.views.open_id_connect.UserInfoView.as_view(), - name='organizer.oauth2.v1.userinfo'), + name='organizer.oauth2.v1.userinfo'), url(r'^oauth2/v1/keys$', pretix.presale.views.open_id_connect.KeysView.as_view(), - name='organizer.oauth2.v1.jwks'), + name='organizer.oauth2.v1.jwks'), url(r'^.well-known/openid-configuration$', pretix.presale.views.open_id_connect.WellKnownConfigurationView.as_view(), - name='organizer.oauth2.v1.configuration'), + name='organizer.oauth2.v1.configuration'), ] locale_patterns = [ diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py index 5a7c151e4..6ff2c0d25 100644 --- a/src/pretix/presale/utils.py +++ b/src/pretix/presale/utils.py @@ -1,5 +1,5 @@ -import warnings import time +import warnings from importlib import import_module from urllib.parse import urljoin @@ -19,7 +19,7 @@ from django_scopes import scope from pretix.base.middleware import LocaleMiddleware -from pretix.base.models import Event, Organizer, Customer +from pretix.base.models import Customer, Event, Organizer from pretix.multidomain.urlreverse import ( get_event_domain, get_organizer_domain, ) @@ -232,9 +232,9 @@ def _detect_event(request, require_live=True, require_plugin=None): .select_related('organizer') \ .using(db) \ .get( - slug=url.kwargs['event'], - organizer__slug=url.kwargs['organizer'] - ) + slug=url.kwargs['event'], + organizer__slug=url.kwargs['organizer'] + ) request.organizer = request.event.organizer domain = get_event_domain(request.event) @@ -267,11 +267,11 @@ def _detect_event(request, require_live=True, require_plugin=None): if require_live and not request.event.live: can_access = ( - url.url_name == 'event.auth' - or ( - request.user.is_authenticated - and request.user.has_event_permission(request.organizer, request.event, request=request) - ) + url.url_name == 'event.auth' + or ( + request.user.is_authenticated + and request.user.has_event_permission(request.organizer, request.event, request=request) + ) ) if not can_access and 'pretix_event_access_{}'.format(request.event.pk) in request.session: diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py index 48f82ed77..e7a870962 100644 --- a/src/pretix/presale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -12,8 +12,8 @@ from pretix.base.i18n import language from pretix.base.models import ( - CartPosition, InvoiceAddress, ItemAddOn, OrderPosition, Question, - QuestionAnswer, QuestionOption, Customer, + CartPosition, Customer, InvoiceAddress, ItemAddOn, OrderPosition, Question, + QuestionAnswer, QuestionOption, ) from pretix.base.services.cart import get_fees from pretix.helpers.cookies import set_cookie_without_samesite diff --git a/src/pretix/presale/views/customer.py b/src/pretix/presale/views/customer.py index f36c74233..24e313638 100644 --- a/src/pretix/presale/views/customer.py +++ b/src/pretix/presale/views/customer.py @@ -28,13 +28,11 @@ from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.multidomain.models import KnownDomain from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse -from pretix.presale.forms.customer import TokenGenerator from pretix.presale.forms.customer_forms import ( - AuthenticationForm, ChangeInfoForm, ChangePasswordForm, RegistrationForm, - ResetPasswordForm, SetPasswordForm, + AuthenticationForm, ChangeInfoForm, ) from pretix.presale.utils import ( - customer_login, customer_logout, update_customer_session_auth_hash, + customer_login, update_customer_session_auth_hash, ) SessionStore = import_module(settings.SESSION_ENGINE).SessionStore @@ -273,9 +271,9 @@ def get_popup_origin(self): if popup_origin: popup_origin_parsed = urlparse(popup_origin) untrusted = ( - popup_origin_parsed.hostname != urlparse(settings.SITE_URL).hostname and - not KnownDomain.objects.filter(domainname=popup_origin_parsed.hostname, - organizer=self.request.organizer.pk).exists() + popup_origin_parsed.hostname != urlparse(settings.SITE_URL).hostname and + not KnownDomain.objects.filter(domainname=popup_origin_parsed.hostname, + organizer=self.request.organizer.pk).exists() ) if untrusted: popup_origin = None diff --git a/src/pretix/presale/views/customer_view/__init__.py b/src/pretix/presale/views/customer_view/__init__.py index bb418d263..0391ad514 100644 --- a/src/pretix/presale/views/customer_view/__init__.py +++ b/src/pretix/presale/views/customer_view/__init__.py @@ -1,2 +1,4 @@ -from .authentication_view import (LoginView, LogoutView, RegistrationView) -from .password_view import (ResetPasswordView, SetPasswordView, ChangePasswordView) +from .authentication_view import LoginView, LogoutView, RegistrationView # noqa +from .password_view import ( # noqa + ChangePasswordView, ResetPasswordView, SetPasswordView, +) diff --git a/src/pretix/presale/views/customer_view/authentication_view.py b/src/pretix/presale/views/customer_view/authentication_view.py index 3abb95a16..b72b0e236 100644 --- a/src/pretix/presale/views/customer_view/authentication_view.py +++ b/src/pretix/presale/views/customer_view/authentication_view.py @@ -1,24 +1,24 @@ from django.conf import settings +from django.contrib import messages +from django.db import transaction +from django.http import Http404, HttpResponseRedirect +from django.utils.decorators import method_decorator +from django.utils.http import url_has_allowed_host_and_scheme +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.cache import never_cache +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.debug import sensitive_post_parameters +from django.views.generic import FormView, View from pretix.helpers.cookies import set_cookie_without_samesite from pretix.helpers.jwt_generate import generate_customer_sso_token from pretix.multidomain.middlewares import get_cookie_domain -from pretix.presale.views.customer import RedirectBackMixin -from django.views.generic import FormView, View -from pretix.presale.forms.customer_forms import (AuthenticationForm, RegistrationForm) -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.decorators.debug import sensitive_post_parameters -from django.views.decorators.cache import never_cache -from django.http import Http404, HttpResponseRedirect from pretix.multidomain.urlreverse import eventreverse -from pretix.presale.utils import ( - customer_login, customer_logout +from pretix.presale.forms.customer_forms import ( + AuthenticationForm, RegistrationForm, ) -from django.utils.http import url_has_allowed_host_and_scheme -from django.db import transaction -from django.contrib import messages -from django.utils.translation import gettext_lazy as _ +from pretix.presale.utils import customer_login, customer_logout +from pretix.presale.views.customer import RedirectBackMixin class LoginView(RedirectBackMixin, FormView): diff --git a/src/pretix/presale/views/customer_view/password_view.py b/src/pretix/presale/views/customer_view/password_view.py index 99e6b4cca..bf342df7d 100644 --- a/src/pretix/presale/views/customer_view/password_view.py +++ b/src/pretix/presale/views/customer_view/password_view.py @@ -1,19 +1,22 @@ -from django.views.generic import FormView -from pretix.presale.forms.customer_forms import (ChangePasswordForm, ResetPasswordForm, SetPasswordForm) +from django.contrib import messages +from django.db import transaction +from django.http import Http404, HttpResponseRedirect from django.utils.decorators import method_decorator -from django.views.decorators.debug import sensitive_post_parameters -from django.views.decorators.csrf import csrf_protect +from django.utils.translation import gettext_lazy as _ from django.views.decorators.cache import never_cache -from django.http import Http404, HttpResponseRedirect +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.debug import sensitive_post_parameters +from django.views.generic import FormView + from pretix.base.models import Customer -from django.contrib import messages -from django.utils.translation import gettext_lazy as _ -from pretix.presale.forms.customer import TokenGenerator -from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse -from django.db import transaction from pretix.base.services.mail import mail -from pretix.presale.views.customer import CustomerRequiredMixin +from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse +from pretix.presale.forms.customer import TokenGenerator +from pretix.presale.forms.customer_forms import ( + ChangePasswordForm, ResetPasswordForm, SetPasswordForm, +) from pretix.presale.utils import update_customer_session_auth_hash +from pretix.presale.views.customer import CustomerRequiredMixin class SetPasswordView(FormView): diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index a134fc024..92e4623b8 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -1,19 +1,20 @@ import calendar -import jwt -import sys import datetime as dt +import importlib.util +import logging +import sys from collections import defaultdict from datetime import date, datetime, timedelta from decimal import Decimal from importlib import import_module -import logging import isoweek +import jwt import pytz from django.conf import settings from django.core.exceptions import PermissionDenied from django.db.models import ( - Count, Exists, IntegerField, OuterRef, Prefetch, Value, Q + Count, Exists, IntegerField, OuterRef, Prefetch, Q, Value, ) from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render @@ -27,7 +28,7 @@ from pretix.base.channels import get_all_sales_channels from pretix.base.models import ( - ItemVariation, Quota, SeatCategoryMapping, Voucher, Order + ItemVariation, Order, Quota, SeatCategoryMapping, Voucher, ) from pretix.base.models.event import SubEvent from pretix.base.models.items import ( @@ -42,7 +43,6 @@ EventListMixin, add_subevents_for_days, days_for_template, filter_qs_by_attr, weeks_for_template, ) -from pretix.multidomain.urlreverse import build_absolute_uri from ...helpers.formats.en.formats import WEEK_FORMAT from . import ( @@ -50,8 +50,6 @@ iframe_entry_view_wrapper, ) -import importlib.util - package_name = 'pretix_venueless' if importlib.util.find_spec(package_name) is not None: @@ -648,6 +646,7 @@ def post(self, request, *args, **kwargs): request.session['pretix_event_access_{}'.format(request.event.pk)] = parent return redirect(eventreverse(request.event, 'presale:event.index')) + @method_decorator(allow_frame_if_namespaced, 'dispatch') @method_decorator(iframe_entry_view_wrapper, 'dispatch') class JoinOnlineVideoView(EventViewMixin, View): @@ -678,8 +677,8 @@ def validate_access(self, request, *args, **kwargs): else: # Get all orders of customer which belong to this event order_list = (Order.objects.filter(Q(event=self.request.event) - & (Q(customer=self.request.customer) | Q(email__iexact=self.request.customer.email))) - .select_related('event').order_by('-datetime')) + & (Q(customer=self.request.customer) | Q(email__iexact=self.request.customer.email))) + .select_related('event').order_by('-datetime')) # Check qs is empty if not order_list: # no order placed yet diff --git a/src/pretix/presale/views/open_id_connect.py b/src/pretix/presale/views/open_id_connect.py index 73969f3c7..1f45d09ae 100644 --- a/src/pretix/presale/views/open_id_connect.py +++ b/src/pretix/presale/views/open_id_connect.py @@ -7,7 +7,7 @@ from Crypto.PublicKey import RSA from django.db import transaction -from django.http import Http404, HttpResponse, JsonResponse, HttpRequest +from django.http import Http404, HttpRequest, HttpResponse, JsonResponse from django.shortcuts import redirect, render from django.utils.crypto import get_random_string from django.utils.decorators import method_decorator @@ -27,7 +27,9 @@ from pretix.multidomain.urlreverse import build_absolute_uri from pretix.presale.forms.customer_forms import AuthenticationForm from pretix.presale.utils import customer_login, get_customer_auth_time -from pretix.presale.views.customer_view.authentication_view import set_cookie_after_logged_in +from pretix.presale.views.customer_view.authentication_view import ( + set_cookie_after_logged_in, +) RESPONSE_TYPES_SUPPORTED = ("code", "id_token token", "id_token", "code id_token", "code id_token token", "code token") diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index 312d08a57..f28bcdad1 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -25,9 +25,7 @@ from django.views.decorators.cache import cache_page from django.views.decorators.gzip import gzip_page from django.views.decorators.http import condition -from django.views.i18n import ( - JavaScriptCatalog, get_formats, -) +from django.views.i18n import JavaScriptCatalog, get_formats from lxml import html from pretix.base.i18n import language diff --git a/src/pretix/settings.py b/src/pretix/settings.py index 251e65fc2..7f5d7881c 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -2,11 +2,10 @@ import logging import os import sys -import importlib_metadata - from urllib.parse import urlparse -from .settings_helpers import build_db_tls_config, build_redis_tls_config + import django.conf.locale +import importlib_metadata from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import get_random_string from kombu import Queue @@ -14,7 +13,7 @@ from . import __version__ from .helpers.config import EnvOrParserConfig - +from .settings_helpers import build_db_tls_config, build_redis_tls_config from django.contrib.messages import constants as messages # NOQA from django.utils.translation import gettext_lazy as _ # NOQA diff --git a/src/pretix/urls.py b/src/pretix/urls.py index 436c62544..b5a690ee4 100644 --- a/src/pretix/urls.py +++ b/src/pretix/urls.py @@ -1,6 +1,5 @@ from django.conf import settings -from django.urls import include -from django.urls import re_path as url +from django.urls import include, re_path as url from django.views.generic import RedirectView import pretix.control.urls diff --git a/src/set_github_token.py b/src/set_github_token.py index 91f70bb0a..8038ff010 100644 --- a/src/set_github_token.py +++ b/src/set_github_token.py @@ -1,4 +1,5 @@ import os + import toml # Get the stripe_key from environment variable diff --git a/src/tests/api/test_device_event_selection.py b/src/tests/api/test_device_event_selection.py index 88bdcfa48..f2a72ed1e 100644 --- a/src/tests/api/test_device_event_selection.py +++ b/src/tests/api/test_device_event_selection.py @@ -10,7 +10,7 @@ @pytest.mark.django_db def test_no_events(device_client, device): - resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1') + resp = device_client.get('/api/v1/device/eventselection?current_event=e1') assert resp.status_code == 404 @@ -45,30 +45,30 @@ def test_choose_between_events(device_client, device): resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}') assert resp.status_code == 304 with freeze_time("2020-01-10T16:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1') + resp = device_client.get('/api/v1/device/eventselection?current_event=e1') assert resp.status_code == 200 - resp = device_client.get(f'/api/v1/device/eventselection?current_event=e2') + resp = device_client.get('/api/v1/device/eventselection?current_event=e2') assert resp.status_code == 304 # Next one only with freeze_time("2020-01-10T12:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' # Last one only with freeze_time("2020-01-10T17:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e2' # Running one with freeze_time("2020-01-10T14:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' with freeze_time("2020-01-10T16:01:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}') + resp = device_client.get('/api/v1/device/eventselection?current_event=e1¤t_checkinlist={cl1.pk}') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e2' assert resp.data['checkinlist'] == cl2.pk @@ -87,11 +87,11 @@ def test_choose_between_events(device_client, device): # Switch at half-time with freeze_time("2020-01-10T15:29:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' with freeze_time("2020-01-10T15:31:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e2' @@ -135,21 +135,21 @@ def test_choose_between_subevents(device_client, device): # Next one only with freeze_time("2020-01-10T12:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se1.pk # Last one only with freeze_time("2020-01-10T17:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se2.pk # Running one with freeze_time("2020-01-10T14:30:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se1.pk @@ -163,7 +163,7 @@ def test_choose_between_subevents(device_client, device): # Prefer the one on the same day with freeze_time("2020-01-10T23:59:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se2.pk @@ -177,12 +177,12 @@ def test_choose_between_subevents(device_client, device): # Switch at half-time with freeze_time("2020-01-10T15:29:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.status_code == 200 assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se1.pk with freeze_time("2020-01-10T15:31:00+09:00"): - resp = device_client.get(f'/api/v1/device/eventselection') + resp = device_client.get('/api/v1/device/eventselection') assert resp.data['event']['slug'] == 'e1' assert resp.data['subevent'] == se2.pk diff --git a/src/tests/api/test_exporters.py b/src/tests/api/test_exporters.py index 60b3824ce..e9fefe0d4 100644 --- a/src/tests/api/test_exporters.py +++ b/src/tests/api/test_exporters.py @@ -81,7 +81,7 @@ def test_org_list(token_client, organizer, event): resp = token_client.get('/api/v1/organizers/{}/exporters/'.format(organizer.slug)) assert resp.status_code == 200 assert c in resp.data['results'] - resp = token_client.get('/api/v1/organizers/{}/exporters/orderlist/'.format(organizer.slug, event.slug)) + resp = token_client.get('/api/v1/organizers/{}/exporters/orderlist/'.format(organizer.slug)) assert resp.status_code == 200 assert c == resp.data diff --git a/src/tests/api/test_idempotency.py b/src/tests/api/test_idempotency.py index 950040d5b..98f61894b 100644 --- a/src/tests/api/test_idempotency.py +++ b/src/tests/api/test_idempotency.py @@ -112,10 +112,10 @@ def test_ignore_get(token_client, organizer, event): @pytest.mark.django_db def test_ignore_outside_api(token_client, organizer): - resp = token_client.post('/control/login'.format(organizer.slug), + resp = token_client.post('/control/login', PAYLOAD, format='json', HTTP_X_IDEMPOTENCY_KEY='foo') assert resp.status_code == 200 - resp = token_client.post('/control/invalid/'.format(organizer.slug), + resp = token_client.post('/control/invalid/', PAYLOAD, format='json', HTTP_X_IDEMPOTENCY_KEY='foo') assert resp.status_code == 302 diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index c1d6f8c7d..878d4f32c 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -1741,10 +1741,10 @@ class ItemCategoryTest(TestCase): This test case tests various methods around the category model. """ - def setUp(cls): - cls.o = Organizer.objects.create(name='Dummy', slug='dummy') - cls.event = Event.objects.create( - organizer=cls.o, name='Dummy', slug='dummy', + def setUp(self): + self.o = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=self.o, name='Dummy', slug='dummy', date_from=now(), ) @@ -1769,10 +1769,10 @@ class ItemTest(TestCase): This test case tests various methods around the item model. """ - def setUp(cls): - cls.o = Organizer.objects.create(name='Dummy', slug='dummy') - cls.event = Event.objects.create( - organizer=cls.o, name='Dummy', slug='dummy', + def setUp(self): + self.o = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=self.o, name='Dummy', slug='dummy', date_from=now(), ) @@ -1845,8 +1845,8 @@ def test_availability_filter(self): class EventTest(TestCase): - def setUp(cls): - cls.organizer = Organizer.objects.create(name='Dummy', slug='dummy') + def setUp(self): + self.organizer = Organizer.objects.create(name='Dummy', slug='dummy') @classscope(attr='organizer') def test_event_end_before_start(self): @@ -2091,15 +2091,15 @@ def test_active_quotas_annotation_variation(self): class SubEventTest(TestCase): - def setUp(cls): - cls.organizer = Organizer.objects.create(name='Dummy', slug='dummy') - cls.event = Event.objects.create( - organizer=cls.organizer, name='Dummy', slug='dummy', + def setUp(self): + self.organizer = Organizer.objects.create(name='Dummy', slug='dummy') + self.event = Event.objects.create( + organizer=self.organizer, name='Dummy', slug='dummy', date_from=now(), date_to=now() - timedelta(hours=1), has_subevents=True ) - cls.se = SubEvent.objects.create( - name='Testsub', date_from=now(), event=cls.event + self.se = SubEvent.objects.create( + name='Testsub', date_from=now(), event=self.event ) @classscope(attr='organizer') @@ -2185,70 +2185,70 @@ def test_file_handling(self): class CheckinListTestCase(TestCase): - def setUp(cls): - cls.organizer = Organizer.objects.create(name='Dummy', slug='dummy') - with scope(organizer=cls.organizer): - cls.event = Event.objects.create( - organizer=cls.organizer, name='Dummy', slug='dummy', + def setUp(self): + self.organizer = Organizer.objects.create(name='Dummy', slug='dummy') + with scope(organizer=self.organizer): + self.event = Event.objects.create( + organizer=self.organizer, name='Dummy', slug='dummy', date_from=now(), date_to=now() - timedelta(hours=1), ) - cls.item1 = cls.event.items.create(name="Ticket", default_price=12) - cls.item2 = cls.event.items.create(name="Shirt", default_price=6) - cls.cl_all = cls.event.checkin_lists.create( + self.item1 = self.event.items.create(name="Ticket", default_price=12) + self.item2 = self.event.items.create(name="Shirt", default_price=6) + self.cl_all = self.event.checkin_lists.create( name='All', all_products=True ) - cls.cl_all_pending = cls.event.checkin_lists.create( + self.cl_all_pending = self.event.checkin_lists.create( name='Z Pending', all_products=True, include_pending=True ) - cls.cl_both = cls.event.checkin_lists.create( + self.cl_both = self.event.checkin_lists.create( name='Both', all_products=False ) - cls.cl_both.limit_products.add(cls.item1) - cls.cl_both.limit_products.add(cls.item2) - cls.cl_tickets = cls.event.checkin_lists.create( + self.cl_both.limit_products.add(self.item1) + self.cl_both.limit_products.add(self.item2) + self.cl_tickets = self.event.checkin_lists.create( name='Tickets', all_products=False ) - cls.cl_tickets.limit_products.add(cls.item1) + self.cl_tickets.limit_products.add(self.item1) o = Order.objects.create( - code='FOO', event=cls.event, email='dummy@dummy.test', + code='FOO', event=self.event, email='dummy@dummy.test', status=Order.STATUS_PAID, datetime=now(), expires=now() + timedelta(days=10), total=Decimal("30"), locale='en' ) OrderPosition.objects.create( order=o, - item=cls.item1, + item=self.item1, variation=None, price=Decimal("12"), ) op2 = OrderPosition.objects.create( order=o, - item=cls.item1, + item=self.item1, variation=None, price=Decimal("12"), ) op3 = OrderPosition.objects.create( order=o, - item=cls.item2, + item=self.item2, variation=None, price=Decimal("6"), ) - op2.checkins.create(list=cls.cl_tickets) - op3.checkins.create(list=cls.cl_both) + op2.checkins.create(list=self.cl_tickets) + op3.checkins.create(list=self.cl_both) o = Order.objects.create( - code='FOO', event=cls.event, email='dummy@dummy.test', + code='FOO', event=self.event, email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=Decimal("30"), locale='en' ) op4 = OrderPosition.objects.create( order=o, - item=cls.item2, + item=self.item2, variation=None, price=Decimal("6"), ) - op4.checkins.create(list=cls.cl_all_pending) + op4.checkins.create(list=self.cl_all_pending) @classscope(attr='organizer') def test_attributes(self): diff --git a/src/tests/control/test_auth.py b/src/tests/control/test_auth.py index a7b6a39c7..3fe0ac2db 100644 --- a/src/tests/control/test_auth.py +++ b/src/tests/control/test_auth.py @@ -317,7 +317,7 @@ def test_totp_invalid(self): d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() - response = self.client.post('/control/login/2fa'.format(d.pk), { + response = self.client.post('/control/login/2fa', { 'token': str(totp.token() + 2) }) self.assertEqual(response.status_code, 302) @@ -329,7 +329,7 @@ def test_totp_valid(self): d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() - response = self.client.post('/control/login/2fa?next=/control/events/'.format(d.pk), { + response = self.client.post('/control/login/2fa?next=/control/events/', { 'token': str(totp.token()) }) self.assertEqual(response.status_code, 302) @@ -343,7 +343,7 @@ def fail(*args, **kwargs): m = self.monkeypatch m.setattr("webauthn.verify_authentication_response", fail) - d = U2FDevice.objects.create( + U2FDevice.objects.create( user=self.user, name='test', json_data='{"appId": "https://local.eventyay.com", "keyHandle": ' '"j9Rkpon1J5U3eDQMM8YqAvwEapt-m87V8qdCaImiAqmvTJ' @@ -353,7 +353,7 @@ def fail(*args, **kwargs): response = self.client.get('/control/login/2fa') assert 'token' in response.content.decode() - response = self.client.post('/control/login/2fa'.format(d.pk), { + response = self.client.post('/control/login/2fa', { 'token': '{"response": "true"}' }) self.assertEqual(response.status_code, 302) @@ -368,7 +368,7 @@ def test_u2f_valid(self): b'', 1, 'single_device', True, )) - d = U2FDevice.objects.create( + U2FDevice.objects.create( user=self.user, name='test', json_data='{"appId": "https://local.eventyay.com", "keyHandle": ' '"j9Rkpon1J5U3eDQMM8YqAvwEapt-m87V8qdCaImiAqmvTJ' @@ -378,7 +378,7 @@ def test_u2f_valid(self): response = self.client.get('/control/login/2fa') assert 'token' in response.content.decode() - response = self.client.post('/control/login/2fa'.format(d.pk), { + response = self.client.post('/control/login/2fa', { 'token': '{"response": "true"}' }) self.assertEqual(response.status_code, 302) diff --git a/src/tests/control/test_giftcards.py b/src/tests/control/test_giftcards.py index 90a4dcc90..c8c85384a 100644 --- a/src/tests/control/test_giftcards.py +++ b/src/tests/control/test_giftcards.py @@ -153,11 +153,11 @@ def test_card_detail_view_transact_invalid_value(organizer, admin_user, gift_car @pytest.mark.django_db def test_manage_acceptance(organizer, organizer2, admin_user, gift_card, client, team2): client.login(email='dummy@dummy.dummy', password='dummy') - client.post('/control/organizer/dummy/giftcards'.format(gift_card.pk), { + client.post('/control/organizer/dummy/giftcards', { 'add': organizer2.slug }) assert organizer.gift_card_issuer_acceptance.filter(issuer=organizer2).exists() - client.post('/control/organizer/dummy/giftcards'.format(gift_card.pk), { + client.post('/control/organizer/dummy/giftcards', { 'del': organizer2.slug }) assert not organizer.gift_card_issuer_acceptance.filter(issuer=organizer2).exists() @@ -166,7 +166,7 @@ def test_manage_acceptance(organizer, organizer2, admin_user, gift_card, client, @pytest.mark.django_db def test_manage_acceptance_permission_required(organizer, organizer2, admin_user, gift_card, client): client.login(email='dummy@dummy.dummy', password='dummy') - client.post('/control/organizer/dummy/giftcards'.format(gift_card.pk), { + client.post('/control/organizer/dummy/giftcards', { 'add': organizer2.slug }) assert not organizer.gift_card_issuer_acceptance.filter(issuer=organizer2).exists() diff --git a/src/tests/control/test_orders.py b/src/tests/control/test_orders.py index 722d25fff..856e16c72 100644 --- a/src/tests/control/test_orders.py +++ b/src/tests/control/test_orders.py @@ -1894,7 +1894,7 @@ def test_refund_propose_lower_payment(client, env): }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '6.00' - assert doc.select("input[name=refund-manual]".format(p2.pk))[0]['value'] == '1.00' + assert doc.select("input[name=refund-manual]")[0]['value'] == '1.00' @pytest.mark.django_db @@ -1915,7 +1915,7 @@ def test_refund_propose_equal_payment(client, env): }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00' - assert not doc.select("input[name=refund-manual]".format(p2.pk))[0].get('value') + assert not doc.select("input[name=refund-manual]")[0].get('value') @pytest.mark.django_db @@ -1936,7 +1936,7 @@ def test_refund_propose_higher_payment(client, env): }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00' - assert not doc.select("input[name=refund-manual]".format(p2.pk))[0].get('value') + assert not doc.select("input[name=refund-manual]")[0].get('value') @pytest.mark.django_db diff --git a/src/tests/plugins/banktransfer/test_actions.py b/src/tests/plugins/banktransfer/test_actions.py index 0e1f8bd18..0aed1a764 100644 --- a/src/tests/plugins/banktransfer/test_actions.py +++ b/src/tests/plugins/banktransfer/test_actions.py @@ -113,7 +113,7 @@ def test_comment(env, client): amount=12, date='unknown') client.login(email='dummy@dummy.dummy', password='dummy') r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), { - 'action_{}'.format(trans.pk): 'comment:This is my comment'.format(env[2].code) + 'action_{}'.format(trans.pk): 'comment:This is my comment' }).content.decode('utf-8')) assert r['status'] == 'ok' trans.refresh_from_db()