forked from CGM_Public/pretix_original
Compare commits
38 Commits
fieldset-p
...
a11y-custo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60e7a81c05 | ||
|
|
dd5b57d4a2 | ||
|
|
297971e81a | ||
|
|
faf64f0973 | ||
|
|
1a39f209e9 | ||
|
|
6fc62dcaf5 | ||
|
|
6df3c121b4 | ||
|
|
d1a6ab89fe | ||
|
|
3ec0fdb4d2 | ||
|
|
20c14b8b24 | ||
|
|
a2100c9295 | ||
|
|
094c04df73 | ||
|
|
de6f6025e2 | ||
|
|
3a1f19fa51 | ||
|
|
4bc1adc5e2 | ||
|
|
2ca22ef663 | ||
|
|
1fdf8cb01e | ||
|
|
83b8cb3b4b | ||
|
|
2fe5b65f94 | ||
|
|
87d54ae068 | ||
|
|
e3cd5af1d7 | ||
|
|
b1fbf4d5b7 | ||
|
|
ce2e94b8d5 | ||
|
|
ccf32ed2c1 | ||
|
|
57bed6e6db | ||
|
|
5a85ed49e8 | ||
|
|
9e2aeaa400 | ||
|
|
2442f2bfb5 | ||
|
|
2c03468ef5 | ||
|
|
53d80e56e6 | ||
|
|
5fede841d7 | ||
|
|
3c5ccaa1ba | ||
|
|
59dccaf680 | ||
|
|
3f499447da | ||
|
|
4765dd5c9a | ||
|
|
8f7fca42e5 | ||
|
|
5437bad1c1 | ||
|
|
3c16c5f66a |
@@ -288,7 +288,6 @@ Example::
|
||||
[django]
|
||||
secret=j1kjps5a5&4ilpn912s7a1!e2h!duz^i3&idu@_907s$wrz@x-
|
||||
debug=off
|
||||
passwords_argon2=on
|
||||
|
||||
``secret``
|
||||
The secret to be used by Django for signing and verification purposes. If this
|
||||
@@ -304,10 +303,6 @@ Example::
|
||||
|
||||
.. WARNING:: Never set this to ``True`` in production!
|
||||
|
||||
``passwords_argon``
|
||||
Use the ``argon2`` algorithm for password hashing. Disable on systems with a small number of CPU cores (currently
|
||||
less than 8).
|
||||
|
||||
``profile``
|
||||
Enable code profiling for a random subset of requests. Disabled by default, see
|
||||
:ref:`perf-monitoring` for details.
|
||||
|
||||
@@ -249,7 +249,7 @@ Endpoints
|
||||
"orderposition": null,
|
||||
"cartposition": null,
|
||||
"voucher": null
|
||||
}
|
||||
},
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
@@ -260,114 +260,3 @@ Endpoints
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_block/
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_block/
|
||||
|
||||
Set the ``blocked`` attribute to ``true`` for a large number of seats at once.
|
||||
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
|
||||
You can pass up to 10,000 seats in one request.
|
||||
|
||||
The endpoint will return an error if you pass a seat ID that does not exist.
|
||||
However, it will not return an error if one of the passed seats is already blocked or sold.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ids": [12, 45, 56]
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param subevent_id: The ``id`` field of the subevent to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The seat could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_unblock/
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_unblock/
|
||||
|
||||
Set the ``blocked`` attribute to ``false`` for a large number of seats at once.
|
||||
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
|
||||
You can pass up to 10,000 seats in one request.
|
||||
|
||||
The endpoint will return an error if you pass a seat ID that does not exist.
|
||||
However, it will not return an error if one of the passed seat is already unblocked or is sold.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ids": [12, 45, 56]
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param subevent_id: The ``id`` field of the subevent to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The seat could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
@@ -53,7 +53,7 @@ dependencies = [
|
||||
"django-phonenumber-field==7.3.*",
|
||||
"django-redis==5.4.*",
|
||||
"django-scopes==2.0.*",
|
||||
"django-statici18n==2.6.*",
|
||||
"django-statici18n==2.5.*",
|
||||
"djangorestframework==3.15.*",
|
||||
"dnspython==2.7.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
@@ -76,7 +76,7 @@ dependencies = [
|
||||
"phonenumberslite==8.13.*",
|
||||
"Pillow==11.0.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==5.29.*",
|
||||
"protobuf==5.28.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==2.22",
|
||||
@@ -97,10 +97,10 @@ dependencies = [
|
||||
"text-unidecode==1.*",
|
||||
"tlds>=2020041600",
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"ua-parser==0.18.*",
|
||||
"vat_moss_forked==2020.3.20.0.11.0",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.3.*",
|
||||
"webauthn==2.2.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2024.12.0.dev0"
|
||||
__version__ = "2024.11.0.dev0"
|
||||
|
||||
@@ -989,40 +989,6 @@ def prefetch_by_id(items, qs, id_attr, target_attr):
|
||||
setattr(item, target_attr, result.get(getattr(item, id_attr)))
|
||||
|
||||
|
||||
class SeatBulkBlockInputSerializer(serializers.Serializer):
|
||||
ids = serializers.ListField(child=serializers.IntegerField(), required=False, allow_empty=True)
|
||||
seat_guids = serializers.ListField(child=serializers.CharField(), required=False, allow_empty=True)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
data = super().to_internal_value(data)
|
||||
|
||||
if data.get("seat_guids") and data.get("ids"):
|
||||
raise ValidationError("Please pass either seat_guids or ids.")
|
||||
|
||||
if data.get("seat_guids"):
|
||||
seat_ids = data["seat_guids"]
|
||||
if len(seat_ids) > 10000:
|
||||
raise ValidationError({"seat_guids": ["Please do not pass over 10000 seats."]})
|
||||
|
||||
seats = {s.seat_guid: s for s in self.context["queryset"].filter(seat_guid__in=seat_ids)}
|
||||
for s in seat_ids:
|
||||
if s not in seats:
|
||||
raise ValidationError({"seat_guids": [f"The seat '{s}' does not exist."]})
|
||||
elif data.get("ids"):
|
||||
seat_ids = data["ids"]
|
||||
if len(seat_ids) > 10000:
|
||||
raise ValidationError({"ids": ["Please do not pass over 10000 seats."]})
|
||||
|
||||
seats = self.context["queryset"].in_bulk(seat_ids)
|
||||
for s in seat_ids:
|
||||
if s not in seats:
|
||||
raise ValidationError({"ids": [f"The seat '{s}' does not exist."]})
|
||||
else:
|
||||
raise ValidationError("Please pass either seat_guids or ids.")
|
||||
|
||||
return {"seats": seats.values()}
|
||||
|
||||
|
||||
class SeatSerializer(I18nAwareModelSerializer):
|
||||
orderposition = serializers.IntegerField(source='orderposition_id')
|
||||
cartposition = serializers.IntegerField(source='cartposition_id')
|
||||
|
||||
@@ -40,7 +40,6 @@ from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import serializers, views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import (
|
||||
NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
@@ -51,9 +50,8 @@ from pretix.api.auth.permission import EventCRUDPermission
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.event import (
|
||||
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
|
||||
EventSettingsSerializer, ItemMetaPropertiesSerializer,
|
||||
SeatBulkBlockInputSerializer, SeatSerializer, SubEventSerializer,
|
||||
TaxRuleSerializer,
|
||||
EventSettingsSerializer, ItemMetaPropertiesSerializer, SeatSerializer,
|
||||
SubEventSerializer, TaxRuleSerializer,
|
||||
)
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import (
|
||||
@@ -239,9 +237,9 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
disabled = {m: 'disabled' for m in current_plugins_value if m not in updated_plugins_value}
|
||||
changed = merge_dicts(enabled, disabled)
|
||||
|
||||
for module, operation in changed.items():
|
||||
for module, action in changed.items():
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.plugins.' + operation,
|
||||
'pretix.event.plugins.' + action,
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'plugin': module}
|
||||
@@ -746,24 +744,3 @@ class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
auth=self.request.auth,
|
||||
data={"seats": [serializer.instance.pk]},
|
||||
)
|
||||
|
||||
def bulk_change_blocked(self, blocked):
|
||||
s = SeatBulkBlockInputSerializer(
|
||||
data=self.request.data,
|
||||
context={"event": self.request.event, "queryset": self.get_queryset()},
|
||||
)
|
||||
s.is_valid(raise_exception=True)
|
||||
|
||||
seats = s.validated_data["seats"]
|
||||
for seat in seats:
|
||||
seat.blocked = blocked
|
||||
Seat.objects.bulk_update(seats, ["blocked"], batch_size=1000)
|
||||
return Response({})
|
||||
|
||||
@action(methods=["POST"], detail=False)
|
||||
def bulk_block(self, request, *args, **kwargs):
|
||||
return self.bulk_change_blocked(True)
|
||||
|
||||
@action(methods=["POST"], detail=False)
|
||||
def bulk_unblock(self, request, *args, **kwargs):
|
||||
return self.bulk_change_blocked(False)
|
||||
|
||||
@@ -35,7 +35,6 @@ from django.utils.translation import get_language, gettext_lazy as _
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.signals import register_html_mail_renderers
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
|
||||
from pretix.base.services.placeholders import ( # noqa
|
||||
get_available_placeholders, PlaceholderContext
|
||||
@@ -80,7 +79,7 @@ class BaseHTMLMailRenderer:
|
||||
return self.identifier
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order=None,
|
||||
position=None, context=None) -> str:
|
||||
position=None) -> str:
|
||||
"""
|
||||
This method should generate the HTML part of the email.
|
||||
|
||||
@@ -89,7 +88,6 @@ class BaseHTMLMailRenderer:
|
||||
:param subject: The email subject.
|
||||
:param order: The order if this email is connected to one, otherwise ``None``.
|
||||
:param position: The order position if this email is connected to one, otherwise ``None``.
|
||||
:param context: Context to use to render placeholders in the plain body
|
||||
:return: An HTML string
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -136,10 +134,8 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
||||
def compile_markdown(self, plaintext):
|
||||
return markdown_compile_email(plaintext)
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
|
||||
body_md = self.compile_markdown(plain_body)
|
||||
if context:
|
||||
body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
htmlctx = {
|
||||
'site': settings.PRETIX_INSTANCE_NAME,
|
||||
'site_url': settings.SITE_URL,
|
||||
|
||||
@@ -54,7 +54,6 @@ from django.core.validators import (
|
||||
from django.db.models import QuerySet
|
||||
from django.forms import Select, widgets
|
||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
@@ -78,7 +77,7 @@ from pretix.base.i18n import (
|
||||
get_babel_locale, get_language_without_region, language,
|
||||
)
|
||||
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
|
||||
from pretix.base.models.tax import ask_for_vat_id
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
|
||||
from pretix.base.services.tax import (
|
||||
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
||||
)
|
||||
@@ -603,7 +602,6 @@ class BaseQuestionsForm(forms.Form):
|
||||
questions = pos.item.questions_to_ask
|
||||
event = kwargs.pop('event')
|
||||
self.all_optional = kwargs.pop('all_optional', False)
|
||||
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -678,7 +676,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
|
||||
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
||||
add_fields['street'] = forms.CharField(
|
||||
required=self.attendee_addresses_required,
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('Address'),
|
||||
widget=forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
@@ -688,7 +686,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
initial=(cartpos.street if cartpos else orderpos.street),
|
||||
)
|
||||
add_fields['zipcode'] = forms.CharField(
|
||||
required=False,
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
max_length=30,
|
||||
label=_('ZIP code'),
|
||||
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
||||
@@ -697,7 +695,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
}),
|
||||
)
|
||||
add_fields['city'] = forms.CharField(
|
||||
required=False,
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('City'),
|
||||
max_length=255,
|
||||
initial=(cartpos.city if cartpos else orderpos.city),
|
||||
@@ -709,12 +707,11 @@ class BaseQuestionsForm(forms.Form):
|
||||
add_fields['country'] = CountryField(
|
||||
countries=CachedCountries
|
||||
).formfield(
|
||||
required=self.attendee_addresses_required,
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('Country'),
|
||||
initial=country,
|
||||
widget=forms.Select(attrs={
|
||||
'autocomplete': 'country',
|
||||
'data-country-information-url': reverse('js_helpers.states'),
|
||||
}),
|
||||
)
|
||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||
@@ -949,9 +946,9 @@ class BaseQuestionsForm(forms.Form):
|
||||
d = super().clean()
|
||||
|
||||
if self.address_validation:
|
||||
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
|
||||
self.cleaned_data = d = validate_address(d, True)
|
||||
|
||||
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
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.'))
|
||||
|
||||
@@ -1008,7 +1005,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
'street': forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
'placeholder': _('Street and Number'),
|
||||
'autocomplete': 'street-address',
|
||||
'autocomplete': 'street-address'
|
||||
}),
|
||||
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
||||
'country': forms.Select(attrs={
|
||||
@@ -1024,7 +1021,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
'data-display-dependency': '#id_is_business_1',
|
||||
'autocomplete': 'organization',
|
||||
}),
|
||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
|
||||
'internal_reference': forms.TextInput,
|
||||
}
|
||||
labels = {
|
||||
@@ -1058,7 +1055,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
])
|
||||
|
||||
self.fields['country'].choices = CachedCountries()
|
||||
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
|
||||
|
||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||
fprefix = self.prefix + '-' if self.prefix else ''
|
||||
@@ -1087,10 +1083,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
)
|
||||
self.fields['state'].widget.is_required = True
|
||||
|
||||
self.fields['street'].required = False
|
||||
self.fields['zipcode'].required = False
|
||||
self.fields['city'].required = False
|
||||
|
||||
# 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 ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
|
||||
self.data = self.data.copy()
|
||||
@@ -1143,7 +1135,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
validate_address # local import to prevent impact on startup time
|
||||
|
||||
data = self.cleaned_data
|
||||
|
||||
if not data.get('is_business'):
|
||||
data['company'] = ''
|
||||
data['vat_id'] = ''
|
||||
@@ -1151,11 +1142,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
data['vat_id'] = ''
|
||||
if self.event.settings.invoice_address_required:
|
||||
if data.get('is_business') and not data.get('company'):
|
||||
raise ValidationError({"company": _('You need to provide a company name.')})
|
||||
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 not self.all_optional and 'street' in self.fields and not data.get('street') and not data.get('zipcode') and not data.get('city'):
|
||||
raise ValidationError({"street": _('This field is required.')})
|
||||
|
||||
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
||||
self.instance.vat_id_validated = False
|
||||
|
||||
@@ -9,7 +9,6 @@ from decimal import Decimal
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import i18nfield.fields
|
||||
from argon2.exceptions import HashingError
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.db import migrations, models
|
||||
@@ -26,14 +25,7 @@ def initial_user(apps, schema_editor):
|
||||
user = User(email='admin@localhost')
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
try:
|
||||
user.password = make_password('admin')
|
||||
except HashingError:
|
||||
raise Exception(
|
||||
"Could not hash password of initial user with argon2id. If this is a system with less than 8 CPU cores, "
|
||||
"you might need to disable argon2id by setting `passwords_argon2=off` in the `[django]` section of the "
|
||||
"pretix.cfg configuration file."
|
||||
)
|
||||
user.password = make_password('admin')
|
||||
user.save()
|
||||
|
||||
|
||||
|
||||
@@ -823,9 +823,6 @@ class Event(EventMixin, LoggedModel):
|
||||
self.save()
|
||||
self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk})
|
||||
|
||||
if hasattr(other, 'alternative_domain_assignment'):
|
||||
other.alternative_domain_assignment.domain.event_assignments.create(event=self)
|
||||
|
||||
if not self.all_sales_channels:
|
||||
self.limit_sales_channels.set(
|
||||
self.organizer.sales_channels.filter(
|
||||
|
||||
@@ -2275,7 +2275,6 @@ class OrderFee(models.Model):
|
||||
FEE_TYPE_SERVICE = "service"
|
||||
FEE_TYPE_CANCELLATION = "cancellation"
|
||||
FEE_TYPE_INSURANCE = "insurance"
|
||||
FEE_TYPE_LATE = "late"
|
||||
FEE_TYPE_OTHER = "other"
|
||||
FEE_TYPE_GIFTCARD = "giftcard"
|
||||
FEE_TYPES = (
|
||||
@@ -2284,7 +2283,6 @@ class OrderFee(models.Model):
|
||||
(FEE_TYPE_SERVICE, _("Service fee")),
|
||||
(FEE_TYPE_CANCELLATION, _("Cancellation fee")),
|
||||
(FEE_TYPE_INSURANCE, _("Insurance fee")),
|
||||
(FEE_TYPE_LATE, _("Late fee")),
|
||||
(FEE_TYPE_OTHER, _("Other fees")),
|
||||
(FEE_TYPE_GIFTCARD, _("Gift card")),
|
||||
)
|
||||
@@ -3206,9 +3204,9 @@ class InvoiceAddress(models.Model):
|
||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
|
||||
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
||||
name_parts = models.JSONField(default=dict)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=True)
|
||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
|
||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=False)
|
||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
|
||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
|
||||
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
||||
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
||||
countries=CachedCountries)
|
||||
|
||||
@@ -76,7 +76,7 @@ from pretix.base.services.tasks import TransactionAwareTask
|
||||
from pretix.base.services.tickets import get_tickets_for_order
|
||||
from pretix.base.signals import email_filter, global_email_filter
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.ical import get_private_icals
|
||||
@@ -311,13 +311,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
try:
|
||||
if plain_text_only:
|
||||
body_html = None
|
||||
elif 'context' in inspect.signature(renderer.render).parameters:
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without context argument because context argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||
else:
|
||||
# Backwards compatibility
|
||||
@@ -329,8 +323,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
logger.exception('Could not render HTML body')
|
||||
body_html = None
|
||||
|
||||
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||
|
||||
send_task = mail_send_task.si(
|
||||
to=[email] if isinstance(email, str) else list(email),
|
||||
cc=cc,
|
||||
@@ -663,7 +655,7 @@ def render_mail(template, context):
|
||||
if isinstance(template, LazyI18nString):
|
||||
body = str(template)
|
||||
if context:
|
||||
body = format_map(body, context, mode=SafeFormatter.MODE_IGNORE_RICH)
|
||||
body = format_map(body, context)
|
||||
else:
|
||||
tpl = get_template(template)
|
||||
body = tpl.render(context)
|
||||
|
||||
@@ -26,7 +26,6 @@ from decimal import Decimal
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -40,8 +39,7 @@ from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
|
||||
from pretix.base.signals import (
|
||||
register_mail_placeholders, register_text_placeholders,
|
||||
)
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.helpers.format import PlainHtmlAlternativeString, SafeFormatter
|
||||
from pretix.helpers.format import SafeFormatter
|
||||
|
||||
logger = logging.getLogger('pretix.base.services.placeholders')
|
||||
|
||||
@@ -109,91 +107,6 @@ class SimpleFunctionalTextPlaceholder(BaseTextPlaceholder):
|
||||
return self._sample
|
||||
|
||||
|
||||
class BaseRichTextPlaceholder(BaseTextPlaceholder):
|
||||
"""
|
||||
This is the base class for all placeholders which can render either to plain text
|
||||
or to a rich HTML element.
|
||||
"""
|
||||
|
||||
def __init__(self, identifier, args):
|
||||
self._identifier = identifier
|
||||
self._args = args
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self._identifier
|
||||
|
||||
@property
|
||||
def required_context(self):
|
||||
return self._args
|
||||
|
||||
@property
|
||||
def is_block(self):
|
||||
return False
|
||||
|
||||
def render(self, context):
|
||||
return PlainHtmlAlternativeString(
|
||||
self.render_plain(**{k: context[k] for k in self._args}),
|
||||
self.render_html(**{k: context[k] for k in self._args}),
|
||||
self.is_block,
|
||||
)
|
||||
|
||||
def render_html(self, **kwargs):
|
||||
"""
|
||||
HTML rendering of the placeholder. Should return "safe" HTML, i.e. everything needs to be
|
||||
escaped.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def render_plain(self, **kwargs):
|
||||
"""
|
||||
Plain text rendering of the placeholder.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def render_sample(self, event):
|
||||
return PlainHtmlAlternativeString(
|
||||
self.render_sample_plain(event=event),
|
||||
self.render_sample_html(event=event),
|
||||
self.is_block,
|
||||
)
|
||||
|
||||
def render_sample_html(self, event):
|
||||
raise NotImplementedError
|
||||
|
||||
def render_sample_plain(self, event):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SimpleButtonPlaceholder(BaseRichTextPlaceholder):
|
||||
def __init__(self, identifier, args, url_func, text_func, sample_url_func, sample_text_func):
|
||||
super().__init__(identifier, args)
|
||||
self._url_func = url_func
|
||||
self._text_func = text_func
|
||||
self._sample_url_func = sample_url_func
|
||||
self._sample_text_func = sample_text_func
|
||||
|
||||
def render_html(self, **context):
|
||||
text = self._text_func(**{k: context[k] for k in self._args})
|
||||
url = self._url_func(**{k: context[k] for k in self._args})
|
||||
return f'<a href="{url}" class="button">{escape(text)}</a>'
|
||||
|
||||
def render_plain(self, **context):
|
||||
text = self._text_func(**{k: context[k] for k in self._args})
|
||||
url = self._url_func(**{k: context[k] for k in self._args})
|
||||
return f'{text}: {url}'
|
||||
|
||||
def render_sample_html(self, event):
|
||||
text = self._sample_text_func(event)
|
||||
url = self._sample_url_func(event)
|
||||
return f'<a href="{url}" class="button">{escape(text)}</a>'
|
||||
|
||||
def render_sample_plain(self, event):
|
||||
text = self._sample_text_func(event)
|
||||
url = self._sample_url_func(event)
|
||||
return f'{text}: {url}'
|
||||
|
||||
|
||||
class PlaceholderContext(SafeFormatter):
|
||||
"""
|
||||
Holds the contextual arguments and corresponding list of available placeholders for formatting
|
||||
@@ -371,27 +284,6 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['order', 'event'],
|
||||
url_func=lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_secret()
|
||||
}
|
||||
),
|
||||
text_func=lambda order, event: _("View order details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'hash': '98kusd8ofsj8dnkd'
|
||||
}
|
||||
),
|
||||
sample_text_func=lambda event: _("View order details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
@@ -456,27 +348,6 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['event', 'position'],
|
||||
url_func=lambda event, position: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
),
|
||||
text_func=lambda event, position: _("View registration details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'position': '123'
|
||||
}
|
||||
),
|
||||
sample_text_func=lambda event: _("View registration details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
event,
|
||||
@@ -732,8 +603,8 @@ def base_placeholders(sender, **kwargs):
|
||||
|
||||
|
||||
class FormPlaceholderMixin:
|
||||
def _set_field_placeholders(self, fn, base_parameters, rich=False):
|
||||
placeholders = get_available_placeholders(self.event, base_parameters, rich=rich)
|
||||
def _set_field_placeholders(self, fn, base_parameters):
|
||||
placeholders = get_available_placeholders(self.event, base_parameters)
|
||||
ht = format_placeholders_help_text(placeholders, self.event)
|
||||
if self.fields[fn].help_text:
|
||||
self.fields[fn].help_text += ' ' + str(ht)
|
||||
@@ -744,7 +615,7 @@ class FormPlaceholderMixin:
|
||||
)
|
||||
|
||||
|
||||
def get_available_placeholders(event, base_parameters, rich=False):
|
||||
def get_available_placeholders(event, base_parameters):
|
||||
if 'order' in base_parameters:
|
||||
base_parameters.append('invoice_address')
|
||||
base_parameters.append('position_or_address')
|
||||
@@ -753,35 +624,6 @@ def get_available_placeholders(event, base_parameters, rich=False):
|
||||
if not isinstance(val, (list, tuple)):
|
||||
val = [val]
|
||||
for v in val:
|
||||
if isinstance(v, BaseRichTextPlaceholder) and not rich:
|
||||
continue
|
||||
if all(rp in base_parameters for rp in v.required_context):
|
||||
params[v.identifier] = v
|
||||
return params
|
||||
|
||||
|
||||
def get_sample_context(event, context_parameters, rich=True):
|
||||
context_dict = {}
|
||||
lbl = _('This value will be replaced based on dynamic parameters.')
|
||||
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items():
|
||||
sample = v.render_sample(event)
|
||||
if isinstance(sample, PlainHtmlAlternativeString):
|
||||
context_dict[k] = PlainHtmlAlternativeString(
|
||||
sample.plain,
|
||||
'<{el} class="placeholder placeholder-html" title="{title}">{html}</{el}>'.format(
|
||||
el='div' if sample.is_block else 'span',
|
||||
title=lbl,
|
||||
html=sample.html,
|
||||
)
|
||||
)
|
||||
elif str(sample).strip().startswith('* ') or str(sample).startswith(' '):
|
||||
context_dict[k] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
lbl,
|
||||
markdown_compile_email(str(sample))
|
||||
)
|
||||
else:
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
lbl,
|
||||
escape(sample)
|
||||
)
|
||||
return context_dict
|
||||
|
||||
@@ -131,9 +131,6 @@
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
.content table td.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a.button {
|
||||
display: inline-block;
|
||||
@@ -181,9 +178,6 @@
|
||||
pre, pre code {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.text-right, .content table td.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
{% if rtl %}
|
||||
body {
|
||||
@@ -192,9 +186,6 @@
|
||||
.content {
|
||||
text-align: right;
|
||||
}
|
||||
.text-right, .content table td.text-right {
|
||||
text-align: left;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% block addcss %}{% endblock %}
|
||||
|
||||
@@ -305,7 +305,6 @@ def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes
|
||||
source,
|
||||
extensions=[
|
||||
'markdown.extensions.sane_lists',
|
||||
'markdown.extensions.tables',
|
||||
EmailNl2BrExtension(),
|
||||
LinkifyAndCleanExtension(
|
||||
linker,
|
||||
|
||||
@@ -22,30 +22,16 @@
|
||||
import pycountry
|
||||
from django.http import JsonResponse
|
||||
|
||||
from pretix.base.addressvalidation import (
|
||||
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
|
||||
)
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES
|
||||
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||
|
||||
|
||||
def states(request):
|
||||
cc = request.GET.get("country", "DE")
|
||||
info = {
|
||||
'street': {'required': True},
|
||||
'zipcode': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||
'city': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||
'state': {'visible': cc in COUNTRIES_WITH_STATE_IN_ADDRESS, 'required': cc in COUNTRIES_WITH_STATE_IN_ADDRESS},
|
||||
'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False},
|
||||
}
|
||||
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
return JsonResponse({'data': [], **info, })
|
||||
return JsonResponse({'data': []})
|
||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||
return JsonResponse({
|
||||
'data': [
|
||||
{'name': s.name, 'code': s.code[3:]}
|
||||
for s in sorted(statelist, key=lambda s: s.name)
|
||||
],
|
||||
**info,
|
||||
})
|
||||
return JsonResponse({'data': [
|
||||
{'name': s.name, 'code': s.code[3:]}
|
||||
for s in sorted(statelist, key=lambda s: s.name)
|
||||
]})
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pycountry
|
||||
@@ -76,10 +76,8 @@ from pretix.control.forms import (
|
||||
)
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.models import AlternativeDomainAssignment, KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_organizer_domain,
|
||||
)
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.plugins.banktransfer.payment import BankTransfer
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
@@ -365,9 +363,14 @@ class EventUpdateForm(I18nModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.change_slug = kwargs.pop('change_slug', False)
|
||||
self.domain = kwargs.pop('domain', False)
|
||||
|
||||
kwargs.setdefault('initial', {})
|
||||
self.instance = kwargs['instance']
|
||||
if self.domain and self.instance:
|
||||
initial_domain = self.instance.domains.first()
|
||||
if initial_domain:
|
||||
kwargs['initial'].setdefault('domain', initial_domain.domainname)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.change_slug:
|
||||
@@ -376,54 +379,48 @@ class EventUpdateForm(I18nModelForm):
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
|
||||
try:
|
||||
if self.domain:
|
||||
self.fields['domain'] = forms.CharField(
|
||||
max_length=255,
|
||||
label=_('Domain'),
|
||||
initial=self.instance.domain.domainname,
|
||||
required=False,
|
||||
disabled=True,
|
||||
help_text=_('You can configure this in your organizer settings.')
|
||||
)
|
||||
except KnownDomain.DoesNotExist:
|
||||
domain = get_organizer_domain(self.instance.organizer)
|
||||
try:
|
||||
current_domain_assignment = self.instance.alternative_domain_assignment
|
||||
except AlternativeDomainAssignment.DoesNotExist:
|
||||
current_domain_assignment = None
|
||||
self.fields['domain'] = forms.ChoiceField(
|
||||
label=_('Domain'),
|
||||
help_text=_('You can add more domains in your organizer account.'),
|
||||
choices=[('', _('Same as organizer account') + (f" ({domain})" if domain else ""))] + [
|
||||
(d.domainname, d.domainname) for d in self.instance.organizer.domains.filter(mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
],
|
||||
initial=current_domain_assignment.domain_id if current_domain_assignment else "",
|
||||
label=_('Custom domain'),
|
||||
required=False,
|
||||
help_text=_('You need to configure the custom domain in the webserver beforehand.')
|
||||
)
|
||||
self.fields['limit_sales_channels'].queryset = self.event.organizer.sales_channels.all()
|
||||
self.fields['limit_sales_channels'].widget = SalesChannelCheckboxSelectMultiple(self.event, attrs={
|
||||
'data-inverse-dependency': '<[name$=all_sales_channels]',
|
||||
}, choices=self.fields['limit_sales_channels'].widget.choices)
|
||||
|
||||
def clean_domain(self):
|
||||
d = self.cleaned_data['domain']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(event=self.instance.pk).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit)
|
||||
|
||||
try:
|
||||
current_domain_assignment = instance.alternative_domain_assignment
|
||||
except AlternativeDomainAssignment.DoesNotExist:
|
||||
current_domain_assignment = None
|
||||
if self.cleaned_data['domain'] and not hasattr(instance, 'domain'):
|
||||
domain = self.instance.organizer.domains.get(mode=KnownDomain.MODE_ORG_ALT_DOMAIN, domainname=self.cleaned_data["domain"])
|
||||
AlternativeDomainAssignment.objects.update_or_create(
|
||||
event=instance,
|
||||
defaults={
|
||||
"domain": domain,
|
||||
}
|
||||
)
|
||||
instance.cache.clear()
|
||||
elif current_domain_assignment:
|
||||
current_domain_assignment.delete()
|
||||
if self.domain:
|
||||
current_domain = instance.domains.first()
|
||||
if self.cleaned_data['domain']:
|
||||
if current_domain and current_domain.domainname != self.cleaned_data['domain']:
|
||||
current_domain.delete()
|
||||
KnownDomain.objects.create(
|
||||
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
|
||||
)
|
||||
elif not current_domain:
|
||||
KnownDomain.objects.create(
|
||||
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
|
||||
)
|
||||
elif current_domain:
|
||||
current_domain.delete()
|
||||
instance.cache.clear()
|
||||
|
||||
return instance
|
||||
@@ -1385,7 +1382,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
|
||||
self.event.meta_values_cached = self.event.meta_values.select_related('property').all()
|
||||
|
||||
for k, v in self.base_context.items():
|
||||
self._set_field_placeholders(k, v, rich=k.startswith('mail_text_'))
|
||||
self._set_field_placeholders(k, v)
|
||||
|
||||
for k, v in list(self.fields.items()):
|
||||
if k.endswith('_attendee') and not event.settings.attendee_emails_asked:
|
||||
|
||||
@@ -133,108 +133,63 @@ class OrganizerDeleteForm(forms.Form):
|
||||
class OrganizerUpdateForm(OrganizerForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.domain = kwargs.pop('domain', False)
|
||||
self.change_slug = kwargs.pop('change_slug', False)
|
||||
kwargs.setdefault('initial', {})
|
||||
self.instance = kwargs['instance']
|
||||
if self.domain and self.instance:
|
||||
initial_domain = self.instance.domains.filter(event__isnull=True).first()
|
||||
if initial_domain:
|
||||
kwargs['initial'].setdefault('domain', initial_domain.domainname)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.change_slug:
|
||||
self.fields['slug'].widget.attrs['readonly'] = 'readonly'
|
||||
if self.domain:
|
||||
self.fields['domain'] = forms.CharField(
|
||||
max_length=255,
|
||||
label=_('Custom domain'),
|
||||
required=False,
|
||||
help_text=_('You need to configure the custom domain in the webserver beforehand.')
|
||||
)
|
||||
|
||||
def clean_domain(self):
|
||||
d = self.cleaned_data['domain']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.pk,
|
||||
event__isnull=True).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def clean_slug(self):
|
||||
if self.change_slug:
|
||||
return self.cleaned_data['slug']
|
||||
return self.instance.slug
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit)
|
||||
|
||||
class KnownDomainForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = KnownDomain
|
||||
fields = ["domainname", "mode", "event"]
|
||||
field_classes = {
|
||||
"event": SafeModelChoiceField,
|
||||
}
|
||||
if self.domain:
|
||||
current_domain = instance.domains.filter(event__isnull=True).first()
|
||||
if self.cleaned_data['domain']:
|
||||
if current_domain and current_domain.domainname != self.cleaned_data['domain']:
|
||||
current_domain.delete()
|
||||
KnownDomain.objects.create(organizer=instance, domainname=self.cleaned_data['domain'])
|
||||
elif not current_domain:
|
||||
KnownDomain.objects.create(organizer=instance, domainname=self.cleaned_data['domain'])
|
||||
elif current_domain:
|
||||
current_domain.delete()
|
||||
instance.cache.clear()
|
||||
for ev in instance.events.all():
|
||||
ev.cache.clear()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["event"].queryset = self.organizer.events.all()
|
||||
if self.instance and self.instance.pk:
|
||||
self.fields["domainname"].widget.attrs['readonly'] = 'readonly'
|
||||
|
||||
def clean_domainname(self):
|
||||
if self.instance and self.instance.pk:
|
||||
return self.instance.domainname
|
||||
d = self.cleaned_data['domainname']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.organizer).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_ORG_DOMAIN and d["event"]:
|
||||
raise ValidationError(
|
||||
_("Do not choose an event for this mode.")
|
||||
)
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_ORG_ALT_DOMAIN and d["event"]:
|
||||
raise ValidationError(
|
||||
_("Do not choose an event for this mode. You can assign events to this domain in event settings.")
|
||||
)
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_EVENT_DOMAIN and not d["event"]:
|
||||
raise ValidationError(
|
||||
_("You need to choose an event.")
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class BaseKnownDomainFormSet(forms.BaseInlineFormSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['organizer'] = self.organizer
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
organizer=self.organizer,
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
data = [f.cleaned_data for f in self.forms]
|
||||
|
||||
if len([d for d in data if d.get("mode") == KnownDomain.MODE_ORG_DOMAIN and not d.get("DELETE")]) > 1:
|
||||
raise ValidationError(_("You may set only one organizer domain."))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
KnownDomainFormset = inlineformset_factory(
|
||||
Organizer, KnownDomain,
|
||||
KnownDomainForm,
|
||||
formset=BaseKnownDomainFormSet,
|
||||
can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
return instance
|
||||
|
||||
|
||||
class SafeOrderPositionChoiceField(forms.ModelChoiceField):
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{{ html_head|safe }}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.slug layout="control" %}
|
||||
{% if form.domain %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.date_from layout="control" %}
|
||||
{% bootstrap_field form.date_to layout="control" %}
|
||||
|
||||
@@ -46,13 +46,11 @@
|
||||
<div id="cp{{ pos.id }}">
|
||||
<div class="panel-body">
|
||||
{% for form in forms %}
|
||||
<div class="profile-scope">
|
||||
{% if form.pos.item != pos.item %}
|
||||
{# Add-Ons #}
|
||||
<legend>+ {{ form.pos.item }}</legend>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
</div>
|
||||
{% if form.pos.item != pos.item %}
|
||||
{# Add-Ons #}
|
||||
<legend>+ {{ form.pos.item }}</legend>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,71 +294,6 @@
|
||||
<legend>{% trans "Invoices" %}</legend>
|
||||
{% bootstrap_field sform.invoice_regenerate_allowed layout="control" %}
|
||||
</fieldset>
|
||||
{% if domain_formset %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Domains" %}</legend>
|
||||
<div class="alert alert-warning">
|
||||
{% trans "This dialog is intended for advanced users." %}
|
||||
{% trans "The domain needs to be configured on your webserver before it can be used here." %}
|
||||
</div>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ domain_formset.prefix }}">
|
||||
{{ domain_formset.management_form }}
|
||||
{% bootstrap_formset_errors domain_formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in domain_formset %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.domainname layout='' form_group_class="" %}
|
||||
{% bootstrap_form_errors form %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field form.mode layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field form.event layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<label aria-hidden="true"> </label><br>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ domain_formset.empty_form.id }}
|
||||
{% bootstrap_field domain_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field domain_formset.empty_form.domainname layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field domain_formset.empty_form.mode layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field domain_formset.empty_form.event layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<label aria-hidden="true"> </label><br>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add domain" %}</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -62,7 +62,7 @@ from django.http import (
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.html import escape
|
||||
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 _, gettext_noop
|
||||
@@ -100,12 +100,9 @@ from ...base.models.items import (
|
||||
Item, ItemCategory, ItemMetaProperty, Question, Quota,
|
||||
)
|
||||
from ...base.services.mail import prefix_subject
|
||||
from ...base.services.placeholders import get_sample_context
|
||||
from ...base.settings import LazyI18nStringList
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from ...helpers.format import (
|
||||
PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from ...helpers.format import format_map
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -242,6 +239,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
kwargs = super().get_form_kwargs()
|
||||
if self.request.user.has_active_staff_session(self.request.session.session_key):
|
||||
kwargs['change_slug'] = True
|
||||
kwargs['domain'] = True
|
||||
return kwargs
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -719,7 +717,20 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
return get_sample_context(self.request.event, MailSettingsForm.base_context[item])
|
||||
ctx = {}
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||
s = str(p.render_sample(self.request.event))
|
||||
if s.strip().startswith('* '):
|
||||
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
markdown_compile_email(s)
|
||||
)
|
||||
else:
|
||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(s)
|
||||
)
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
preview_item = request.POST.get('item', '')
|
||||
@@ -741,15 +752,9 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
bleach.clean(v), self.placeholders(preview_item), raise_on_missing=True
|
||||
), highlight=True)
|
||||
else:
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs[self.supported_locale[idx]] = format_map(
|
||||
markdown_compile_email(
|
||||
format_map(v, placeholders, raise_on_missing=True)
|
||||
),
|
||||
placeholders,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
format_map(v, self.placeholders(preview_item), raise_on_missing=True)
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
|
||||
PlaceholderValidator.error_message)
|
||||
@@ -772,18 +777,13 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item], rich=True).values():
|
||||
sample = p.render_sample(self.request.event)
|
||||
if isinstance(sample, PlainHtmlAlternativeString):
|
||||
ctx[p.identifier] = sample
|
||||
else:
|
||||
ctx[p.identifier] = conditional_escape(sample)
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||
ctx[p.identifier] = escape(str(p.render_sample(self.request.event)))
|
||||
return ctx
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
v = str(request.event.settings.mail_text_order_placed)
|
||||
context = self.placeholders('mail_text_order_placed')
|
||||
v = format_map(v, context)
|
||||
v = format_map(v, self.placeholders('mail_text_order_placed'))
|
||||
renderers = request.event.get_html_mail_renderers()
|
||||
if request.GET.get('renderer') in renderers:
|
||||
with rolledback_transaction():
|
||||
@@ -801,8 +801,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
str(request.event.settings.mail_text_signature),
|
||||
gettext('Your order: %(code)s') % {'code': order.code},
|
||||
order,
|
||||
position=None,
|
||||
context=context,
|
||||
position=None
|
||||
)
|
||||
r = HttpResponse(v, content_type='text/html')
|
||||
r._csp_ignore = True
|
||||
|
||||
@@ -134,7 +134,7 @@ from pretix.control.signals import order_search_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
@@ -2351,7 +2351,7 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
||||
'subject': mark_safe(_('Subject: {subject}').format(
|
||||
subject=prefix_subject(order.event, escape(email_subject), highlight=True)
|
||||
)),
|
||||
'html': format_map(markdown_compile_email(email_content), email_context, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
'html': markdown_compile_email(email_content)
|
||||
}
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
else:
|
||||
|
||||
@@ -104,11 +104,11 @@ from pretix.control.forms.organizer import (
|
||||
CustomerCreateForm, CustomerUpdateForm, DeviceBulkEditForm, DeviceForm,
|
||||
EventMetaPropertyAllowedValueFormSet, EventMetaPropertyForm, GateForm,
|
||||
GiftCardAcceptanceInviteForm, GiftCardCreateForm, GiftCardUpdateForm,
|
||||
KnownDomainFormset, MailSettingsForm, MembershipTypeForm,
|
||||
MembershipUpdateForm, OrganizerDeleteForm, OrganizerFooterLinkFormset,
|
||||
OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm,
|
||||
ReusableMediumCreateForm, ReusableMediumUpdateForm, SalesChannelForm,
|
||||
SSOClientForm, SSOProviderForm, TeamForm, WebHookForm,
|
||||
MailSettingsForm, MembershipTypeForm, MembershipUpdateForm,
|
||||
OrganizerDeleteForm, OrganizerFooterLinkFormset, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, ReusableMediumCreateForm,
|
||||
ReusableMediumUpdateForm, SalesChannelForm, SSOClientForm, SSOProviderForm,
|
||||
TeamForm, WebHookForm,
|
||||
)
|
||||
from pretix.control.forms.rrule import RRuleForm
|
||||
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||
@@ -122,7 +122,7 @@ from pretix.control.views.mailsetup import MailSettingsSetupView
|
||||
from pretix.helpers import OF_SELF, GroupConcat
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
@@ -357,10 +357,9 @@ class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
|
||||
highlight=True,
|
||||
)
|
||||
else:
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs[self.supported_locale[idx]] = format_map(markdown_compile_email(
|
||||
format_map(v, placeholders)
|
||||
), placeholders, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
format_map(v, self.placeholders(preview_item))
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'item': preview_item,
|
||||
@@ -448,10 +447,6 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def get_object(self, queryset=None) -> Organizer:
|
||||
return self.object
|
||||
|
||||
@cached_property
|
||||
def domain_config(self):
|
||||
return self.request.user.has_active_staff_session(self.request.session.session_key)
|
||||
|
||||
@cached_property
|
||||
def sform(self):
|
||||
return OrganizerSettingsForm(
|
||||
@@ -466,8 +461,6 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['footer_links_formset'] = self.footer_links_formset
|
||||
if self.domain_config:
|
||||
context['domain_formset'] = self.domain_formset
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
@@ -490,8 +483,6 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
self.request.organizer.log_action('pretix.organizer.footerlinks.changed', user=self.request.user, data={
|
||||
'data': self.footer_links_formset.cleaned_data
|
||||
})
|
||||
if self.domain_config and self.domain_formset.has_changed():
|
||||
self._save_domain_config()
|
||||
if form.has_changed():
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.changed',
|
||||
@@ -502,22 +493,10 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def _save_domain_config(self):
|
||||
for form in self.domain_formset.initial_forms:
|
||||
if form.instance.pk and form.has_changed():
|
||||
self.object.domains.get(pk=form.instance.pk).log_delete(self.request.user)
|
||||
self.domain_formset.save()
|
||||
for new_obj in self.domain_formset.new_objects:
|
||||
new_obj.log_create(self.request.user)
|
||||
for ch_obj, form in self.domain_formset.changed_objects:
|
||||
ch_obj.log_create(self.request.user)
|
||||
self.request.organizer.cache.clear()
|
||||
for ev in self.request.organizer.events.all():
|
||||
ev.cache.clear()
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
if self.request.user.has_active_staff_session(self.request.session.session_key):
|
||||
kwargs['domain'] = True
|
||||
kwargs['change_slug'] = True
|
||||
return kwargs
|
||||
|
||||
@@ -529,7 +508,7 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid() and self.footer_links_formset.is_valid() and (not self.domain_config or self.domain_formset.is_valid()):
|
||||
if form.is_valid() and self.sform.is_valid() and self.footer_links_formset.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
@@ -540,11 +519,6 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
organizer=self.object,
|
||||
prefix="footer-links", instance=self.object)
|
||||
|
||||
@cached_property
|
||||
def domain_formset(self):
|
||||
return KnownDomainFormset(self.request.POST if self.request.method == "POST" else None, prefix="domains",
|
||||
instance=self.object, organizer=self.object)
|
||||
|
||||
def save_footer_links_formset(self, obj):
|
||||
self.footer_links_formset.save()
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from django.http import (
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -59,12 +59,12 @@ from django.views.generic import (
|
||||
)
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.models import (
|
||||
CartPosition, LogEntry, Voucher, WaitingListEntry,
|
||||
)
|
||||
from pretix.base.models.vouchers import generate_codes
|
||||
from pretix.base.services.mail import prefix_subject
|
||||
from pretix.base.services.placeholders import get_sample_context
|
||||
from pretix.base.services.vouchers import vouchers_send
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.base.views.tasks import AsyncFormView
|
||||
@@ -74,7 +74,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import voucher_form_class
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
@@ -549,10 +549,22 @@ class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
base_ctx = ['event', 'name']
|
||||
if item == 'send_message':
|
||||
base_ctx += ['voucher_list']
|
||||
ctx = get_sample_context(self.request.event, base_ctx)
|
||||
for p in get_available_placeholders(self.request.event, base_ctx).values():
|
||||
s = str(p.render_sample(self.request.event))
|
||||
if s.strip().startswith('* ') or s.startswith(' '):
|
||||
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
markdown_compile_email(s)
|
||||
)
|
||||
else:
|
||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(s)
|
||||
)
|
||||
return self.SafeDict(ctx)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -567,10 +579,9 @@ class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
|
||||
highlight=True
|
||||
)
|
||||
else:
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs["all"] = format_map(markdown_compile_email(
|
||||
format_map(request.POST.get(preview_item), placeholders)
|
||||
), placeholders, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
msgs["all"] = markdown_compile_email(
|
||||
format_map(request.POST.get(preview_item), self.placeholders(preview_item))
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'item': preview_item,
|
||||
|
||||
@@ -25,29 +25,14 @@ from string import Formatter
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlainHtmlAlternativeString:
|
||||
def __init__(self, plain, html, is_block=False):
|
||||
self.plain = plain
|
||||
self.html = html
|
||||
self.is_block = is_block
|
||||
|
||||
def __repr__(self):
|
||||
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
|
||||
|
||||
|
||||
class SafeFormatter(Formatter):
|
||||
"""
|
||||
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
|
||||
(b) does not allow any unwanted shenanigans like attribute access or format specifiers.
|
||||
"""
|
||||
MODE_IGNORE_RICH = 0
|
||||
MODE_RICH_TO_PLAIN = 1
|
||||
MODE_RICH_TO_HTML = 2
|
||||
|
||||
def __init__(self, context, raise_on_missing=False, mode=MODE_IGNORE_RICH):
|
||||
def __init__(self, context, raise_on_missing=False):
|
||||
self.context = context
|
||||
self.raise_on_missing = raise_on_missing
|
||||
self.mode = mode
|
||||
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
return self.get_value(field_name, args, kwargs), field_name
|
||||
@@ -55,22 +40,14 @@ class SafeFormatter(Formatter):
|
||||
def get_value(self, key, args, kwargs):
|
||||
if not self.raise_on_missing and key not in self.context:
|
||||
return '{' + str(key) + '}'
|
||||
r = self.context[key]
|
||||
if isinstance(r, PlainHtmlAlternativeString):
|
||||
if self.mode == self.MODE_IGNORE_RICH:
|
||||
return '{' + str(key) + '}'
|
||||
elif self.mode == self.MODE_RICH_TO_PLAIN:
|
||||
return r.plain
|
||||
elif self.mode == self.MODE_RICH_TO_HTML:
|
||||
return r.html
|
||||
return r
|
||||
return self.context[key]
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
# Ignore format_spec
|
||||
# Ignore format _spec
|
||||
return super().format_field(value, '')
|
||||
|
||||
|
||||
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_IGNORE_RICH):
|
||||
def format_map(template, context, raise_on_missing=False):
|
||||
if not isinstance(template, str):
|
||||
template = str(template)
|
||||
return SafeFormatter(context, raise_on_missing, mode=mode).format(template)
|
||||
return SafeFormatter(context, raise_on_missing).format(template)
|
||||
|
||||
@@ -8,17 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-11-25 18:00+0000\n"
|
||||
"Last-Translator: Jakub Stribrny <kubajznik@users.noreply.translate.pretix.eu>"
|
||||
"\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
|
||||
"\n"
|
||||
"PO-Revision-Date: 2023-09-15 15:21+0000\n"
|
||||
"Last-Translator: Michael <michael.happl@gmx.at>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
|
||||
">\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
"X-Generator: Weblate 5.0.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -12462,7 +12461,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/forms/checkin.py:176
|
||||
msgid "Barcode"
|
||||
msgstr "Čárový kód"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/checkin.py:179
|
||||
msgid "Check-in time"
|
||||
@@ -12475,8 +12474,6 @@ msgstr "Typ check-inu"
|
||||
#: pretix/control/forms/checkin.py:187
|
||||
msgid "Allow check-in of unpaid order (if check-in list permits it)"
|
||||
msgstr ""
|
||||
"Povolit check-in pro nezaplacenou objednávku (pokud to seznam check-in "
|
||||
"dovoluje)"
|
||||
|
||||
#: pretix/control/forms/checkin.py:191
|
||||
msgid "Support for check-in questions"
|
||||
@@ -12511,11 +12508,11 @@ msgstr "Časové pásmo akce"
|
||||
|
||||
#: pretix/control/forms/event.py:140
|
||||
msgid "I don't want to specify taxes now"
|
||||
msgstr "Přidat podrobnosti o daních později"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:141
|
||||
msgid "You can always configure tax rates later."
|
||||
msgstr "Daňové sazby můžete nastavit kdykoliv."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:145
|
||||
msgid "Sales tax rate"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-11-29 23:00+0000\n"
|
||||
"PO-Revision-Date: 2024-11-19 15:15+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"es/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -3799,7 +3799,7 @@ msgstr "Todos los productos (incluso los recién creados)"
|
||||
#: pretix/base/models/checkin.py:56 pretix/plugins/badges/exporters.py:436
|
||||
#: pretix/plugins/checkinlists/exporters.py:842
|
||||
msgid "Limit to products"
|
||||
msgstr "Límite a los productos"
|
||||
msgstr "Limita a los productos"
|
||||
|
||||
#: pretix/base/models/checkin.py:60
|
||||
msgid ""
|
||||
@@ -9565,7 +9565,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py:1175
|
||||
msgid "Introductory text"
|
||||
msgstr "Texto de introducción"
|
||||
msgstr "Texto introductorio"
|
||||
|
||||
#: pretix/base/settings.py:1176
|
||||
msgid "Will be printed on every invoice above the invoice rows."
|
||||
@@ -11397,7 +11397,7 @@ msgstr "Color de fondo de la página"
|
||||
|
||||
#: pretix/base/settings.py:2845
|
||||
msgid "Use round edges"
|
||||
msgstr "Utilizar bordes redondos"
|
||||
msgstr "Utilice bordes redondos"
|
||||
|
||||
#: pretix/base/settings.py:2854
|
||||
msgid ""
|
||||
@@ -11433,7 +11433,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py:2899 pretix/base/settings.py:2941
|
||||
msgid "Use header image in its full size"
|
||||
msgstr "Utilizar la imagen del encabezado en su tamaño completo"
|
||||
msgstr "Utilice la imagen del encabezado en su tamaño completo"
|
||||
|
||||
#: pretix/base/settings.py:2900 pretix/base/settings.py:2942
|
||||
msgid "We recommend to upload a picture at least 1170 pixels wide."
|
||||
@@ -12822,11 +12822,11 @@ msgstr "Zona horaria del evento"
|
||||
|
||||
#: pretix/control/forms/event.py:140
|
||||
msgid "I don't want to specify taxes now"
|
||||
msgstr "No quiero especificar los impuestos ahora"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:141
|
||||
msgid "You can always configure tax rates later."
|
||||
msgstr "Siempre puede configurar los tipos impositivos más adelante."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:145
|
||||
msgid "Sales tax rate"
|
||||
@@ -12879,8 +12879,6 @@ msgid ""
|
||||
"You have not specified a tax rate. If you do not want us to compute sales "
|
||||
"taxes, please check \"{field}\" above."
|
||||
msgstr ""
|
||||
"No ha especificado el tipo impositivo. Si no desea que calculemos los "
|
||||
"impuestos sobre las ventas, marque «{field}» más arriba."
|
||||
|
||||
#: pretix/control/forms/event.py:308
|
||||
msgid "Copy configuration from"
|
||||
@@ -13067,12 +13065,13 @@ msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:989 pretix/control/forms/organizer.py:534
|
||||
msgid "Bcc address"
|
||||
msgstr "Direcciones en copia oculta"
|
||||
msgstr "Direcciones CCO"
|
||||
|
||||
#: pretix/control/forms/event.py:990 pretix/control/forms/organizer.py:535
|
||||
msgid "All emails will be sent to this address as a Bcc copy"
|
||||
msgstr ""
|
||||
"Todos los correos electrónicos se enviarán a esta dirección en copia oculta"
|
||||
"Todos los correos electrónicos se enviarán a esta dirección como una copia "
|
||||
"de CCO"
|
||||
|
||||
#: pretix/control/forms/event.py:996 pretix/control/forms/organizer.py:541
|
||||
msgid "Signature"
|
||||
@@ -13649,7 +13648,7 @@ msgstr "Fecha de inicio"
|
||||
#: pretix/control/forms/filter.py:1710 pretix/control/forms/filter.py:1713
|
||||
#: pretix/control/forms/filter.py:2344
|
||||
msgid "Date until"
|
||||
msgstr "Fecha final"
|
||||
msgstr "Fecha límite"
|
||||
|
||||
#: pretix/control/forms/filter.py:1218
|
||||
msgid "Start time from"
|
||||
@@ -16019,11 +16018,11 @@ msgstr ""
|
||||
|
||||
#: pretix/control/logdisplay.py:422
|
||||
msgid "A custom email has been sent."
|
||||
msgstr "Un email personalizado ha sido enviado."
|
||||
msgstr "Un e-mail personalizado ha sido enviado."
|
||||
|
||||
#: pretix/control/logdisplay.py:423
|
||||
msgid "A custom email has been sent to an attendee."
|
||||
msgstr "Un email personalizado ha sido enviado al participante."
|
||||
msgstr "Un e-mail personalizado ha sido enviado al participante."
|
||||
|
||||
#: pretix/control/logdisplay.py:424
|
||||
msgid ""
|
||||
@@ -16471,7 +16470,7 @@ msgstr "Un plugin ha sido desactivado."
|
||||
|
||||
#: pretix/control/logdisplay.py:533
|
||||
msgid "The shop has been taken live."
|
||||
msgstr "La tienda ha sido puesta en marcha."
|
||||
msgstr "La tienda ha sido tomada en vivo."
|
||||
|
||||
#: pretix/control/logdisplay.py:534
|
||||
msgid "The shop has been taken offline."
|
||||
@@ -16950,11 +16949,11 @@ msgstr "Parametrizaciones globales"
|
||||
|
||||
#: pretix/control/navigation.py:440
|
||||
msgid "Update check"
|
||||
msgstr "Comprobación de actualizaciones"
|
||||
msgstr "Verificación de actualización"
|
||||
|
||||
#: pretix/control/navigation.py:445
|
||||
msgid "License check"
|
||||
msgstr "Verificación de la licencia"
|
||||
msgstr "Revisa de licencia"
|
||||
|
||||
#: pretix/control/navigation.py:450
|
||||
msgid "System report"
|
||||
@@ -17951,7 +17950,7 @@ msgstr "Regla de check-in personalizada"
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:117
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html:84
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
msgstr "Tratar"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/list_edit.html:89
|
||||
msgid "Visualize"
|
||||
@@ -20027,8 +20026,8 @@ msgid ""
|
||||
"For more information or to obtain a paid pretix Enterprise license, contact "
|
||||
"support@pretix.eu."
|
||||
msgstr ""
|
||||
"Para obtener más información u obtener una licencia pretix Enterprise de "
|
||||
"pago, comuníquese con support@pretix.eu."
|
||||
"Para obtener más información u obtener una licencia pretix Enterprise paga, "
|
||||
"comuníquese con support@pretix.eu."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_license.html:26
|
||||
msgid "License settings and check"
|
||||
@@ -20070,9 +20069,10 @@ msgid ""
|
||||
"pretix support when your license renews. It may also be requested by pretix "
|
||||
"support to aid debugging of problems."
|
||||
msgstr ""
|
||||
"Si dispone de una licencia de pretix Enterprise, deberá enviar este informe "
|
||||
"al servicio de asistencia pretix cuando renueve su licencia. También puede "
|
||||
"ser solicitado por el soporte pretix para ayudar a la solución de problemas."
|
||||
"Si tienes una licencia de pretix de Enterprise, este informe se debe "
|
||||
"entregar al equipo del servicio al cliente de pretix cuando tu licencia "
|
||||
"renueve. También el servicio al cliente de pretix podría pedirlo para ayudar "
|
||||
"durante depurar."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html:8
|
||||
msgid ""
|
||||
@@ -21286,11 +21286,11 @@ msgstr "Revocar"
|
||||
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:6
|
||||
#: pretix/control/templates/pretixcontrol/user/settings.html:61
|
||||
msgid "Authorized applications"
|
||||
msgstr "Aplicaciones autorizadas"
|
||||
msgstr "Solicitudes autorizadas"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:9
|
||||
msgid "Manage your own apps"
|
||||
msgstr "Gestione sus propias aplicaciones"
|
||||
msgstr "Gestiona tus propias aplicaciones"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:18
|
||||
msgid "Permissions"
|
||||
@@ -31601,7 +31601,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/form.html:14
|
||||
msgid "Open Layout Designer"
|
||||
msgstr "Abrir herramienta de diseño"
|
||||
msgstr "Abrir diseñador de diseño"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/templates/pretixplugins/ticketoutputpdf/form.html:18
|
||||
msgid "Advanced mode (multiple layouts)"
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-11-29 23:00+0000\n"
|
||||
"PO-Revision-Date: 2024-11-19 15:15+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -416,9 +416,7 @@ msgstr "Une facture existe déjà pour cet ordre."
|
||||
#: pretix/api/views/order.py:637 pretix/control/views/orders.py:1716
|
||||
#: pretix/control/views/users.py:143
|
||||
msgid "There was an error sending the mail. Please try again later."
|
||||
msgstr ""
|
||||
"Une erreur s'est produite lors de l'envoi de l'e-mail. Veuillez réessayer "
|
||||
"plus tard."
|
||||
msgstr "Il y a eu une erreur d'envoi du mail. Veuillez réessayer plus tard."
|
||||
|
||||
#: pretix/api/views/order.py:715 pretix/base/services/cart.py:215
|
||||
#: pretix/base/services/orders.py:186 pretix/presale/views/order.py:799
|
||||
@@ -1413,7 +1411,7 @@ msgstr "Adresse"
|
||||
#: pretix/plugins/checkinlists/exporters.py:533
|
||||
#: pretix/plugins/reports/exporters.py:841
|
||||
msgid "ZIP code"
|
||||
msgstr "Code postal"
|
||||
msgstr "Code Postal"
|
||||
|
||||
#: pretix/base/exporters/invoices.py:209 pretix/base/exporters/invoices.py:217
|
||||
#: pretix/base/exporters/invoices.py:335 pretix/base/exporters/invoices.py:343
|
||||
@@ -1497,7 +1495,7 @@ msgstr "Destinataire de facture :"
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:83
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:307
|
||||
msgid "Company"
|
||||
msgstr "Entreprise"
|
||||
msgstr "Société"
|
||||
|
||||
#: pretix/base/exporters/invoices.py:215 pretix/base/exporters/invoices.py:341
|
||||
msgid "Street address"
|
||||
@@ -2136,7 +2134,7 @@ msgstr "Règle fiscale"
|
||||
#: pretix/base/exporters/orderlist.py:644
|
||||
#: pretix/base/exporters/orderlist.py:648 pretix/base/pdf.py:330
|
||||
msgid "Invoice address name"
|
||||
msgstr "Nom de l'adresse de facturation"
|
||||
msgstr "Adresse de facturation"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:480
|
||||
#: pretix/base/exporters/orderlist.py:683 pretix/base/models/orders.py:204
|
||||
@@ -2235,7 +2233,7 @@ msgstr "Nom du participant"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:176
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:179
|
||||
msgid "Attendee email"
|
||||
msgstr "E-mail du participant"
|
||||
msgstr "Adresse mail du participant"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:609 pretix/base/models/vouchers.py:312
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:5
|
||||
@@ -2321,19 +2319,19 @@ msgstr "Add-on à la position ID"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:650 pretix/base/pdf.py:340
|
||||
msgid "Invoice address street"
|
||||
msgstr "Adresse de facturation (rue)"
|
||||
msgstr "Adresse de facturation : rue"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:650 pretix/base/pdf.py:345
|
||||
msgid "Invoice address ZIP code"
|
||||
msgstr "Adresse de facturation (Code postal)"
|
||||
msgstr "Adresse de facturation : code postal"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:650 pretix/base/pdf.py:350
|
||||
msgid "Invoice address city"
|
||||
msgstr "Adresse de facturation (ville)"
|
||||
msgstr "Adresse de facturation : ville"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:651 pretix/base/pdf.py:360
|
||||
msgid "Invoice address country"
|
||||
msgstr "Adresse de facturation (pays)"
|
||||
msgstr "Adresse de facturation : pays"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:652
|
||||
msgctxt "address"
|
||||
@@ -3589,7 +3587,7 @@ msgstr "Vous ne pouvez pas attribuer un poste secret qui existe déjà."
|
||||
|
||||
#: pretix/base/modelimport_orders.py:490
|
||||
msgid "Please enter a valid language code."
|
||||
msgstr "Veuillez saisir un code linguistique valide."
|
||||
msgstr "Veuillez saisir un code de langue valide."
|
||||
|
||||
#: pretix/base/modelimport_orders.py:558 pretix/base/modelimport_orders.py:560
|
||||
msgid "Please enter a valid sales channel."
|
||||
@@ -3612,8 +3610,8 @@ msgstr "Aucun siège correspondant n’a été trouvé."
|
||||
msgid ""
|
||||
"The seat you selected has already been taken. Please select a different seat."
|
||||
msgstr ""
|
||||
"La place que vous avez choisie est déjà occupée. Veuillez choisir une autre "
|
||||
"place."
|
||||
"Le siège que vous avez sélectionné a déjà été pris. Veuillez sélectionner un "
|
||||
"autre siège."
|
||||
|
||||
#: pretix/base/modelimport_orders.py:592 pretix/base/services/cart.py:209
|
||||
msgid "You need to select a specific seat."
|
||||
@@ -3731,7 +3729,7 @@ msgstr "Vous devez choisir le produit « {prod} » pour ce siège."
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/tags.html:42
|
||||
#: pretix/control/views/vouchers.py:120
|
||||
msgid "Tag"
|
||||
msgstr "Balise"
|
||||
msgstr "Tag"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:334 pretix/base/models/vouchers.py:297
|
||||
msgid "Shows hidden products that match this voucher"
|
||||
@@ -3998,7 +3996,7 @@ msgstr ""
|
||||
#: pretix/base/models/customers.py:310 pretix/base/models/orders.py:1534
|
||||
#: pretix/base/models/orders.py:3204 pretix/base/settings.py:1108
|
||||
msgid "Company name"
|
||||
msgstr "Nom de l'entreprise"
|
||||
msgstr "Nom de la société"
|
||||
|
||||
#: pretix/base/models/customers.py:314 pretix/base/models/orders.py:1538
|
||||
#: pretix/base/models/orders.py:3211 pretix/base/settings.py:81
|
||||
@@ -5782,7 +5780,7 @@ msgstr "expiré"
|
||||
#: pretix/base/models/orders.py:253 pretix/control/forms/filter.py:560
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:64
|
||||
msgid "Locale"
|
||||
msgstr "Langue"
|
||||
msgstr "Régionalisation"
|
||||
|
||||
#: pretix/base/models/orders.py:268 pretix/control/forms/filter.py:571
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:57
|
||||
@@ -6068,7 +6066,7 @@ msgstr "Badge"
|
||||
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:66
|
||||
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:113
|
||||
msgid "Ticket"
|
||||
msgstr "Billet"
|
||||
msgstr "Ticket"
|
||||
|
||||
#: pretix/base/models/orders.py:3405
|
||||
msgid "Certificate"
|
||||
@@ -6474,7 +6472,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:6
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:8
|
||||
msgid "Vouchers"
|
||||
msgstr "Bons d'achat"
|
||||
msgstr "Bons de réduction"
|
||||
|
||||
#: pretix/base/models/vouchers.py:339
|
||||
msgid "You cannot select a quota that belongs to a different event."
|
||||
@@ -7178,7 +7176,7 @@ msgstr "Jacques Martin"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:186
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:189
|
||||
msgid "Attendee company"
|
||||
msgstr "Entreprise du participant"
|
||||
msgstr "Entreprise participante"
|
||||
|
||||
#: pretix/base/pdf.py:178 pretix/base/pdf.py:336
|
||||
#: pretix/base/services/tickets.py:118 pretix/control/views/pdf.py:111
|
||||
@@ -7322,7 +7320,7 @@ msgstr "Ville quelconque"
|
||||
|
||||
#: pretix/base/pdf.py:335
|
||||
msgid "Invoice address company"
|
||||
msgstr "Adresse de facturation de l'entreprise"
|
||||
msgstr "Adresse de la facturation de l'entreprise"
|
||||
|
||||
#: pretix/base/pdf.py:341
|
||||
msgid "Sesame Street 42"
|
||||
@@ -7495,7 +7493,7 @@ msgstr "Nom du participant pour la formule de salutation"
|
||||
#: pretix/base/services/placeholders.py:567
|
||||
#: pretix/control/forms/organizer.py:612
|
||||
msgid "Mr Doe"
|
||||
msgstr "M. Doe"
|
||||
msgstr "M. Dupont"
|
||||
|
||||
#: pretix/base/pdf.py:655 pretix/base/pdf.py:662
|
||||
#: pretix/plugins/badges/exporters.py:501
|
||||
@@ -9966,7 +9964,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py:1520
|
||||
msgid "Allow users to download tickets"
|
||||
msgstr "Autoriser les utilisateurs à télécharger les billets"
|
||||
msgstr "Autoriser les utilisateurs à télécharger des billets"
|
||||
|
||||
#: pretix/base/settings.py:1521
|
||||
msgid "If this is off, nobody can download a ticket."
|
||||
@@ -12957,11 +12955,11 @@ msgstr "Fuseau horaire de l'événement"
|
||||
|
||||
#: pretix/control/forms/event.py:140
|
||||
msgid "I don't want to specify taxes now"
|
||||
msgstr "Je ne veux pas spécifier les taxes maintenant"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:141
|
||||
msgid "You can always configure tax rates later."
|
||||
msgstr "Vous pouvez toujours configurer les taux d'imposition ultérieurement."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/event.py:145
|
||||
msgid "Sales tax rate"
|
||||
@@ -13016,9 +13014,6 @@ msgid ""
|
||||
"You have not specified a tax rate. If you do not want us to compute sales "
|
||||
"taxes, please check \"{field}\" above."
|
||||
msgstr ""
|
||||
"Vous n'avez pas spécifié de taux d'imposition. Si vous ne souhaitez pas que "
|
||||
"nous calculions les impôts sur les ventes, veuillez cocher \"{field}\" ci-"
|
||||
"dessus."
|
||||
|
||||
#: pretix/control/forms/event.py:308
|
||||
msgid "Copy configuration from"
|
||||
@@ -13775,7 +13770,7 @@ msgstr "Prévente terminée"
|
||||
#: pretix/control/forms/filter.py:2339
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:84
|
||||
msgid "Date from"
|
||||
msgstr "Date à partir de"
|
||||
msgstr "Date de début"
|
||||
|
||||
#: pretix/control/forms/filter.py:1211 pretix/control/forms/filter.py:1214
|
||||
#: pretix/control/forms/filter.py:1710 pretix/control/forms/filter.py:1713
|
||||
@@ -16986,7 +16981,7 @@ msgstr "Widget"
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:47
|
||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:12
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
msgstr "Réglages"
|
||||
|
||||
#: pretix/control/navigation.py:164
|
||||
msgid "Categories"
|
||||
@@ -17020,7 +17015,7 @@ msgstr "Tous les bons de réduction"
|
||||
|
||||
#: pretix/control/navigation.py:284
|
||||
msgid "Tags"
|
||||
msgstr "Balises"
|
||||
msgstr "Tags"
|
||||
|
||||
#: pretix/control/navigation.py:296
|
||||
msgctxt "navigation"
|
||||
@@ -17856,7 +17851,7 @@ msgstr "Supprimer"
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:91
|
||||
#: pretix/presale/templates/pretixpresale/fragment_event_list_filter.html:21
|
||||
msgid "Filter"
|
||||
msgstr "Filtrer"
|
||||
msgstr "Filtre"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:50
|
||||
msgid "Your search did not match any check-ins."
|
||||
@@ -17917,7 +17912,7 @@ msgstr "Refusé"
|
||||
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:152
|
||||
#: pretix/control/templates/pretixcontrol/event/index.html:24
|
||||
msgid "Copy to clipboard"
|
||||
msgstr "Copier dans le presse-papier"
|
||||
msgstr "Copier dans le Presse-papiers"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:7
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:11
|
||||
@@ -18254,7 +18249,7 @@ msgstr "Voir tous les événements récents"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:65
|
||||
msgid "Your event series"
|
||||
msgstr "Vos séries d'événements"
|
||||
msgstr "Votre série d'événements"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/dashboard.html:81
|
||||
msgid "View all event series"
|
||||
@@ -21947,7 +21942,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:229
|
||||
msgid "Contact email"
|
||||
msgstr "E-mail de contact"
|
||||
msgstr "Email de contact"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:233
|
||||
msgid ""
|
||||
@@ -22209,21 +22204,21 @@ msgstr "Historique des commandes"
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html:4
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html:6
|
||||
msgid "Email history"
|
||||
msgstr "Historique de l'e-mail"
|
||||
msgstr "Historique des e-mails"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/mail_history.html:33
|
||||
msgid ""
|
||||
"This email has been sent with an older version of pretix. We are therefore "
|
||||
"not able to display it here accurately."
|
||||
msgstr ""
|
||||
"Cet e-mail a été envoyé avec une ancienne version de pretix. Nous ne sommes "
|
||||
"Cet email a été envoyé avec une ancienne version de pretix. Nous ne sommes "
|
||||
"donc pas en mesure de l'afficher correctement ici."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/mail_history.html:39
|
||||
#: pretix/control/templates/pretixcontrol/order/mail_history.html:50
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html:30
|
||||
msgid "Subject:"
|
||||
msgstr "Sujet :"
|
||||
msgstr "Sujet :"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/pay.html:5
|
||||
#: pretix/control/templates/pretixcontrol/order/pay.html:9
|
||||
@@ -22862,7 +22857,7 @@ msgstr "Aperçu des données"
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_process.html:43
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_process.html:43
|
||||
msgid "Import settings"
|
||||
msgstr "Importer les paramètres"
|
||||
msgstr "Paramétrages d’importation"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_process.html:49
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_process.html:49
|
||||
@@ -22870,13 +22865,13 @@ msgid ""
|
||||
"The import will be performed regardless of your quotas, so it will be "
|
||||
"possible to overbook your event using this option."
|
||||
msgstr ""
|
||||
"L'importation sera effectuée sans tenir compte de vos quotas, de sorte qu'il "
|
||||
"sera possible de surbooker votre événement à l'aide de cette option."
|
||||
"L’importation sera effectuée quels que soient vos quotas, il sera donc "
|
||||
"possible de surréserver votre événement en utilisant cette option."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_process.html:57
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_process.html:57
|
||||
msgid "Perform import"
|
||||
msgstr "Réaliser l'importation"
|
||||
msgstr "Effectuer l’importation"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:10
|
||||
@@ -22890,15 +22885,15 @@ msgid ""
|
||||
"The uploaded file should be a CSV file with a header row. You will be able "
|
||||
"to assign the meanings of the different columns in the next step."
|
||||
msgstr ""
|
||||
"Le fichier téléchargé doit être un fichier CSV avec une ligne d'en-tête. "
|
||||
"Vous pourrez définir la signification des différentes colonnes à l'étape "
|
||||
"Le fichier téléchargé doit être un fichier CSV avec une ligne d’en-tête. "
|
||||
"Vous pourrez attribuer les significations des différentes colonnes à l’étape "
|
||||
"suivante."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:22
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:22
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:46
|
||||
msgid "Import file"
|
||||
msgstr "Importer le fichier"
|
||||
msgstr "Importer un fichier"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:25
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:25
|
||||
@@ -22913,7 +22908,7 @@ msgstr "Détecter automatiquement"
|
||||
#: pretix/control/templates/pretixcontrol/orders/import_start.html:35
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:35
|
||||
msgid "Start import"
|
||||
msgstr "Lancer l'importation"
|
||||
msgstr "Démarrer l'exportation"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:14
|
||||
msgid "Nobody ordered a ticket yet."
|
||||
@@ -25526,7 +25521,7 @@ msgstr "Créer plusieurs bons"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:12
|
||||
msgid "Voucher codes"
|
||||
msgstr "Codes de bons d'achat"
|
||||
msgstr "Codes de réduction"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:17
|
||||
msgid "Prefix (optional)"
|
||||
@@ -25705,7 +25700,7 @@ msgstr "Expiration"
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:183
|
||||
#, python-format
|
||||
msgid "Any product in quota \"%(quota)s\""
|
||||
msgstr "Tout produit dans le quota « %(quota)s »"
|
||||
msgstr "Tout produit dans le quota \"%(quota)s\""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:200
|
||||
msgid "Use as a template for new vouchers"
|
||||
@@ -26816,7 +26811,9 @@ msgstr[1] ""
|
||||
#: pretix/presale/views/order.py:1279 pretix/presale/views/order.py:1662
|
||||
#: pretix/presale/views/order.py:1693
|
||||
msgid "Unknown order code or not authorized to access this order."
|
||||
msgstr "Code de commande inconnu ou non autorisé pour accéder à cette commande."
|
||||
msgstr ""
|
||||
"Code de commande inconnu ou utilisateur non autorisé à accéder à cette "
|
||||
"commande."
|
||||
|
||||
#: pretix/control/views/orders.py:675 pretix/presale/views/order.py:1111
|
||||
msgid "Ticket download is not enabled for this product."
|
||||
@@ -29300,7 +29297,7 @@ msgstr "Date de téléchargement"
|
||||
|
||||
#: pretix/plugins/checkinlists/exporters.py:767
|
||||
msgid "Upload time"
|
||||
msgstr "Temps de chargement"
|
||||
msgstr "Temps de téléchargement"
|
||||
|
||||
#: pretix/plugins/checkinlists/exporters.py:818
|
||||
msgid "OK"
|
||||
@@ -33147,7 +33144,7 @@ msgstr "Nous appliquons ce bon de réduction à votre panier..."
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:56
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:26
|
||||
msgid "Redeem voucher"
|
||||
msgstr "Utiliser le bon d'achat"
|
||||
msgstr "Utiliser bon d'achat"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:10
|
||||
msgid "Change summary"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-12-02 06:00+0000\n"
|
||||
"PO-Revision-Date: 2024-11-15 20:00+0000\n"
|
||||
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"hu/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -2796,9 +2796,12 @@ msgid "Reusable media"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/exporters/reusablemedia.py:35
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgctxt "export_category"
|
||||
msgid "Reusable media"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/base/exporters/reusablemedia.py:36
|
||||
msgid ""
|
||||
@@ -3569,8 +3572,11 @@ msgid "Invalid option selected."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/modelimport_orders.py:658 pretix/base/modelimport_orders.py:666
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Ambiguous option selected."
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/base/modelimport_orders.py:697 pretix/base/models/orders.py:238
|
||||
#: pretix/control/forms/orders.py:686 pretix/control/forms/organizer.py:795
|
||||
@@ -7714,7 +7720,8 @@ msgid ""
|
||||
"You are receiving this email because someone placed an order for {event} for "
|
||||
"you."
|
||||
msgstr ""
|
||||
"Ezt az emailt a következő eseményre való rendelésed kapcsán küldtük: {event}"
|
||||
"Azért küldtük ezt az e-mailt mert valaki rendelt neked jegyet a következő "
|
||||
"eseményre: {event}"
|
||||
|
||||
#: pretix/base/services/mail.py:282 pretix/base/services/mail.py:298
|
||||
#, python-brace-format
|
||||
@@ -8256,8 +8263,11 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/settings.py:190
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Activate re-usable media"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/base/settings.py:191
|
||||
msgid ""
|
||||
@@ -9707,7 +9717,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Szia!\n"
|
||||
"\n"
|
||||
"A rendelésed sikeres volt a következő eseményre: {event}.\n"
|
||||
"A rendelésed sikeres volt a következő eseményre: {event}\n"
|
||||
"Mivel csak ingyenes termékeket rendeltél, nem kell fizetned.\n"
|
||||
"\n"
|
||||
"Módosíthatod a rendelésed részleteit és megtekintheted az állapotát a "
|
||||
@@ -10247,7 +10257,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Szia!\n"
|
||||
"\n"
|
||||
"Elfogadtuk a rendelésedet a következő eseményre és örömmel várunk: {event}.\n"
|
||||
"Elfogadtuk a rendelésedet a következő eseményre és örömmel várunk: {event}\n"
|
||||
"Mivel csak ingyenes termékeket rendeltél, nem kell fizetned.\n"
|
||||
"\n"
|
||||
"Módosíthatod a rendelésed részleteit és megtekintheted az állapotát a "
|
||||
@@ -12848,8 +12858,11 @@ msgid "Device status"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/filter.py:2621
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Active devices"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/forms/filter.py:2622
|
||||
msgid "Revoked devices"
|
||||
@@ -13879,8 +13892,11 @@ msgid "Organizer short name"
|
||||
msgstr "Szervező rövidített név"
|
||||
|
||||
#: pretix/control/forms/organizer.py:1092
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Allow access to reusable media"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/forms/organizer.py:1093
|
||||
msgid ""
|
||||
@@ -20955,8 +20971,11 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:291
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Select action"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:312
|
||||
#: pretix/control/views/orders.py:335
|
||||
@@ -21383,8 +21402,11 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:188
|
||||
#: pretix/control/templates/pretixcontrol/subevents/index.html:211
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Edit selected"
|
||||
msgstr ""
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html:12
|
||||
msgid "Organizer settings"
|
||||
@@ -22725,12 +22747,18 @@ msgid "Delete selected"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/index.html:214
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Activate selected"
|
||||
msgstr "Aktiválás"
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/subevents/index.html:217
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Deactivate selected"
|
||||
msgstr "Deaktiválás"
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:4
|
||||
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:6
|
||||
@@ -25679,8 +25707,11 @@ msgid "Badge layout: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/badges/templates/pretixplugins/badges/edit.html:26
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Save & continue"
|
||||
msgstr "Mentés és folytatás"
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:10
|
||||
msgid "You haven't created any badge layouts yet."
|
||||
@@ -28598,7 +28629,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:4
|
||||
msgid "The total amount will be withdrawn from your credit card."
|
||||
msgstr "Az összeg a kártyádról kerül levonásra."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:8
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_card.html:29
|
||||
@@ -28943,7 +28974,7 @@ msgstr "Összes jegy letöltése (PDF)"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:66
|
||||
msgid "Download ticket (PDF)"
|
||||
msgstr "Jegy PDF letöltése"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/views.py:62
|
||||
msgid "Default ticket layout"
|
||||
@@ -29876,9 +29907,12 @@ msgstr "%(item)s, %(var)s hozzáadása a kosárhoz"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:370
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:231
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:385
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgctxt "checkbox"
|
||||
msgid "Select"
|
||||
msgstr "Kiválaszt"
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:205
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:350
|
||||
@@ -31074,11 +31108,11 @@ msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/position.html:10
|
||||
msgid "Your registration"
|
||||
msgstr "A rendelésed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/position.html:31
|
||||
msgid "Your items"
|
||||
msgstr "A tételeid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/position.html:46
|
||||
msgid "Additional information"
|
||||
@@ -31394,8 +31428,11 @@ msgid "Social features"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/fragment_modals.html:114
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "No date selected."
|
||||
msgid "Save selection"
|
||||
msgstr "Kijelölés mentése"
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:82
|
||||
#, python-format
|
||||
@@ -31722,7 +31759,7 @@ msgstr "Ismeretlen eseménykód vagy nincs jogod az eseményhez."
|
||||
#: pretix/presale/views/event.py:902
|
||||
msgctxt "subevent"
|
||||
msgid "No date selected."
|
||||
msgstr "Nincs időpont kiválasztva."
|
||||
msgstr "Nincs dátum kiválasztva."
|
||||
|
||||
#: pretix/presale/views/event.py:905
|
||||
msgctxt "subevent"
|
||||
|
||||
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
|
||||
"PO-Revision-Date: 2024-10-01 22:52+0000\n"
|
||||
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/hu/>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/hu/>\n"
|
||||
"Language: hu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -779,7 +779,7 @@ msgstr "A kosár lejárt"
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
msgid "Time zone:"
|
||||
msgstr "Időzona:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
msgid "Your local time:"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-11-24 01:00+0000\n"
|
||||
"Last-Translator: gabriblas <github@unowen.simplelogin.com>\n"
|
||||
"PO-Revision-Date: 2024-11-18 02:00+0000\n"
|
||||
"Last-Translator: Damiano <estux@users.noreply.translate.pretix.eu>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -4424,9 +4424,6 @@ msgid ""
|
||||
"If checked, an event can only be taken live if the property is set. In event "
|
||||
"series, its always optional to set a value for individual dates"
|
||||
msgstr ""
|
||||
"Se selezionato, un evento può essere pubblicato solamente se la proprietà è "
|
||||
"stata impostata. Per le serie di eventi, impostare un valore per le date di "
|
||||
"singoli eventi è sempre opzionale"
|
||||
|
||||
#: pretix/base/models/event.py:1721 pretix/base/models/items.py:2211
|
||||
msgid "Valid values"
|
||||
@@ -4590,10 +4587,6 @@ msgid ""
|
||||
"their own. They can only be bought in combination with a product that has "
|
||||
"this category configured as a possible source for add-ons."
|
||||
msgstr ""
|
||||
"Se selezionato, i prodotti che appartengono a questa categoria non potranno "
|
||||
"essere venduti separatamente. Potranno invece essere acquistati in "
|
||||
"combinazione con un altro prodotto che indichi questa categoria come un add-"
|
||||
"on valido."
|
||||
|
||||
#: pretix/base/models/items.py:114 pretix/base/models/items.py:159
|
||||
#: pretix/control/forms/item.py:99
|
||||
@@ -4601,25 +4594,21 @@ msgid "Normal category"
|
||||
msgstr "Categoria normale"
|
||||
|
||||
#: pretix/base/models/items.py:115 pretix/control/forms/item.py:112
|
||||
#, fuzzy
|
||||
msgid "Normal + cross-selling category"
|
||||
msgstr "Categoria normale + cross-selling"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:116 pretix/control/forms/item.py:107
|
||||
#, fuzzy
|
||||
msgid "Cross-selling category"
|
||||
msgstr "Categoria cross-selling"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:124
|
||||
msgid "Always show in cross-selling step"
|
||||
msgstr "Visualizza sempre nello step di cross-selling (vendita aggiuntiva)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:125
|
||||
msgid ""
|
||||
"Only show products that qualify for a discount according to discount rules"
|
||||
msgstr ""
|
||||
"Visualizza solamente prodotti su cui è applicabile uno sconto, in base alle "
|
||||
"regole di sconto"
|
||||
|
||||
#: pretix/base/models/items.py:126
|
||||
msgid "Only show if the cart contains one of the following products"
|
||||
@@ -4627,7 +4616,7 @@ msgstr "Mostra solo se il carrello contiene uno dei seguenti prodotti"
|
||||
|
||||
#: pretix/base/models/items.py:129
|
||||
msgid "Cross-selling condition"
|
||||
msgstr "Condizione per il cross-selling (vendita aggiuntiva)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:137
|
||||
#, fuzzy
|
||||
@@ -7167,7 +7156,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/pdf.py:500
|
||||
msgid "Seat: row"
|
||||
msgstr "Posto: fila"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/pdf.py:505
|
||||
msgid "Seat: seat number"
|
||||
@@ -7579,7 +7568,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/services/cart.py:210
|
||||
msgid "Please select a valid seat."
|
||||
msgstr "Si prega di selezionare un posto a sedere valido."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/services/cart.py:211
|
||||
msgid "You can not select a seat for this position."
|
||||
@@ -8026,7 +8015,7 @@ msgstr ""
|
||||
#: pretix/base/services/modelimport.py:236
|
||||
#, python-brace-format
|
||||
msgid "Invalid data in row {row}: {message}"
|
||||
msgstr "Dati invalidi alla linea {row}: {message}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/services/modelimport.py:217
|
||||
msgid "A voucher cannot be created without a code."
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
|
||||
"PO-Revision-Date: 2024-11-21 12:58+0000\n"
|
||||
"Last-Translator: Ryo <saremba@rami.io>\n"
|
||||
"PO-Revision-Date: 2024-10-22 17:00+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
"Language: ja\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -44,6 +44,7 @@ msgid "Catalan"
|
||||
msgstr "カタルーニャ語"
|
||||
|
||||
#: pretix/_base_settings.py:85
|
||||
#, fuzzy
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr "中国語(簡体字)"
|
||||
|
||||
@@ -56,6 +57,7 @@ msgid "Czech"
|
||||
msgstr "チェコ"
|
||||
|
||||
#: pretix/_base_settings.py:88
|
||||
#, fuzzy
|
||||
msgid "Danish"
|
||||
msgstr "デンマーク語"
|
||||
|
||||
@@ -121,11 +123,11 @@ msgstr "ロシア語"
|
||||
|
||||
#: pretix/_base_settings.py:104
|
||||
msgid "Slovak"
|
||||
msgstr "スロバキア語"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py:105
|
||||
msgid "Swedish"
|
||||
msgstr "スウェーデン語"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py:106
|
||||
msgid "Spanish"
|
||||
@@ -282,7 +284,7 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/item.py:306
|
||||
msgid "Only admission products can currently be personalized."
|
||||
msgstr "現在、パーソナライズできるのは入場商品(チケット)のみです。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/serializers/item.py:317
|
||||
msgid ""
|
||||
@@ -376,11 +378,11 @@ msgstr "このユーザーは既にチームへの参加を承認されていま
|
||||
#: pretix/api/views/cart.py:209
|
||||
msgid ""
|
||||
"The specified voucher has already been used the maximum number of times."
|
||||
msgstr "指定されたバウチャーは、すでに最大使用回数に達しています。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/views/checkin.py:610 pretix/api/views/checkin.py:617
|
||||
msgid "Medium connected to other event"
|
||||
msgstr "他のイベントに関連付けられているメディアです"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/views/oauth.py:107 pretix/control/logdisplay.py:476
|
||||
#, python-brace-format
|
||||
@@ -467,7 +469,7 @@ msgstr "外部からの払い戻し"
|
||||
|
||||
#: pretix/api/webhooks.py:285
|
||||
msgid "Refund of payment requested by customer"
|
||||
msgstr "お客様から支払いの返金が要求されました"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/webhooks.py:289
|
||||
#, fuzzy
|
||||
@@ -541,8 +543,7 @@ msgstr "イベント情報のデータが削除されました"
|
||||
msgid ""
|
||||
"Product changed (including product added or deleted and including changes to "
|
||||
"nested objects like variations or bundles)"
|
||||
msgstr "製品が変更されました(製品の追加または削除、バリエーションやバンドルのような"
|
||||
"ネストされたオブジェクトの変更を含む)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/webhooks.py:354
|
||||
#, fuzzy
|
||||
@@ -566,7 +567,7 @@ msgstr "その金額がカードに請求されました。"
|
||||
|
||||
#: pretix/api/webhooks.py:370
|
||||
msgid "Waiting list entry added"
|
||||
msgstr "ウェイティングリストにエントリーが追加されました"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/webhooks.py:374
|
||||
#, fuzzy
|
||||
@@ -580,7 +581,7 @@ msgstr "その金額がカードに請求されました。"
|
||||
|
||||
#: pretix/api/webhooks.py:382
|
||||
msgid "Waiting list entry received voucher"
|
||||
msgstr "ウェイティングリストのエントリーがバウチャーを受け取りました"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/webhooks.py:386
|
||||
#, fuzzy
|
||||
@@ -607,15 +608,15 @@ msgstr "国"
|
||||
#: pretix/plugins/banktransfer/payment.py:679
|
||||
#: pretix/presale/forms/customer.py:140
|
||||
msgid "This field is required."
|
||||
msgstr "この項目は必須です。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/addressvalidation.py:213
|
||||
msgid "Enter a postal code in the format XXX."
|
||||
msgstr "郵便番号をXXXの形式で入力してください。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/addressvalidation.py:222 pretix/base/addressvalidation.py:224
|
||||
msgid "Enter a postal code in the format XXXX."
|
||||
msgstr "郵便番号をXXXXの形式で入力してください。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/auth.py:146
|
||||
#, python-brace-format
|
||||
@@ -657,7 +658,7 @@ msgstr "パスワード"
|
||||
|
||||
#: pretix/base/auth.py:176 pretix/base/auth.py:183
|
||||
msgid "Your password must contain both numeric and alphabetic characters."
|
||||
msgstr "パスワードには数字とアルファベットの両方を含める必要があります。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/auth.py:202 pretix/base/auth.py:212
|
||||
#, python-format
|
||||
@@ -665,8 +666,7 @@ msgid "Your password may not be the same as your previous password."
|
||||
msgid_plural ""
|
||||
"Your password may not be the same as one of your %(history_length)s previous "
|
||||
"passwords."
|
||||
msgstr[0] "パスワードは、過去%(history_length)s件のいずれかのパスワードと同じにすること"
|
||||
"はできません。"
|
||||
msgstr[0] ""
|
||||
|
||||
#: pretix/base/channels.py:168
|
||||
msgid "Online shop"
|
||||
@@ -674,14 +674,13 @@ msgstr "オンラインショップ"
|
||||
|
||||
#: pretix/base/channels.py:174
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/channels.py:175
|
||||
msgid ""
|
||||
"API sales channels come with no built-in functionality, but may be used for "
|
||||
"custom integrations."
|
||||
msgstr "API販売チャネルには組み込みの機能はありませんが、カスタムインテグレーションで"
|
||||
"使用することができます。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/context.py:45
|
||||
#, python-brace-format
|
||||
@@ -700,25 +699,25 @@ msgstr "ソースコード"
|
||||
#: pretix/base/customersso/oidc.py:61
|
||||
#, python-brace-format
|
||||
msgid "Configuration option \"{name}\" is missing."
|
||||
msgstr "設定オプション\"{name}\"がありません。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/customersso/oidc.py:69 pretix/base/customersso/oidc.py:74
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unable to retrieve configuration from \"{url}\". Error message: \"{error}\"."
|
||||
msgstr "\"{url}\"から設定を取得できませんでした。エラーメッセージ:\"{error}\"。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/customersso/oidc.py:80 pretix/base/customersso/oidc.py:85
|
||||
#: pretix/base/customersso/oidc.py:90 pretix/base/customersso/oidc.py:95
|
||||
#: pretix/base/customersso/oidc.py:100 pretix/base/customersso/oidc.py:105
|
||||
#, python-brace-format
|
||||
msgid "Incompatible SSO provider: \"{error}\"."
|
||||
msgstr "互換性のないSSOプロバイダー: \"{error}\"。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/customersso/oidc.py:111
|
||||
#, python-brace-format
|
||||
msgid "You are not requesting \"{scope}\"."
|
||||
msgstr "\"{scope}\" をリクエストしていません。"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/customersso/oidc.py:117
|
||||
#, python-brace-format
|
||||
|
||||
@@ -80,32 +80,28 @@ class MultiDomainMiddleware(MiddlewareMixin):
|
||||
request.port = int(port) if port else None
|
||||
request.host = domain
|
||||
if domain == default_domain:
|
||||
request.domain_mode = "system"
|
||||
request.urlconf = "pretix.multidomain.maindomain_urlconf"
|
||||
elif domain:
|
||||
cached = cache.get('pretix_multidomain_instances_{}'.format(domain))
|
||||
cached = cache.get('pretix_multidomain_instance_{}'.format(domain))
|
||||
|
||||
if cached is None:
|
||||
try:
|
||||
kd = KnownDomain.objects.select_related('organizer', 'event').get(domainname=domain) # noqa
|
||||
orga = kd.organizer
|
||||
event = kd.event
|
||||
mode = kd.mode
|
||||
except KnownDomain.DoesNotExist:
|
||||
orga = False
|
||||
event = False
|
||||
mode = "system"
|
||||
cache.set(
|
||||
'pretix_multidomain_instances_{}'.format(domain),
|
||||
(orga.pk if orga else None, event.pk if event else None, mode),
|
||||
'pretix_multidomain_instance_{}'.format(domain),
|
||||
(orga.pk if orga else None, event.pk if event else None),
|
||||
3600
|
||||
)
|
||||
else:
|
||||
orga, event, mode = cached
|
||||
orga, event = cached
|
||||
|
||||
if mode == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
if event:
|
||||
request.event_domain = True
|
||||
request.domain_mode = KnownDomain.MODE_EVENT_DOMAIN
|
||||
if isinstance(event, Event):
|
||||
request.organizer = orga
|
||||
request.event = event
|
||||
@@ -114,18 +110,11 @@ class MultiDomainMiddleware(MiddlewareMixin):
|
||||
request.event = Event.objects.select_related('organizer').get(pk=event)
|
||||
request.organizer = request.event.organizer
|
||||
request.urlconf = "pretix.multidomain.event_domain_urlconf"
|
||||
elif mode == KnownDomain.MODE_ORG_ALT_DOMAIN:
|
||||
elif orga:
|
||||
request.organizer_domain = True
|
||||
request.domain_mode = KnownDomain.MODE_ORG_ALT_DOMAIN
|
||||
request.organizer = orga if isinstance(orga, Organizer) else Organizer.objects.get(pk=orga)
|
||||
request.urlconf = "pretix.multidomain.organizer_alternative_domain_urlconf"
|
||||
elif mode == KnownDomain.MODE_ORG_DOMAIN:
|
||||
request.organizer_domain = True
|
||||
request.domain_mode = KnownDomain.MODE_ORG_DOMAIN
|
||||
request.organizer = orga if isinstance(orga, Organizer) else Organizer.objects.get(pk=orga)
|
||||
request.urlconf = "pretix.multidomain.organizer_domain_urlconf"
|
||||
elif settings.DEBUG or domain in LOCAL_HOST_NAMES:
|
||||
request.domain_mode = "system"
|
||||
request.urlconf = "pretix.multidomain.maindomain_urlconf"
|
||||
else:
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# Generated by Django 4.2.16 on 2024-11-12 10:46
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0273_remove_checkinlist_auto_checkin_sales_channels"),
|
||||
("pretixmultidomain", "0002_knowndomain_event"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AlternativeDomainAssignment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="knowndomain",
|
||||
name="mode",
|
||||
field=models.CharField(default="organizer", max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="knowndomain",
|
||||
name="event",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="domain",
|
||||
to="pretixbase.event",
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="UPDATE pretixmultidomain_knowndomain SET mode = 'event' WHERE event_id IS NOT NULL",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="knowndomain",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("mode", "organizer")),
|
||||
fields=("organizer",),
|
||||
name="unique_organizer_domain",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="alternativedomainassignment",
|
||||
name="domain",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="event_assignments",
|
||||
to="pretixmultidomain.knowndomain",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="alternativedomainassignment",
|
||||
name="event",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="alternative_domain_assignment",
|
||||
to="pretixbase.event",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -21,7 +21,6 @@
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
@@ -29,134 +28,39 @@ from pretix.base.models import Event, Organizer
|
||||
|
||||
|
||||
class KnownDomain(models.Model):
|
||||
MODE_ORG_DOMAIN = "organizer"
|
||||
MODE_ORG_ALT_DOMAIN = "organizer_alternative"
|
||||
MODE_EVENT_DOMAIN = "event"
|
||||
MODES = (
|
||||
(MODE_ORG_DOMAIN, _("Organizer domain")),
|
||||
(MODE_ORG_ALT_DOMAIN, _("Alternative organizer domain for a set of events")),
|
||||
(MODE_EVENT_DOMAIN, _("Event domain")),
|
||||
)
|
||||
|
||||
domainname = models.CharField(
|
||||
max_length=255,
|
||||
primary_key=True,
|
||||
verbose_name=_("Domain name"),
|
||||
)
|
||||
mode = models.CharField(
|
||||
max_length=255,
|
||||
choices=MODES,
|
||||
default=MODE_ORG_DOMAIN,
|
||||
verbose_name=_("Mode"),
|
||||
)
|
||||
organizer = models.ForeignKey(
|
||||
Organizer,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name='domains',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
event = models.OneToOneField(
|
||||
Event,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name='domain',
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("Event"),
|
||||
)
|
||||
domainname = models.CharField(max_length=255, primary_key=True)
|
||||
organizer = models.ForeignKey(Organizer, blank=True, null=True, related_name='domains', on_delete=models.CASCADE)
|
||||
event = models.ForeignKey(Event, blank=True, null=True, related_name='domains', on_delete=models.PROTECT)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Known domain")
|
||||
verbose_name_plural = _("Known domains")
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=("organizer",),
|
||||
name="unique_organizer_domain",
|
||||
condition=Q(mode="organizer"),
|
||||
),
|
||||
]
|
||||
ordering = ("-mode", "domainname")
|
||||
|
||||
def __str__(self):
|
||||
return self.domainname
|
||||
|
||||
@scopes_disabled()
|
||||
def save(self, *args, **kwargs):
|
||||
if self.event:
|
||||
self.mode = KnownDomain.MODE_EVENT_DOMAIN
|
||||
elif self.mode == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
raise ValueError("Event domain needs event")
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.get_cache().clear()
|
||||
try:
|
||||
self.event.alternative_domain_assignment.delete()
|
||||
except AlternativeDomainAssignment.DoesNotExist:
|
||||
pass
|
||||
elif self.organizer:
|
||||
self.organizer.get_cache().clear()
|
||||
for event in self.organizer.events.all():
|
||||
event.get_cache().clear()
|
||||
cache.delete('pretix_multidomain_organizer_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_instances_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_instance_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_event_{}'.format(self.domainname))
|
||||
|
||||
@scopes_disabled()
|
||||
def delete(self, *args, **kwargs):
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
self.event.get_cache().clear()
|
||||
elif self.organizer:
|
||||
self.organizer.cache.clear()
|
||||
self.organizer.get_cache().clear()
|
||||
for event in self.organizer.events.all():
|
||||
event.cache.clear()
|
||||
event.get_cache().clear()
|
||||
cache.delete('pretix_multidomain_organizer_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_instances_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_instance_{}'.format(self.domainname))
|
||||
cache.delete('pretix_multidomain_event_{}'.format(self.domainname))
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
def _log_domain_action(self, user, data):
|
||||
if self.event:
|
||||
self.event.log_action(
|
||||
'pretix.event.settings',
|
||||
user=user,
|
||||
data=data
|
||||
)
|
||||
else:
|
||||
self.organizer.log_action(
|
||||
'pretix.organizer.settings',
|
||||
user=user,
|
||||
data=data
|
||||
)
|
||||
|
||||
def log_create(self, user):
|
||||
self._log_domain_action(user, {'add_alt_domain': self.domainname} if self.mode == KnownDomain.MODE_ORG_ALT_DOMAIN else {'domain': self.domainname})
|
||||
|
||||
def log_delete(self, user):
|
||||
self._log_domain_action(user, {'remove_alt_domain': self.domainname} if self.mode == KnownDomain.MODE_ORG_ALT_DOMAIN else {'domain': None})
|
||||
|
||||
|
||||
class AlternativeDomainAssignment(models.Model):
|
||||
domain = models.ForeignKey(
|
||||
KnownDomain,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="event_assignments",
|
||||
)
|
||||
event = models.OneToOneField(
|
||||
Event,
|
||||
related_name="alternative_domain_assignment",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
@scopes_disabled()
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.event.cache.clear()
|
||||
cache.delete('pretix_multidomain_instances_{}'.format(self.domain_id))
|
||||
cache.delete('pretix_multidomain_event_{}'.format(self.domain_id))
|
||||
|
||||
@scopes_disabled()
|
||||
def delete(self, *args, **kwargs):
|
||||
self.event.cache.clear()
|
||||
cache.delete('pretix_multidomain_instances_{}'.format(self.domain_id))
|
||||
cache.delete('pretix_multidomain_event_{}'.format(self.domain_id))
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
from pretix.presale.urls import event_patterns, locale_patterns
|
||||
from pretix.presale.views import organizer
|
||||
from pretix.urls import common_patterns
|
||||
|
||||
presale_patterns = [
|
||||
re_path(r'', include((locale_patterns + [
|
||||
re_path(r'^$', organizer.RedirectToOrganizerIndex.as_view(), name='organizer.alt.index'),
|
||||
re_path(r'^(?P<event>[^/]+)/', include(event_patterns)),
|
||||
], 'presale')))
|
||||
]
|
||||
|
||||
raw_plugin_patterns = []
|
||||
for app in apps.get_app_configs():
|
||||
if hasattr(app, 'PretixPluginMeta'):
|
||||
if importlib.util.find_spec(app.name + '.urls'):
|
||||
urlmod = importlib.import_module(app.name + '.urls')
|
||||
if hasattr(urlmod, 'event_patterns'):
|
||||
patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name)
|
||||
raw_plugin_patterns.append(
|
||||
re_path(r'^(?P<event>[^/]+)/', include((patterns, app.label)))
|
||||
)
|
||||
if hasattr(urlmod, 'organizer_patterns'):
|
||||
patterns = plugin_event_urls(urlmod.organizer_patterns, plugin=app.name)
|
||||
raw_plugin_patterns.append(
|
||||
re_path(r'', include((patterns, app.label)))
|
||||
)
|
||||
|
||||
plugin_patterns = [
|
||||
re_path(r'', include((raw_plugin_patterns, 'plugins')))
|
||||
]
|
||||
|
||||
# The presale namespace comes last, because it contains a wildcard catch
|
||||
urlpatterns = common_patterns + plugin_patterns + presale_patterns
|
||||
|
||||
handler404 = 'pretix.base.views.errors.page_not_found'
|
||||
handler500 = 'pretix.base.views.errors.server_error'
|
||||
@@ -43,33 +43,28 @@ from pretix.base.models import Event, Organizer
|
||||
from .models import KnownDomain
|
||||
|
||||
|
||||
def get_event_domain(event, fallback=False, return_mode=False):
|
||||
def get_event_domain(event, fallback=False, return_info=False):
|
||||
assert isinstance(event, Event)
|
||||
if not event.pk:
|
||||
# Can happen on the "event deleted" response
|
||||
return (None, None) if return_mode else None
|
||||
suffix = ('_fallback' if fallback else '') + ('_mode' if return_mode else '')
|
||||
return (None, None) if return_info else None
|
||||
suffix = ('_fallback' if fallback else '') + ('_info' if return_info else '')
|
||||
domain = getattr(event, '_cached_domain' + suffix, None) or event.cache.get('domain' + suffix)
|
||||
if domain is None:
|
||||
domain = None, None
|
||||
if hasattr(event, 'alternative_domain_assignment'):
|
||||
domain = event.alternative_domain_assignment.domain_id, KnownDomain.MODE_ORG_ALT_DOMAIN
|
||||
elif fallback:
|
||||
if fallback:
|
||||
domains = KnownDomain.objects.filter(
|
||||
Q(event=event, mode=KnownDomain.MODE_EVENT_DOMAIN) |
|
||||
Q(organizer_id=event.organizer_id, event__isnull=True, mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
Q(event=event) | Q(organizer_id=event.organizer_id, event__isnull=True)
|
||||
)
|
||||
domains_event = [d for d in domains if d.event_id == event.pk]
|
||||
domains_org = [d for d in domains if not d.event_id]
|
||||
if domains_event:
|
||||
domain = domains_event[0].domainname, KnownDomain.MODE_EVENT_DOMAIN
|
||||
domain = domains_event[0].domainname, "event"
|
||||
elif domains_org:
|
||||
domain = domains_org[0].domainname, KnownDomain.MODE_ORG_DOMAIN
|
||||
domain = domains_org[0].domainname, "organizer"
|
||||
else:
|
||||
try:
|
||||
domain = event.domain.domainname, KnownDomain.MODE_EVENT_DOMAIN
|
||||
except KnownDomain.DoesNotExist:
|
||||
domain = None, None
|
||||
domains = event.domains.all()
|
||||
domain = domains[0].domainname if domains else None, "event"
|
||||
event.cache.set('domain' + suffix, domain or 'none')
|
||||
setattr(event, '_cached_domain' + suffix, domain or 'none')
|
||||
elif domain == 'none':
|
||||
@@ -77,7 +72,7 @@ def get_event_domain(event, fallback=False, return_mode=False):
|
||||
domain = None, None
|
||||
else:
|
||||
setattr(event, '_cached_domain' + suffix, domain)
|
||||
return domain if return_mode else domain[0]
|
||||
return domain if return_info or not isinstance(domain, tuple) else domain[0]
|
||||
|
||||
|
||||
def get_organizer_domain(organizer):
|
||||
@@ -86,7 +81,7 @@ def get_organizer_domain(organizer):
|
||||
return None
|
||||
domain = getattr(organizer, '_cached_domain', None) or organizer.cache.get('domain')
|
||||
if domain is None:
|
||||
domains = organizer.domains.filter(event__isnull=True, mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
domains = organizer.domains.filter(event__isnull=True)
|
||||
domain = domains[0].domainname if domains else None
|
||||
organizer.cache.set('domain', domain or 'none')
|
||||
organizer._cached_domain = domain or 'none'
|
||||
@@ -136,8 +131,7 @@ def eventreverse(obj, name, kwargs=None):
|
||||
:returns: An absolute or relative URL as a string
|
||||
"""
|
||||
from pretix.multidomain import (
|
||||
event_domain_urlconf, maindomain_urlconf,
|
||||
organizer_alternative_domain_urlconf, organizer_domain_urlconf,
|
||||
event_domain_urlconf, maindomain_urlconf, organizer_domain_urlconf,
|
||||
)
|
||||
|
||||
c = None
|
||||
@@ -159,24 +153,17 @@ def eventreverse(obj, name, kwargs=None):
|
||||
raise TypeError('obj should be Event or Organizer')
|
||||
|
||||
if event:
|
||||
domain, domaintype = get_event_domain(obj, fallback=True, return_mode=True)
|
||||
domain, domaintype = get_event_domain(obj, fallback=True, return_info=True)
|
||||
else:
|
||||
domain, domaintype = get_organizer_domain(organizer), KnownDomain.MODE_ORG_DOMAIN
|
||||
domain, domaintype = get_organizer_domain(organizer), "organizer"
|
||||
|
||||
if domain:
|
||||
if domaintype == KnownDomain.MODE_EVENT_DOMAIN and 'event' in kwargs:
|
||||
if domaintype == "event" and 'event' in kwargs:
|
||||
del kwargs['event']
|
||||
if 'organizer' in kwargs:
|
||||
del kwargs['organizer']
|
||||
|
||||
if domaintype == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
urlconf = event_domain_urlconf
|
||||
elif domaintype == KnownDomain.MODE_ORG_ALT_DOMAIN:
|
||||
urlconf = organizer_alternative_domain_urlconf
|
||||
else:
|
||||
urlconf = organizer_domain_urlconf
|
||||
|
||||
path = reverse(name, kwargs=kwargs, urlconf=urlconf)
|
||||
path = reverse(name, kwargs=kwargs, urlconf=event_domain_urlconf if domaintype == "event" else organizer_domain_urlconf)
|
||||
siteurlsplit = urlsplit(settings.SITE_URL)
|
||||
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
||||
domain = '%s:%d' % (domain, siteurlsplit.port)
|
||||
|
||||
@@ -79,8 +79,8 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
|
||||
widget=I18nMarkdownTextarea, required=True,
|
||||
locales=event.settings.get('locales'),
|
||||
)
|
||||
self._set_field_placeholders('subject', context_parameters, rich=False)
|
||||
self._set_field_placeholders('message', context_parameters, rich=True)
|
||||
self._set_field_placeholders('subject', context_parameters)
|
||||
self._set_field_placeholders('message', context_parameters)
|
||||
|
||||
|
||||
class WaitinglistMailForm(BaseMailForm):
|
||||
@@ -382,7 +382,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
|
||||
)
|
||||
|
||||
self._set_field_placeholders('subject', ['event', 'order', 'event_or_subevent'])
|
||||
self._set_field_placeholders('template', ['event', 'order', 'event_or_subevent'], rich=True)
|
||||
self._set_field_placeholders('template', ['event', 'order', 'event_or_subevent'])
|
||||
|
||||
choices = [(e, l) for e, l in Order.STATUS_CHOICE if e != 'n']
|
||||
choices.insert(0, ('n__valid_if_pending', _('payment pending but already confirmed')))
|
||||
|
||||
@@ -46,10 +46,12 @@ from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, ngettext
|
||||
from django.views.generic import DeleteView, FormView, ListView, TemplateView
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.i18n import LazyI18nString, language
|
||||
from pretix.base.models import Checkin, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models.event import SubEvent
|
||||
@@ -61,8 +63,7 @@ from pretix.plugins.sendmail.tasks import (
|
||||
)
|
||||
|
||||
from ...base.services.mail import prefix_subject
|
||||
from ...base.services.placeholders import get_sample_context
|
||||
from ...helpers.format import SafeFormatter, format_map
|
||||
from ...helpers.format import format_map
|
||||
from ...helpers.models import modelcopy
|
||||
from . import forms
|
||||
from .models import Rule, ScheduledMail
|
||||
@@ -190,15 +191,17 @@ class BaseSenderView(EventPermissionRequiredMixin, FormView):
|
||||
if self.request.POST.get("action") != "send":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = get_sample_context(self.request.event, self.context_parameters)
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, self.context_parameters).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(v.render_sample(self.request.event))
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=set())
|
||||
preview_subject = prefix_subject(self.request.event, format_map(subject, context_dict), highlight=True)
|
||||
message = form.cleaned_data['message'].localize(l)
|
||||
preview_text = format_map(
|
||||
markdown_compile_email(format_map(message, context_dict)),
|
||||
context_dict,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
)
|
||||
preview_text = markdown_compile_email(format_map(message, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
@@ -600,6 +603,31 @@ class CreateRule(EventPermissionRequiredMixin, CreateView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.output = {}
|
||||
|
||||
if self.request.POST.get("action") == "preview":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order',
|
||||
'position_or_address']).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(v.render_sample(self.request.event))
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=set())
|
||||
preview_subject = prefix_subject(self.request.event, format_map(subject, context_dict), highlight=True)
|
||||
template = form.cleaned_data['template'].localize(l)
|
||||
preview_text = markdown_compile_email(format_map(template, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
'html': preview_text,
|
||||
}
|
||||
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
|
||||
messages.success(self.request, _('Your rule has been created.'))
|
||||
|
||||
form.instance.event = self.request.event
|
||||
@@ -657,15 +685,17 @@ class UpdateRule(EventPermissionRequiredMixin, UpdateView):
|
||||
|
||||
for lang in self.request.event.settings.locales:
|
||||
with language(lang, self.request.event.settings.region):
|
||||
placeholders = get_sample_context(self.request.event, ['event', 'order', 'position_or_address'])
|
||||
placeholders = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order', 'position_or_address']).items():
|
||||
placeholders[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(v.render_sample(self.request.event))
|
||||
)
|
||||
|
||||
subject = bleach.clean(self.object.subject.localize(lang), tags=set())
|
||||
preview_subject = prefix_subject(self.request.event, format_map(subject, placeholders), highlight=True)
|
||||
template = self.object.template.localize(lang)
|
||||
preview_text = format_map(
|
||||
markdown_compile_email(format_map(template, placeholders)),
|
||||
placeholders,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
)
|
||||
preview_text = markdown_compile_email(format_map(template, placeholders))
|
||||
|
||||
o[lang] = {
|
||||
'subject': _('Subject: {subject}'.format(subject=preview_subject)),
|
||||
|
||||
@@ -160,7 +160,7 @@ class BaseCheckoutFlowStep:
|
||||
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
|
||||
return eventreverse(self.request.event, 'presale:event.index', kwargs=kwargs)
|
||||
else:
|
||||
return prev.get_step_url(request) + '?dir=prev'
|
||||
return prev.get_step_url(request)
|
||||
|
||||
def get_next_url(self, request):
|
||||
n = self.get_next_applicable(request)
|
||||
@@ -662,7 +662,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
if 'async_id' in request.GET and settings.HAS_CELERY:
|
||||
return self.get_result(request)
|
||||
if len(self.forms) == 0 and len(self.cross_selling_data) == 0 and self.is_completed(request):
|
||||
return redirect(self.get_prev_url(request) if request.GET.get('dir') == 'prev' else self.get_next_url(request))
|
||||
return redirect(self.get_next_url(request))
|
||||
return TemplateFlowStep.get(self, request)
|
||||
|
||||
def _clean_category(self, form, category):
|
||||
@@ -1076,8 +1076,8 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
if warn:
|
||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||
return False
|
||||
if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_addresses_required', as_type=bool) \
|
||||
and (cp.street is None and cp.city is None and cp.country is None):
|
||||
if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_attendees_required', as_type=bool) \
|
||||
and (cp.street is None or cp.city is None or cp.country is None):
|
||||
if warn:
|
||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||
return False
|
||||
|
||||
@@ -133,7 +133,6 @@ class InvoiceAddressForm(BaseInvoiceAddressForm):
|
||||
|
||||
|
||||
class InvoiceNameForm(InvoiceAddressForm):
|
||||
address_validation = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</p>
|
||||
{% elif incomplete %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "A product in your cart is only sold in combination with add-on products that are not available. Please contact the event organizer." %}
|
||||
{% trans "A product in your cart is only sold in combination with add-on products that are no longer available. Please contact the event organizer." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" method="post" data-asynctask
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
<section aria-labelledby="{{ form_prefix }}category-{{ category.id }}"{% if category.description %} aria-describedby="{{ form_prefix }}category-info-{{ category.id }}"{% endif %}>
|
||||
<h{{ headline_level|default:3 }} class="h3" id="{{ form_prefix }}category-{{ category.id }}">{{ category.name }}
|
||||
{% if category.subevent_name %}
|
||||
<small class="text-muted"><i class="fa fa-calendar" aria-hidden="true"></i> {{ category.subevent_name }}</small>
|
||||
<small class="text-muted"><i class="fa fa-calendar"></i> {{ category.subevent_name }}</small>
|
||||
{% endif %}
|
||||
{% if category.category_has_discount %}
|
||||
<small class="text-success">
|
||||
<i class="fa fa-star" aria-hidden="true"></i>
|
||||
<span class="sr-only">{% trans "Congratulations!" %}</span>
|
||||
<span class="sr-only">Congratulations!</span>
|
||||
{% trans "Your order qualifies for a discount" %}
|
||||
</small>
|
||||
{% endif %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load date_fast %}
|
||||
{% load calendarhead %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-calendar" role="grid">
|
||||
<table class="table table-calendar">
|
||||
<caption class="sr-only">{% trans "Calendar" %}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -18,26 +18,7 @@
|
||||
{% if day %}
|
||||
<td class="day {% if day.events %}has-events{% else %}no-events{% endif %}"
|
||||
data-date="{{ day.date|date_fast:"SHORT_DATE_FORMAT" }}">
|
||||
<p>
|
||||
{% if day.events %}
|
||||
<a href="#selected-day" class="day-label event hidden-sm hidden-md hidden-lg">
|
||||
<b aria-hidden="true">{{ day.day }}</b>
|
||||
<time datetime="{{ day.date|date_fast:"Y-m-d" }}" class="sr-only">
|
||||
{{ day.date|date_fast:"SHORT_DATE_FORMAT" }}
|
||||
</time>
|
||||
<span class="sr-only">
|
||||
({% blocktrans trimmed count count=day.events|length %}
|
||||
{{ count }} event
|
||||
{% plural %}
|
||||
{{ count }} events
|
||||
{% endblocktrans %})
|
||||
</span>
|
||||
</a>
|
||||
<time datetime="{{ day.date|date_fast:"Y-m-d" }}" class="hidden-xs">{{ day.day }}</time>
|
||||
{% else %}
|
||||
<time datetime="{{ day.date|date_fast:"Y-m-d" }}" class="day-label">{{ day.day }}</time>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p><time datetime="{{ day.date|date_fast:"Y-m-d" }}">{{ day.day }}</time></p>
|
||||
<ul class="events">
|
||||
{% for event in day.events %}
|
||||
<li><a class="event {% if event.continued %}continued{% endif %} {% spaceless %}
|
||||
@@ -130,7 +111,9 @@
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="selected-day hidden">
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="selected-day" aria-live="polite" class="table-calendar hidden-sm hidden-md hidden-lg"></div>
|
||||
</div>
|
||||
|
||||
@@ -20,5 +20,4 @@
|
||||
<script type="text/javascript" src="{% static "pretixpresale/js/ui/cart.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixpresale/js/ui/iframe.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||
{% endcompress %}
|
||||
|
||||
@@ -57,9 +57,8 @@ from pretix.base.middleware import LocaleMiddleware
|
||||
from pretix.base.models import Customer, Event, Organizer
|
||||
from pretix.base.timemachine import time_machine_now_assigned_from_request
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_event_domain, get_organizer_domain,
|
||||
get_event_domain, get_organizer_domain,
|
||||
)
|
||||
from pretix.presale.signals import process_request, process_response
|
||||
|
||||
@@ -135,7 +134,7 @@ def update_customer_session_auth_hash(request, customer):
|
||||
|
||||
|
||||
def add_customer_to_request(request):
|
||||
if 'cross_domain_customer_auth' in request.GET and request.domain_mode in (KnownDomain.MODE_EVENT_DOMAIN, KnownDomain.MODE_ORG_ALT_DOMAIN):
|
||||
if 'cross_domain_customer_auth' in request.GET and request.event_domain:
|
||||
# The user is logged in on the main domain and now wants to take their session
|
||||
# to a event-specific domain. We validate the one time token received via a
|
||||
# query parameter and make sure we invalidate it right away. Then, we look up
|
||||
@@ -259,12 +258,11 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
|
||||
url = resolve(request.path_info)
|
||||
|
||||
request_domain_mode = getattr(request, 'domain_mode', 'system')
|
||||
try:
|
||||
if request_domain_mode == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
if hasattr(request, 'event_domain'):
|
||||
# We are on an event's custom domain
|
||||
pass
|
||||
elif request_domain_mode in (KnownDomain.MODE_ORG_DOMAIN, KnownDomain.MODE_ORG_ALT_DOMAIN):
|
||||
elif hasattr(request, 'organizer_domain'):
|
||||
# We are on an organizer's custom domain
|
||||
if 'organizer' in url.kwargs and url.kwargs['organizer']:
|
||||
if url.kwargs['organizer'] != request.organizer.slug:
|
||||
@@ -279,20 +277,12 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
organizer=request.organizer,
|
||||
)
|
||||
|
||||
# If this event has a custom domain or is not available on this alt domain, send the user there
|
||||
domain, domainmode = get_event_domain(request.event, fallback=False, return_mode=True)
|
||||
if not domain and request_domain_mode == KnownDomain.MODE_ORG_ALT_DOMAIN:
|
||||
path = request.get_full_path().split("/", 2)[-1]
|
||||
r = redirect_to_url(build_absolute_uri(request.event, "presale:event.index") + path)
|
||||
r['Access-Control-Allow-Origin'] = '*'
|
||||
return r
|
||||
elif domain and domain != request.host:
|
||||
# If this event has a custom domain, send the user there
|
||||
domain = get_event_domain(request.event)
|
||||
if domain:
|
||||
if request.port and request.port not in (80, 443):
|
||||
domain = '%s:%d' % (domain, request.port)
|
||||
if domainmode == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
path = request.get_full_path().split("/", 2)[-1]
|
||||
else:
|
||||
path = request.get_full_path()
|
||||
path = request.get_full_path().split("/", 2)[-1]
|
||||
r = redirect_to_url(urljoin('%s://%s' % (request.scheme, domain), path))
|
||||
r['Access-Control-Allow-Origin'] = '*'
|
||||
return r
|
||||
@@ -309,14 +299,11 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
request.organizer = request.event.organizer
|
||||
|
||||
# If this event has a custom domain, send the user there
|
||||
domain, domainmode = get_event_domain(request.event, fallback=False, return_mode=True)
|
||||
domain = get_event_domain(request.event)
|
||||
if domain:
|
||||
if request.port and request.port not in (80, 443):
|
||||
domain = '%s:%d' % (domain, request.port)
|
||||
if domainmode == KnownDomain.MODE_EVENT_DOMAIN:
|
||||
path = request.get_full_path().split("/", 3)[-1]
|
||||
else:
|
||||
path = request.get_full_path().split("/", 2)[-1]
|
||||
path = request.get_full_path().split("/", 3)[-1]
|
||||
r = redirect_to_url(urljoin('%s://%s' % (request.scheme, domain), path))
|
||||
r['Access-Control-Allow-Origin'] = '*'
|
||||
return r
|
||||
@@ -390,7 +377,6 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
except Event.DoesNotExist:
|
||||
try:
|
||||
if hasattr(request, 'organizer_domain'):
|
||||
# Redirect for case-insensitive event slug
|
||||
event = request.organizer.events.get(
|
||||
slug__iexact=url.kwargs['event'],
|
||||
organizer=request.organizer,
|
||||
@@ -402,7 +388,6 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
return r
|
||||
else:
|
||||
if 'event' in url.kwargs and 'organizer' in url.kwargs:
|
||||
# Redirect for case-insensitive event or organizer slug
|
||||
event = Event.objects.select_related('organizer').get(
|
||||
slug__iexact=url.kwargs['event'],
|
||||
organizer__slug__iexact=url.kwargs['organizer']
|
||||
@@ -418,7 +403,6 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
raise Http404(_('The selected event was not found.'))
|
||||
except Organizer.DoesNotExist:
|
||||
if 'organizer' in url.kwargs:
|
||||
# Redirect for case-insensitive organizer slug
|
||||
try:
|
||||
organizer = Organizer.objects.get(
|
||||
slug__iexact=url.kwargs['organizer']
|
||||
|
||||
@@ -89,7 +89,7 @@ class CheckoutView(View):
|
||||
else:
|
||||
previous_step = step
|
||||
step.c_is_before = True
|
||||
step.c_resolved_url = step.get_step_url(request) + '?dir=prev'
|
||||
step.c_resolved_url = step.get_step_url(request)
|
||||
raise Http404()
|
||||
|
||||
def redirect(self, url):
|
||||
|
||||
@@ -77,7 +77,7 @@ class RedirectBackMixin:
|
||||
self.redirect_field_name,
|
||||
self.request.GET.get(self.redirect_field_name, '')
|
||||
)
|
||||
hosts = list(KnownDomain.objects.filter(organizer=self.request.organizer).values_list('domainname', flat=True))
|
||||
hosts = list(KnownDomain.objects.filter(event__organizer=self.request.organizer).values_list('domainname', flat=True))
|
||||
siteurlsplit = urlsplit(settings.SITE_URL)
|
||||
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
||||
hosts = ['%s:%d' % (h, siteurlsplit.port) for h in hosts]
|
||||
@@ -168,7 +168,7 @@ class LogoutView(View):
|
||||
return HttpResponseRedirect(next_page)
|
||||
|
||||
def get_next_page(self):
|
||||
if getattr(self.request, 'domain_mode', 'system') in (KnownDomain.MODE_ORG_ALT_DOMAIN, KnownDomain.MODE_EVENT_DOMAIN):
|
||||
if getattr(self.request, 'event_domain', False):
|
||||
# After we cleared the cookies on this domain, redirect to the parent domain to clear cookies as well
|
||||
next_page = eventreverse(self.request.organizer, 'presale:organizer.customer.logout', kwargs={})
|
||||
if self.redirect_field_name in self.request.POST or self.redirect_field_name in self.request.GET:
|
||||
@@ -188,7 +188,7 @@ class LogoutView(View):
|
||||
self.redirect_field_name,
|
||||
self.request.GET.get(self.redirect_field_name)
|
||||
)
|
||||
hosts = list(KnownDomain.objects.filter(organizer=self.request.organizer).values_list('domainname', flat=True))
|
||||
hosts = list(KnownDomain.objects.filter(event__organizer=self.request.organizer).values_list('domainname', flat=True))
|
||||
siteurlsplit = urlsplit(settings.SITE_URL)
|
||||
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
||||
hosts = ['%s:%d' % (h, siteurlsplit.port) for h in hosts]
|
||||
|
||||
@@ -71,7 +71,7 @@ from pretix.helpers.formats.en.formats import (
|
||||
)
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.forms.organizer import EventListFilterForm
|
||||
from pretix.presale.ical import get_public_ical
|
||||
from pretix.presale.views import OrganizerViewMixin
|
||||
@@ -1305,8 +1305,3 @@ class OrganizerFavicon(View):
|
||||
return redirect_to_url(get_thumbnail(icon_file, '32x32^', formats=settings.PILLOW_FORMATS_QUESTIONS_FAVICON).thumb.url)
|
||||
else:
|
||||
return redirect_to_url(static("pretixbase/img/favicon.ico"))
|
||||
|
||||
|
||||
class RedirectToOrganizerIndex(View):
|
||||
def get(self, *args, **kwargs):
|
||||
return redirect_to_url(build_absolute_uri(self.request.organizer, "presale:organizer.index"))
|
||||
|
||||
@@ -726,11 +726,7 @@ PASSWORD_HASHERS = [
|
||||
# the HistoricPassword model will not be changed automatically. In case a serious issue with a hasher
|
||||
# comes to light, dropping the contents of the HistoricPassword table might be the more risk-adequate
|
||||
# decision.
|
||||
*(
|
||||
["django.contrib.auth.hashers.Argon2PasswordHasher"]
|
||||
if config.getboolean('django', 'passwords_argon2', fallback=True)
|
||||
else []
|
||||
),
|
||||
"django.contrib.auth.hashers.Argon2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
|
||||
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
|
||||
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
|
||||
|
||||
@@ -275,12 +275,12 @@
|
||||
}
|
||||
|
||||
// Apply the mixin to the panel headings only
|
||||
.panel-default > .panel-heading, .panel-default > legend > .panel-heading { @include panel-heading-styles($panel-default-heading-bg); }
|
||||
.panel-primary > .panel-heading, .panel-primary > legend > .panel-heading { @include panel-heading-styles($panel-primary-heading-bg); }
|
||||
.panel-success > .panel-heading, .panel-success > legend > .panel-heading { @include panel-heading-styles($panel-success-heading-bg); }
|
||||
.panel-info > .panel-heading, .panel-info > legend > .panel-heading { @include panel-heading-styles($panel-info-heading-bg); }
|
||||
.panel-warning > .panel-heading, .panel-warning > legend > .panel-heading { @include panel-heading-styles($panel-warning-heading-bg); }
|
||||
.panel-danger > .panel-heading, .panel-danger > legend > .panel-heading { @include panel-heading-styles($panel-danger-heading-bg); }
|
||||
.panel-default > .panel-heading { @include panel-heading-styles($panel-default-heading-bg); }
|
||||
.panel-primary > .panel-heading { @include panel-heading-styles($panel-primary-heading-bg); }
|
||||
.panel-success > .panel-heading { @include panel-heading-styles($panel-success-heading-bg); }
|
||||
.panel-info > .panel-heading { @include panel-heading-styles($panel-info-heading-bg); }
|
||||
.panel-warning > .panel-heading { @include panel-heading-styles($panel-warning-heading-bg); }
|
||||
.panel-danger > .panel-heading { @include panel-heading-styles($panel-danger-heading-bg); }
|
||||
|
||||
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@mixin panel-variant($border, $heading-text-color, $heading-bg-color, $heading-border) {
|
||||
border-color: $border;
|
||||
|
||||
& > .panel-heading, & > legend > .panel-heading {
|
||||
& > .panel-heading {
|
||||
color: $heading-text-color;
|
||||
background-color: $heading-bg-color;
|
||||
border-color: $heading-border;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
$("select[data-country-information-url]").each(function () {
|
||||
let xhr;
|
||||
const dependency = $(this),
|
||||
loader = $("<span class='fa fa-cog fa-spin'></span>").hide().prependTo(dependency.closest(".form-group").find("label")),
|
||||
url = this.getAttribute('data-country-information-url'),
|
||||
form = dependency.closest(".panel-body, form, .profile-scope"),
|
||||
isRequired = dependency.closest(".form-group").is(".required"),
|
||||
dependents = {
|
||||
'city': form.find("input[name$=city]"),
|
||||
'zipcode': form.find("input[name$=zipcode]"),
|
||||
'street': form.find("textarea[name$=street]"),
|
||||
'state': form.find("select[name$=state]"),
|
||||
'vat_id': form.find("input[name$=vat_id]"),
|
||||
},
|
||||
update = function (ev) {
|
||||
if (xhr) {
|
||||
xhr.abort();
|
||||
}
|
||||
for (var k in dependents) dependents[k].prop("disabled", true);
|
||||
loader.show();
|
||||
xhr = $.getJSON(url + '?country=' + dependency.val(), function (data) {
|
||||
var selected_value = dependents.state.prop("data-selected-value");
|
||||
if (selected_value) dependents.state.prop("data-selected-value", "");
|
||||
dependents.state.find("option:not([value=''])").remove();
|
||||
if (data.data.length > 0) {
|
||||
$.each(data.data, function (k, s) {
|
||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
||||
if (selected_value == s.code) o.prop("selected", true);
|
||||
dependents.state.append(o);
|
||||
});
|
||||
}
|
||||
for(var k in dependents) {
|
||||
const options = data[k],
|
||||
dependent = dependents[k],
|
||||
visible = 'visible' in options ? options.visible : true,
|
||||
required = 'required' in options && options.required && isRequired && visible;
|
||||
|
||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||
dependent.prop("required", required);
|
||||
}
|
||||
for (var k in dependents) dependents[k].prop("disabled", false);
|
||||
}).always(function() {
|
||||
loader.hide();
|
||||
}).fail(function(){
|
||||
// In case of errors, show everything and require nothing, we can still handle errors in backend
|
||||
for(var k in dependents) {
|
||||
const dependent = dependents[k],
|
||||
visible = true,
|
||||
required = false;
|
||||
|
||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||
dependent.prop("required", required);
|
||||
}
|
||||
});
|
||||
};
|
||||
dependents.state.prop("data-selected-value", dependents.state.val());
|
||||
update();
|
||||
dependency.on("change", update);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -53,9 +53,7 @@ $in-border-radius-small: 2px !default;
|
||||
--pretix-brand-primary-darken-17: #{darken($in-brand-primary, 17%)};
|
||||
--pretix-brand-primary-darken-20: #{darken($in-brand-primary, 20%)};
|
||||
--pretix-brand-primary-darken-30: #{darken($in-brand-primary, 30%)};
|
||||
--pretix-brand-primary-tint-90: #{tint($in-brand-primary, 90%)};
|
||||
--pretix-brand-primary-shade-25: #{shade($in-brand-primary, 25%)};
|
||||
--pretix-brand-primary-shade-42: #{shade($in-brand-primary, 42%)};
|
||||
--pretix-brand-primary-lighten-28-saturate-20: #{saturate(lighten($in-brand-primary, 28%), 20%)};
|
||||
--pretix-brand-primary-lighten-23-saturate-2: #{saturate(lighten($in-brand-primary, 23%), 2%)};
|
||||
|
||||
|
||||
@@ -434,6 +434,60 @@ var form_handlers = function (el) {
|
||||
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
||||
});
|
||||
|
||||
$("input[name$=vat_id][data-countries-with-vat-id]").each(function () {
|
||||
var dependent = $(this),
|
||||
dependency_country = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
||||
dependency_id_is_business_1 = $(this).closest(".panel-body, form").find('input[id$=id_is_business_1]'),
|
||||
update = function (ev) {
|
||||
if (dependency_id_is_business_1.length && !dependency_id_is_business_1.prop("checked")) {
|
||||
dependent.closest(".form-group").hide();
|
||||
} else if (dependent.attr('data-countries-with-vat-id').split(',').includes(dependency_country.val())) {
|
||||
dependent.closest(".form-group").show();
|
||||
} else {
|
||||
dependent.closest(".form-group").hide();
|
||||
}
|
||||
};
|
||||
update();
|
||||
dependency_country.on("change", update);
|
||||
dependency_id_is_business_1.on("change", update);
|
||||
});
|
||||
|
||||
$("select[name$=state]:not([data-static])").each(function () {
|
||||
var dependent = $(this),
|
||||
counter = 0,
|
||||
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
||||
update = function (ev) {
|
||||
counter++;
|
||||
var curCounter = counter;
|
||||
dependent.prop("disabled", true);
|
||||
dependency.closest(".form-group").find("label").prepend("<span class='fa fa-cog fa-spin'></span> ");
|
||||
$.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) {
|
||||
if (counter > curCounter) {
|
||||
return; // Lost race
|
||||
}
|
||||
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
|
||||
if (data.data.length > 0) {
|
||||
$.each(data.data, function (k, s) {
|
||||
dependent.append($("<option>").attr("value", s.code).text(s.name));
|
||||
});
|
||||
dependent.closest(".form-group").show();
|
||||
dependent.prop('required', dependency.prop("required"));
|
||||
} else {
|
||||
dependent.closest(".form-group").hide();
|
||||
dependent.prop("required", false);
|
||||
}
|
||||
dependent.prop("disabled", false);
|
||||
dependency.closest(".form-group").find("label .fa-spin").remove();
|
||||
});
|
||||
};
|
||||
if (dependent.find("option").length === 1) {
|
||||
dependent.closest(".form-group").hide();
|
||||
} else {
|
||||
dependent.prop('required', dependency.prop("required"));
|
||||
}
|
||||
dependency.on("change", update);
|
||||
});
|
||||
|
||||
el.find("div.scrolling-choice:not(.no-search)").each(function () {
|
||||
if ($(this).find("input[type=text]").length > 0) {
|
||||
return;
|
||||
|
||||
@@ -194,6 +194,66 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
div.mail-preview {
|
||||
border: 1px solid #ccc;
|
||||
border-top-width: 1px;
|
||||
border-radius: 3px;
|
||||
|
||||
.placeholder {
|
||||
background: var(--pretix-brand-warning-transparent-60);
|
||||
}
|
||||
}
|
||||
|
||||
.mail-preview-group div[lang] {
|
||||
@include border-top-radius(0px);
|
||||
@include border-bottom-radius(0px);
|
||||
border-top-width: 0;
|
||||
margin-bottom: 0;
|
||||
padding-right: 15px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
&:first-child {
|
||||
@include border-top-radius($input-border-radius);
|
||||
border-top-width: 1px;
|
||||
}
|
||||
&:last-child {
|
||||
@include border-bottom-radius($input-border-radius);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h2, h3 {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* Reset styling from bootstrap that we don't actually have in emails */
|
||||
pre {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-line {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
@@ -543,7 +603,7 @@ table td > .checkbox input[type="checkbox"] {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.panel-default>.accordion-radio>.panel-heading, fieldset.accordion-panel>legend>.panel-heading {
|
||||
.panel-default>.accordion-radio>.panel-heading {
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
padding: 12px 15px;
|
||||
@@ -555,12 +615,6 @@ table td > .checkbox input[type="checkbox"] {
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
fieldset.accordion-panel > legend {
|
||||
display: contents;
|
||||
}
|
||||
fieldset.accordion-panel[disabled] > .panel-body {
|
||||
display: none;
|
||||
}
|
||||
.maildesignpreview {
|
||||
label {
|
||||
display: block;
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
div.mail-preview {
|
||||
border: 1px solid #ccc;
|
||||
border-top-width: 1px;
|
||||
border-radius: 3px;
|
||||
|
||||
.placeholder {
|
||||
background: var(--pretix-brand-warning-transparent-60);
|
||||
}
|
||||
.placeholder-html {
|
||||
background: none;
|
||||
outline: 2px solid var(--pretix-brand-warning-transparent-60);
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.mail-preview-group div[lang] {
|
||||
@include border-top-radius(0px);
|
||||
@include border-bottom-radius(0px);
|
||||
border-top-width: 0;
|
||||
margin-bottom: 0;
|
||||
padding-right: 15px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
&:first-child {
|
||||
@include border-top-radius($input-border-radius);
|
||||
border-top-width: 1px;
|
||||
}
|
||||
&:last-child {
|
||||
@include border-bottom-radius($input-border-radius);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h2, h3 {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* Reset styling from bootstrap that we don't actually have in emails */
|
||||
pre {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Add some basic styling similar to our default email renderers */
|
||||
a.button {
|
||||
display: inline-block;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.33333;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
margin: 5px;
|
||||
text-decoration: none;
|
||||
color: var(--pretix-brand-primary);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-right, table td.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
@import "_flags.scss";
|
||||
@import "_orders.scss";
|
||||
@import "_dashboard.scss";
|
||||
@import "_mail_preview.scss";
|
||||
@import "../../pretixbase/scss/webfont.scss";
|
||||
@import "../../fileupload/jquery.fileupload.scss";
|
||||
@import "../../leaflet/leaflet.scss";
|
||||
|
||||
@@ -243,11 +243,6 @@ function setup_basics(el) {
|
||||
$($(this).attr("data-target")).collapse('show');
|
||||
}
|
||||
});
|
||||
$("fieldset.accordion-panel > legend input[type=radio]").change(function() {
|
||||
$(this).closest("fieldset").siblings("fieldset").prop('disabled', true);
|
||||
$(this).closest("fieldset").prop('disabled', false);
|
||||
}).each(function() { $(this).closest("fieldset").prop('disabled', true); }).filter(":checked").trigger('change');
|
||||
|
||||
el.find(".js-only").removeClass("js-only");
|
||||
el.find(".js-hidden").hide();
|
||||
|
||||
@@ -464,26 +459,10 @@ $(function () {
|
||||
.on("change mouseup keyup", update_cart_form);
|
||||
|
||||
$(".table-calendar td.has-events").click(function () {
|
||||
var $grid = $(this).closest("[role='grid']");
|
||||
$grid.find("[aria-selected]").attr("aria-selected", false);
|
||||
$(this).attr("aria-selected", true);
|
||||
$("#selected-day")
|
||||
.html($(this).find(".events").clone())
|
||||
.prepend($("<h3>").text($(this).attr("data-date")));
|
||||
}).each(function() {
|
||||
// check all events classes and set the "winning" class for the availability of the day-label on mobile
|
||||
var $dayLabel = $('.day-label', this);
|
||||
if ($('.available.low', this).length == $('.available', this).length) {
|
||||
$dayLabel.addClass('low');
|
||||
}
|
||||
var classes = ['available', 'waitinglist', 'soon', 'reserved', 'soldout', 'continued', 'over'];
|
||||
for (var c of classes) {
|
||||
if ($('.'+c, this).length) {
|
||||
$dayLabel.addClass(c);
|
||||
// CAREFUL: „return“ as „break“ is not supported before ES2015 and breaks e.g. on iOS 15
|
||||
return;
|
||||
}
|
||||
}
|
||||
var $tr = $(this).closest(".table-calendar").find(".selected-day");
|
||||
$tr.find("td").html($(this).find(".events").clone());
|
||||
$tr.find("td").prepend($("<h3>").text($(this).attr("data-date")));
|
||||
$tr.removeClass("hidden");
|
||||
});
|
||||
|
||||
$(".print-this-page").on("click", function (e) {
|
||||
@@ -538,6 +517,65 @@ $(function () {
|
||||
dependency.closest('.form-group, form').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
|
||||
});
|
||||
|
||||
$("input[name$=vat_id][data-countries-with-vat-id]").each(function () {
|
||||
var dependent = $(this),
|
||||
dependency_country = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
||||
dependency_id_is_business_1 = $(this).closest(".panel-body, form").find('input[id$=id_is_business_1]'),
|
||||
update = function (ev) {
|
||||
if (dependency_id_is_business_1.length && !dependency_id_is_business_1.prop("checked")) {
|
||||
dependent.closest(".form-group").hide();
|
||||
} else if (dependent.attr('data-countries-with-vat-id').split(',').includes(dependency_country.val())) {
|
||||
dependent.closest(".form-group").show();
|
||||
} else {
|
||||
dependent.closest(".form-group").hide();
|
||||
}
|
||||
};
|
||||
update();
|
||||
dependency_country.on("change", update);
|
||||
dependency_id_is_business_1.on("change", update);
|
||||
});
|
||||
|
||||
$("select[name$=state]").each(function () {
|
||||
var dependent = $(this),
|
||||
counter = 0,
|
||||
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
||||
update = function (ev) {
|
||||
counter++;
|
||||
var curCounter = counter;
|
||||
dependent.prop("disabled", true);
|
||||
dependency.closest(".form-group").find("label").prepend("<span class='fa fa-cog fa-spin'></span> ");
|
||||
$.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) {
|
||||
if (counter > curCounter) {
|
||||
return; // Lost race
|
||||
}
|
||||
var selected_value = dependent.prop("data-selected-value");
|
||||
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
|
||||
if (data.data.length > 0) {
|
||||
$.each(data.data, function (k, s) {
|
||||
var o = $("<option>").attr("value", s.code).text(s.name);
|
||||
if (s.code == selected_value || (selected_value && selected_value.indexOf && selected_value.indexOf(s.code) > -1)) {
|
||||
o.prop("selected", true);
|
||||
}
|
||||
dependent.append(o);
|
||||
});
|
||||
dependent.closest(".form-group").show();
|
||||
dependent.prop('required', dependency.prop("required"));
|
||||
} else {
|
||||
dependent.closest(".form-group").hide();
|
||||
dependent.prop("required", false);
|
||||
}
|
||||
dependent.prop("disabled", false);
|
||||
dependency.closest(".form-group").find("label .fa-spin").remove();
|
||||
});
|
||||
};
|
||||
if (dependent.find("option").length === 1) {
|
||||
dependent.closest(".form-group").hide();
|
||||
} else {
|
||||
dependent.prop('required', dependency.prop("required"));
|
||||
}
|
||||
dependency.on("change", update);
|
||||
});
|
||||
|
||||
form_handlers($("body"));
|
||||
|
||||
var local_tz = moment.tz.guess()
|
||||
|
||||
@@ -13,66 +13,93 @@
|
||||
padding: 0;
|
||||
}
|
||||
a.event {
|
||||
--status-bg-color: var(--pretix-brand-primary-tint-90);
|
||||
--status-text-color: var(--pretix-brand-primary-shade-42);
|
||||
--status-border-color: #{$brand-primary};
|
||||
position: relative;
|
||||
display: block;
|
||||
background: var(--status-bg-color);
|
||||
color: var(--status-text-color);
|
||||
border: 1px solid var(--status-border-color);
|
||||
background: var(--pretix-brand-primary-lighten-48);
|
||||
color: $brand-primary;
|
||||
border-radius: $border-radius-base;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 11px;
|
||||
height: 100%;
|
||||
background: var(--status-border-color);
|
||||
}
|
||||
border-style: solid;
|
||||
border-color: var(--pretix-brand-primary-lighten-30);
|
||||
border-width: 1px 1px 1px 12px;
|
||||
border-left-color: inherit;
|
||||
|
||||
padding: 3px 5px 3px 17px;
|
||||
padding: 3px 5px;
|
||||
margin-bottom: 3px;
|
||||
font-size: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
outline: 1px solid var(--status-border-color);
|
||||
outline-offset: 0;
|
||||
background: var(--pretix-brand-primary-lighten-50);
|
||||
border-color: $brand-primary;
|
||||
}
|
||||
&:focus {
|
||||
outline: 2px solid var(--status-border-color);
|
||||
outline-offset: 2px;
|
||||
outline-color: inherit;
|
||||
}
|
||||
|
||||
&.continued, &.over {
|
||||
--status-bg-color: #{$table-bg-accent};
|
||||
--status-text-color: #{$text-muted};
|
||||
--status-border-color: #{tint($text-muted, 50%)};
|
||||
background: lighten(#767676, 54%);
|
||||
border-color: lighten(#767676, 44%);
|
||||
border-left-color: lighten(#767676, 44%);
|
||||
color: #767676;
|
||||
&:hover {
|
||||
background: lighten(#767676, 54%);
|
||||
border-color: lighten(#767676, 40%);
|
||||
}
|
||||
}
|
||||
|
||||
&.soon {
|
||||
background: var(--pretix-brand-primary-lighten-53);
|
||||
border-color: var(--pretix-brand-primary-lighten-40);
|
||||
border-left-color: var(--pretix-brand-primary-lighten-20);
|
||||
color: var(--pretix-brand-primary-lighten-5);
|
||||
&:hover {
|
||||
background: var(--pretix-brand-primary-lighten-55);
|
||||
border-color: var(--pretix-brand-primary-lighten-20);
|
||||
}
|
||||
}
|
||||
|
||||
&.available {
|
||||
--status-bg-color: #{$alert-success-bg};
|
||||
--status-text-color: #{$alert-success-text};
|
||||
--status-border-color: #{$alert-success-border};
|
||||
background: var(--pretix-brand-success-lighten-48);
|
||||
border-color: var(--pretix-brand-success-lighten-30);
|
||||
border-left-color: $brand-success;
|
||||
color: var(--pretix-brand-success-darken-12);
|
||||
|
||||
&.low:before {
|
||||
background: linear-gradient(to bottom, var(--pretix-brand-warning) 1em, var(--status-border-color) 2.5em);
|
||||
&.low {
|
||||
border-left-color: var(--pretix-brand-warning-lighten-12);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--pretix-brand-success-lighten-50);
|
||||
border-color: $brand-success;
|
||||
|
||||
&.low {
|
||||
border-left-color: $brand-warning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.waitinglist {
|
||||
--status-bg-color: #{$alert-warning-bg};
|
||||
--status-text-color: #{$alert-warning-text};
|
||||
--status-border-color: #{$alert-warning-border};
|
||||
background: var(--pretix-brand-warning-lighten-41);
|
||||
border-color: var(--pretix-brand-warning-lighten-31);
|
||||
border-left-color: var(--pretix-brand-warning-lighten-12);
|
||||
color: #963;
|
||||
|
||||
&:hover {
|
||||
background: var(--pretix-brand-warning-lighten-43);
|
||||
border-color: $brand-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&.reserved, &.soldout, {
|
||||
--status-bg-color: #{$alert-danger-bg};
|
||||
--status-text-color: #{$alert-danger-text};
|
||||
--status-border-color: #{$alert-danger-border};
|
||||
background: var(--pretix-brand-danger-lighten-43);
|
||||
border-color: var(--pretix-brand-danger-lighten-30);
|
||||
border-left-color: var(--pretix-brand-danger-lighten-30);
|
||||
color: var(--pretix-brand-danger-darken-5);
|
||||
|
||||
&:hover {
|
||||
background: var(--pretix-brand-danger-lighten-45);
|
||||
border-color: var(--pretix-brand-danger-lighten-25);
|
||||
}
|
||||
}
|
||||
|
||||
&.available > *:first-child,
|
||||
@@ -407,6 +434,8 @@ if concurrency is higher than 9, JavaScript (currently in pretixpresale/js/ui/ma
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
.week-calendar {
|
||||
display: flex;
|
||||
@@ -435,35 +464,24 @@ if concurrency is higher than 9, JavaScript (currently in pretixpresale/js/ui/ma
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.table-calendar {
|
||||
.day, .no-day {
|
||||
padding: 3px 2px;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.day .events {
|
||||
display: none;
|
||||
}
|
||||
a.day-label, .day-label {
|
||||
--status-text-color: #{$text-muted};
|
||||
display: block;
|
||||
padding: 3px 3px 15px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: var(--status-text-color);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.no-events .day-label {
|
||||
padding-left: 12px;
|
||||
}
|
||||
a.day-label:before {
|
||||
width: 8px;
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.table-calendar, .week-calendar {
|
||||
.selected-day {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
#selected-day:has(*) {
|
||||
padding: $table-cell-padding;
|
||||
}
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.table-calendar .day .events {
|
||||
display: none;
|
||||
}
|
||||
.table-calendar td.day.has-events {
|
||||
background: $brand-primary;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
.table-calendar td.day.has-events:hover {
|
||||
background: var(--pretix-brand-primary-darken-15);
|
||||
}
|
||||
}
|
||||
#monthselform .row {
|
||||
|
||||
@@ -134,7 +134,7 @@ a.btn, button.btn {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.panel-default>.accordion-radio>.panel-heading, fieldset.accordion-panel>legend>.panel-heading {
|
||||
.panel-default>.accordion-radio>.panel-heading {
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
padding: 8px 15px;
|
||||
@@ -147,12 +147,6 @@ a.btn, button.btn {
|
||||
.panel-default>.accordion-radio+.panel-collapse>.panel-body {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
fieldset.accordion-panel > legend {
|
||||
display: contents;
|
||||
}
|
||||
fieldset.accordion-panel[disabled] > .panel-body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 0px solid #ddd;
|
||||
|
||||
@@ -1601,80 +1601,6 @@ def test_event_block_unblock_seat(token_client, organizer, event, seatingplan, i
|
||||
assert resp.data['blocked'] is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_block_unblock_seat_bulk(token_client, organizer, event, seatingplan, item):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"seating_plan": seatingplan.pk,
|
||||
"seat_category_mapping": {
|
||||
"Stalls": item.pk
|
||||
}
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
event.refresh_from_db()
|
||||
|
||||
s1 = event.seats.first()
|
||||
s2 = event.seats.last()
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/seats/bulk_block/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"ids": [s1.pk, s2.pk],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
s1.refresh_from_db()
|
||||
s2.refresh_from_db()
|
||||
assert s1.blocked
|
||||
assert s2.blocked
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/seats/bulk_unblock/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"ids": [s1.pk, s2.pk],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
s1.refresh_from_db()
|
||||
s2.refresh_from_db()
|
||||
assert not s1.blocked
|
||||
assert not s2.blocked
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/seats/bulk_block/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"seat_guids": [s1.seat_guid, s2.seat_guid],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
s1.refresh_from_db()
|
||||
s2.refresh_from_db()
|
||||
assert s1.blocked
|
||||
assert s2.blocked
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/seats/bulk_unblock/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"seat_guids": [s1.seat_guid, s2.seat_guid],
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
s1.refresh_from_db()
|
||||
s2.refresh_from_db()
|
||||
assert not s1.blocked
|
||||
assert not s2.blocked
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_expand_seat_filter_and_querycount(token_client, organizer, event, seatingplan, item):
|
||||
event.settings.seating_minimal_distance = 2
|
||||
|
||||
@@ -1967,7 +1967,7 @@ def test_pdf_data(token_client, organizer, event, order, django_assert_max_num_q
|
||||
assert not resp.data['positions'][0].get('pdf_data')
|
||||
|
||||
# order list
|
||||
with django_assert_max_num_queries(32):
|
||||
with django_assert_max_num_queries(31):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
))
|
||||
@@ -1982,7 +1982,7 @@ def test_pdf_data(token_client, organizer, event, order, django_assert_max_num_q
|
||||
assert not resp.data['results'][0]['positions'][0].get('pdf_data')
|
||||
|
||||
# position list
|
||||
with django_assert_max_num_queries(35):
|
||||
with django_assert_max_num_queries(34):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
))
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from pretix.helpers.format import (
|
||||
PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from pretix.helpers.format import format_map
|
||||
|
||||
|
||||
def test_format_map():
|
||||
@@ -30,16 +28,3 @@ def test_format_map():
|
||||
assert format_map("Foo {bar.__module__}", {"bar": 3}) == "Foo {bar.__module__}"
|
||||
assert format_map("Foo {bar!s}", {"bar": 3}) == "Foo 3"
|
||||
assert format_map("Foo {bar:<20}", {"bar": 3}) == "Foo 3"
|
||||
|
||||
|
||||
def test_format_alternatives():
|
||||
ctx = {
|
||||
"bar": PlainHtmlAlternativeString(
|
||||
"plain text",
|
||||
"<span>HTML version</span>",
|
||||
)
|
||||
}
|
||||
|
||||
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_IGNORE_RICH) == "Foo {bar}"
|
||||
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_PLAIN) == "Foo plain text"
|
||||
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_HTML) == "Foo <span>HTML version</span>"
|
||||
|
||||
@@ -40,21 +40,16 @@ def env():
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_control_only_on_main_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
r = client.get('/control/login', HTTP_HOST='foobar')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://example.com/control/login'
|
||||
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1], mode=KnownDomain.MODE_EVENT_DOMAIN)
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
r = client.get('/control/login', HTTP_HOST='barfoo')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://example.com/control/login'
|
||||
|
||||
KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
r = client.get('/control/login', HTTP_HOST='altfoo')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://example.com/control/login'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_append_slash(env, client):
|
||||
@@ -85,15 +80,6 @@ def test_event_on_custom_domain(env, client):
|
||||
assert b'<meta property="og:title" content="MRMCD2015" />' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_on_org_alt_domain(env, client):
|
||||
d = KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
r = client.get('/2015/', HTTP_HOST='foobar')
|
||||
assert r.status_code == 200
|
||||
assert b'<meta property="og:title" content="MRMCD2015" />' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_path_without_trailing_slash_on_org_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
@@ -109,15 +95,6 @@ def test_event_with_org_domain_on_main_domain(env, client):
|
||||
assert r['Location'] == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_with_org_alt_domain_on_main_domain(env, client):
|
||||
d = KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d.event_assignments.create(event=env[1])
|
||||
r = client.get('/mrmcd/2015/', HTTP_HOST='example.com')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_with_custom_domain_on_main_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], event=env[1])
|
||||
@@ -135,40 +112,6 @@ def test_event_with_custom_domain_on_org_domain(env, client):
|
||||
assert r['Location'] == 'http://barfoo'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_with_custom_domain_on_org_alt_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
r = client.get('/2015/', HTTP_HOST='altfoo')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://barfoo'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_with_org_alt_domain_on_org_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
|
||||
r = client.get('/2015/', HTTP_HOST='foobar')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://altfoo/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_without_org_alt_domain_on_org_alt_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
r = client.get('/', HTTP_HOST='foobar')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://example.com/mrmcd/'
|
||||
|
||||
KnownDomain.objects.create(domainname='foobaz', organizer=env[0], mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
r = client.get('/', HTTP_HOST='foobar')
|
||||
assert r.status_code == 302
|
||||
assert r['Location'] == 'http://foobaz/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_organizer_with_org_domain_on_main_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
@@ -200,10 +143,6 @@ def test_unknown_event_on_org_domain(env, client):
|
||||
r = client.get('/1234/', HTTP_HOST='foobar')
|
||||
assert r.status_code == 404
|
||||
|
||||
KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
r = client.get('/1234/', HTTP_HOST='altfoo')
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cookie_domain_on_org_domain(env, client):
|
||||
@@ -214,16 +153,6 @@ def test_cookie_domain_on_org_domain(env, client):
|
||||
assert r.client.cookies['pretix_session']['domain'] == ''
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cookie_domain_on_org_alt_domain(env, client):
|
||||
d = KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
client.post('/2015/cart/add', HTTP_HOST='foobar')
|
||||
r = client.get('/2015/', HTTP_HOST='foobar')
|
||||
assert r.client.cookies['pretix_csrftoken']['domain'] == ''
|
||||
assert r.client.cookies['pretix_session']['domain'] == ''
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cookie_domain_on_event_domain(env, client):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
|
||||
@@ -62,36 +62,6 @@ def test_event_custom_domain_front_page(env):
|
||||
assert rendered == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_event_domain_front_page(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], event=env[1], mode=KnownDomain.MODE_EVENT_DOMAIN)
|
||||
rendered = TEMPLATE_FRONT_PAGE.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://foobar/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_org_alt_domain_front_page(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
rendered = TEMPLATE_FRONT_PAGE.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://altfoo/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_org_alt_domain_unassigned_front_page(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0], mode=KnownDomain.MODE_ORG_DOMAIN)
|
||||
KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
rendered = TEMPLATE_FRONT_PAGE.render(Context({
|
||||
'event': env[1]
|
||||
})).strip()
|
||||
assert rendered == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_main_domain_kwargs(env):
|
||||
rendered = TEMPLATE_KWARGS.render(Context({
|
||||
|
||||
@@ -37,7 +37,7 @@ def env():
|
||||
organizer=o, name='MRMCD2015', slug='2015',
|
||||
date_from=now()
|
||||
)
|
||||
event.cache.clear()
|
||||
event.get_cache().clear()
|
||||
return o, event
|
||||
|
||||
|
||||
@@ -60,16 +60,6 @@ def test_event_org_domain_kwargs(env):
|
||||
assert eventreverse(env[1], 'presale:event.checkout', {'step': 'payment'}) == 'http://foobar/2015/checkout/payment/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_org_alt_domain_kwargs(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
assert eventreverse(env[1], 'presale:event.checkout', {'step': 'payment'}) == 'http://foobar/2015/checkout/payment/'
|
||||
d.event_assignments.create(event=env[1])
|
||||
with scopes_disabled():
|
||||
assert eventreverse(Event.objects.get(pk=env[1].pk), 'presale:event.checkout', {'step': 'payment'}) == 'http://altfoo/2015/checkout/payment/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_main_domain_kwargs(env):
|
||||
assert eventreverse(env[1], 'presale:event.checkout', {'step': 'payment'}) == '/mrmcd/2015/checkout/payment/'
|
||||
@@ -82,15 +72,6 @@ def test_event_org_domain_front_page(env):
|
||||
assert eventreverse(env[0], 'presale:organizer.index') == 'http://foobar/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_org_alt_domain_front_page(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
assert eventreverse(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
assert eventreverse(env[0], 'presale:organizer.index') == 'http://foobar/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_domain_front_page(env):
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
@@ -128,8 +109,8 @@ def test_event_org_domain_keep_scheme(env):
|
||||
}
|
||||
})
|
||||
def test_event_main_domain_cache(env):
|
||||
env[0].cache.clear()
|
||||
with assert_num_queries(2):
|
||||
env[0].get_cache().clear()
|
||||
with assert_num_queries(1):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
with assert_num_queries(0):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
@@ -144,8 +125,8 @@ def test_event_main_domain_cache(env):
|
||||
})
|
||||
def test_event_org_domain_cache(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
env[0].cache.clear()
|
||||
with assert_num_queries(2):
|
||||
env[0].get_cache().clear()
|
||||
with assert_num_queries(1):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
with assert_num_queries(0):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
@@ -161,36 +142,13 @@ def test_event_org_domain_cache(env):
|
||||
def test_event_custom_domain_cache(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
env[0].cache.clear()
|
||||
env[0].get_cache().clear()
|
||||
with assert_num_queries(1):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
with assert_num_queries(0):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(CACHES={
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': 'unique-snowflake',
|
||||
}
|
||||
})
|
||||
@scopes_disabled()
|
||||
def test_event_org_alt_domain_cache_clear(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
kd_alt = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
env[0].cache.clear()
|
||||
with assert_num_queries(2):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
kd_alt.event_assignments.create(event=env[1])
|
||||
with assert_num_queries(2):
|
||||
ev = Event.objects.get(pk=env[1].pk)
|
||||
assert ev.pk == env[1].pk
|
||||
assert ev.organizer == env[0]
|
||||
with assert_num_queries(1):
|
||||
eventreverse(ev, 'presale:event.index')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(CACHES={
|
||||
'default': {
|
||||
@@ -202,14 +160,14 @@ def test_event_org_alt_domain_cache_clear(env):
|
||||
def test_event_org_domain_cache_clear(env):
|
||||
kd = KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
env[0].cache.clear()
|
||||
with assert_num_queries(2):
|
||||
with assert_num_queries(1):
|
||||
eventreverse(env[1], 'presale:event.index')
|
||||
kd.delete()
|
||||
with assert_num_queries(2):
|
||||
ev = Event.objects.get(pk=env[1].pk)
|
||||
assert ev.pk == env[1].pk
|
||||
assert ev.organizer == env[0]
|
||||
with assert_num_queries(2):
|
||||
with assert_num_queries(1):
|
||||
eventreverse(ev, 'presale:event.index')
|
||||
|
||||
|
||||
@@ -232,7 +190,7 @@ def test_event_custom_domain_cache_clear(env):
|
||||
ev = Event.objects.get(pk=env[1].pk)
|
||||
assert ev.pk == env[1].pk
|
||||
assert ev.organizer == env[0]
|
||||
with assert_num_queries(2):
|
||||
with assert_num_queries(1):
|
||||
eventreverse(ev, 'presale:event.index')
|
||||
|
||||
|
||||
@@ -252,11 +210,3 @@ def test_event_custom_domain_absolute(env):
|
||||
def test_event_org_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_org_alt_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
|
||||
@@ -1207,65 +1207,6 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
}
|
||||
assert ia.name_cached == 'Mr John Kennedy'
|
||||
|
||||
def test_invoice_address_required_no_zipcode_country(self):
|
||||
self.event.settings.invoice_address_asked = True
|
||||
self.event.settings.invoice_address_required = True
|
||||
self.event.settings.invoice_address_not_asked_free = True
|
||||
self.event.settings.set('name_scheme', 'title_given_middle_family')
|
||||
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('input[name="city"]')), 1)
|
||||
|
||||
# Not all required fields filled out, expect failure
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name_parts_0': 'Mr',
|
||||
'name_parts_1': 'John',
|
||||
'name_parts_2': '',
|
||||
'name_parts_3': 'Kennedy',
|
||||
'street': '',
|
||||
'zipcode': '',
|
||||
'city': '',
|
||||
'country': 'BI',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
# Correct request for a country where zip code is not required in address
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name_parts_0': 'Mr',
|
||||
'name_parts_1': 'John',
|
||||
'name_parts_2': '',
|
||||
'name_parts_3': 'Kennedy',
|
||||
'street': 'BP 12345',
|
||||
'zipcode': '',
|
||||
'city': 'Bujumbura',
|
||||
'country': 'BI',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
with scopes_disabled():
|
||||
ia = InvoiceAddress.objects.last()
|
||||
assert ia.name_parts == {
|
||||
'title': 'Mr',
|
||||
'given_name': 'John',
|
||||
'middle_name': '',
|
||||
'family_name': 'Kennedy',
|
||||
"_scheme": "title_given_middle_family"
|
||||
}
|
||||
assert ia.name_cached == 'Mr John Kennedy'
|
||||
|
||||
def test_invoice_address_validated(self):
|
||||
self.event.settings.invoice_address_asked = True
|
||||
self.event.settings.invoice_address_required = True
|
||||
|
||||
@@ -378,13 +378,6 @@ def test_org_sso_login_new_customer_popup(env, client, provider):
|
||||
_sso_login(client, provider, popup_origin="https://popuporigin")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_org_sso_login_new_customer_popup_org_alt_domain(env, client, provider):
|
||||
d = KnownDomain.objects.create(organizer=env[0], domainname="popuporigin", mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
_sso_login(client, provider, popup_origin="https://popuporigin")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_org_sso_login_new_customer_popup_invalid_origin(env, client, provider):
|
||||
KnownDomain.objects.create(organizer=env[0], event=env[1], domainname="popuporigin")
|
||||
@@ -703,21 +696,16 @@ def client2():
|
||||
return Client()
|
||||
|
||||
|
||||
def _cross_domain_login(env, client, client2, org_alt=False):
|
||||
def _cross_domain_login(env, client, client2):
|
||||
with scopes_disabled():
|
||||
customer = env[0].customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password('foo')
|
||||
customer.save()
|
||||
KnownDomain.objects.create(domainname='org.test', organizer=env[0])
|
||||
if org_alt:
|
||||
d = KnownDomain.objects.create(domainname='event.test', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
else:
|
||||
KnownDomain.objects.create(domainname='event.test', organizer=env[0], event=env[1])
|
||||
KnownDomain.objects.create(domainname='event.test', organizer=env[0], event=env[1])
|
||||
|
||||
# Log in on org domain
|
||||
path = '/conf/' if org_alt else '/'
|
||||
r = client.post(f'/account/login?next=https://event.test{path}redeem&request_cross_domain_customer_auth=true', {
|
||||
r = client.post('/account/login?next=https://event.test/redeem&request_cross_domain_customer_auth=true', {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
}, HTTP_HOST='org.test')
|
||||
@@ -725,12 +713,12 @@ def _cross_domain_login(env, client, client2, org_alt=False):
|
||||
|
||||
u = urlparse(r.headers['Location'])
|
||||
assert u.netloc == 'event.test'
|
||||
assert u.path == path + 'redeem'
|
||||
assert u.path == '/redeem'
|
||||
q = parse_qs(u.query)
|
||||
assert 'cross_domain_customer_auth' in q
|
||||
|
||||
# Take session over to event domain
|
||||
r = client2.get(f'{path}?{u.query}', HTTP_HOST='event.test')
|
||||
r = client2.get(f'/?{u.query}', HTTP_HOST='event.test')
|
||||
assert r.status_code == 200
|
||||
assert b'john@example.org' in r.content
|
||||
|
||||
@@ -739,27 +727,12 @@ def _cross_domain_login(env, client, client2, org_alt=False):
|
||||
def test_cross_domain_login(env, client, client2):
|
||||
_cross_domain_login(env, client, client2)
|
||||
|
||||
# Logged in on evnet domain
|
||||
# Logged in on org domain
|
||||
r = client.get('/', HTTP_HOST='event.test')
|
||||
assert r.status_code == 200
|
||||
assert b'john@example.org' in r.content
|
||||
|
||||
# Logged in on org domain
|
||||
r = client2.get('/', HTTP_HOST='org.test')
|
||||
assert r.status_code == 200
|
||||
assert b'john@example.org' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cross_domain_login_org_alt(env, client, client2):
|
||||
_cross_domain_login(env, client, client2, org_alt=True)
|
||||
|
||||
# Logged in on org alt domain
|
||||
r = client.get('/conf/', HTTP_HOST='event.test')
|
||||
assert r.status_code == 200
|
||||
assert b'john@example.org' in r.content
|
||||
|
||||
# Logged in on org domain
|
||||
# Logged in on event domain
|
||||
r = client2.get('/', HTTP_HOST='org.test')
|
||||
assert r.status_code == 200
|
||||
assert b'john@example.org' in r.content
|
||||
|
||||
Reference in New Issue
Block a user