Compare commits

...

28 Commits

Author SHA1 Message Date
Raphael Michel
267bac7a9d Add license header 2024-12-06 15:22:39 +01:00
Mira Weller
c7b951346b add name_parts_is_empty helper 2024-12-06 13:03:45 +01:00
Mira Weller
d8adfdd06f Fix backend validation if name is required as part of a required non-business invoice address 2024-12-02 13:30:25 +01:00
Mira Weller
0279ca7d94 Add missing error handling to addressform.js 2024-12-02 10:15:16 +01:00
Richard Schreiber
d1989c3cd3 Fix all-optional in address-form for resellers (#4672) 2024-12-02 09:46:33 +01:00
Raphael Michel
61cb2e15cf Fix validation crash of InvoiceNameForm 2024-11-29 20:08:36 +01:00
Mira Weller
f2ee1d00b3 Don't use animation for address information load indicator 2024-11-29 17:09:14 +01:00
Mira
e8e9698a31 Update address field logic (Z#23163120) (#4659)
* Move country-dependent JS logic to separate file (avoids code duplication for presale and control)
* Correctly apply "required" attribute to address state field
* Load address format information when selecting country
* Fix some other bugs and inconsistencies
2024-11-29 14:56:56 +01:00
Richard Schreiber
a1bf7be244 [A11y] Improve customer account pages (#4654) 2024-11-29 14:16:40 +01:00
Patrick Chilton
f4ca9a5681 Translations: Update Hungarian
Currently translated at 45.6% (106 of 232 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/hu/

powered by weblate
2024-11-29 09:22:29 +01:00
dependabot[bot]
e6d984538f Update django-statici18n requirement from ==2.5.* to ==2.6.* (#4664)
Updates the requirements on [django-statici18n](https://github.com/zyegfryed/django-statici18n) to permit the latest version.
- [Changelog](https://github.com/zyegfryed/django-statici18n/blob/main/docs/changelog.rst)
- [Commits](https://github.com/zyegfryed/django-statici18n/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: django-statici18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 09:22:04 +01:00
dependabot[bot]
9f1ee9157f Update protobuf requirement from ==5.28.* to ==5.29.* (#4666)
Updates the requirements on [protobuf](https://github.com/protocolbuffers/protobuf) to permit the latest version.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.28.0-rc1...v5.29.0)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 09:21:54 +01:00
Raphael Michel
242e5af4b5 Bump version to 2024.12.0.dev0 2024-11-27 13:57:03 +01:00
Raphael Michel
7d6e98e6da Bump version to 2024.11.0 2024-11-27 13:56:37 +01:00
Mira
27f964f3ae Checkout flow: Observe direction when skipping AddOnsStep (#4658) 2024-11-27 11:13:07 +01:00
Patrick Chilton
84b3060c0f Translations: Update Hungarian
Currently translated at 10.7% (623 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/hu/

powered by weblate
2024-11-27 11:10:55 +01:00
CVZ-es
25dcb72f92 Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2024-11-27 11:10:55 +01:00
CVZ-es
4b078867c6 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2024-11-27 11:10:55 +01:00
Jakub Stribrny
c595a59d4a Translations: Update Czech
Currently translated at 73.5% (4250 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/cs/

powered by weblate
2024-11-26 18:48:58 +01:00
Patrick Chilton
f164daeaee Translations: Update Hungarian
Currently translated at 10.7% (621 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/hu/

powered by weblate
2024-11-26 18:48:58 +01:00
gabriblas
c6b6dd8d49 Translations: Update Italian
Currently translated at 24.3% (1409 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/it/

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
8038c87963 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2024-11-26 18:48:58 +01:00
Ryo
c45a970d32 Translations: Update Japanese
Currently translated at 3.8% (220 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2024-11-26 18:48:58 +01:00
kei ogane
a34517233d Translations: Update Japanese
Currently translated at 3.8% (220 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2024-11-26 18:48:58 +01:00
Yasunobu YesNo Kawaguchi
8fb2e5383c Translations: Update Japanese
Currently translated at 3.4% (200 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
86a00f3338 Translations: Update Spanish
Currently translated at 100.0% (5782 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2024-11-26 18:48:58 +01:00
CVZ-es
c8c0d3e7f5 Translations: Update French
Currently translated at 100.0% (5782 of 5782 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2024-11-26 18:48:58 +01:00
Raphael Michel
7dd455ce15 Fix #4641 -- Make usage of argon2id optional (#4643) 2024-11-26 17:31:27 +01:00
46 changed files with 1106 additions and 654 deletions

View File

@@ -288,6 +288,7 @@ 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
@@ -303,6 +304,10 @@ 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.

View File

@@ -53,7 +53,7 @@ dependencies = [
"django-phonenumber-field==7.3.*",
"django-redis==5.4.*",
"django-scopes==2.0.*",
"django-statici18n==2.5.*",
"django-statici18n==2.6.*",
"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.28.*",
"protobuf==5.29.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.22",

View File

@@ -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.11.0.dev0"
__version__ = "2024.12.0.dev0"

View File

@@ -54,6 +54,7 @@ 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
@@ -77,7 +78,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 VAT_ID_COUNTRIES, ask_for_vat_id
from pretix.base.models.tax import ask_for_vat_id
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
)
@@ -276,6 +277,10 @@ class NamePartsFormField(forms.MultiValueField):
return value
def name_parts_is_empty(name_parts_dict):
return not any(k != "_scheme" and v for k, v in name_parts_dict.items())
class WrappedPhonePrefixSelect(Select):
initial = None
@@ -602,6 +607,7 @@ 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)
@@ -676,7 +682,7 @@ class BaseQuestionsForm(forms.Form):
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
add_fields['street'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=self.attendee_addresses_required,
label=_('Address'),
widget=forms.Textarea(attrs={
'rows': 2,
@@ -686,7 +692,7 @@ class BaseQuestionsForm(forms.Form):
initial=(cartpos.street if cartpos else orderpos.street),
)
add_fields['zipcode'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=False,
max_length=30,
label=_('ZIP code'),
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
@@ -695,7 +701,7 @@ class BaseQuestionsForm(forms.Form):
}),
)
add_fields['city'] = forms.CharField(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=False,
label=_('City'),
max_length=255,
initial=(cartpos.city if cartpos else orderpos.city),
@@ -707,11 +713,12 @@ class BaseQuestionsForm(forms.Form):
add_fields['country'] = CountryField(
countries=CachedCountries
).formfield(
required=event.settings.attendee_addresses_required and not self.all_optional,
required=self.attendee_addresses_required,
label=_('Country'),
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
'data-country-information-url': reverse('js_helpers.states'),
}),
)
c = [('', pgettext_lazy('address', 'Select state'))]
@@ -946,9 +953,9 @@ class BaseQuestionsForm(forms.Form):
d = super().clean()
if self.address_validation:
self.cleaned_data = d = validate_address(d, True)
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if d.get('street') 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.'))
@@ -1005,7 +1012,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={
@@ -1021,7 +1028,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', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'internal_reference': forms.TextInput,
}
labels = {
@@ -1055,6 +1062,7 @@ 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 ''
@@ -1083,6 +1091,10 @@ 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()
@@ -1135,16 +1147,19 @@ 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'] = ''
if data.get('is_business') and not ask_for_vat_id(data.get('country')):
data['vat_id'] = ''
if self.event.settings.invoice_address_required:
if self.address_validation and self.event.settings.invoice_address_required and not self.all_optional:
if data.get('is_business') and not data.get('company'):
raise ValidationError(_('You need to provide a company name.'))
if not data.get('is_business') and not data.get('name_parts'):
raise ValidationError({"company": _('You need to provide a company name.')})
if not data.get('is_business') and name_parts_is_empty(data.get('name_parts', {})):
raise ValidationError(_('You need to provide your name.'))
if 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
@@ -1156,7 +1171,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if all(
not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts')
) and len(data.get('name_parts', {})) == 1:
) and name_parts_is_empty(data.get('name_parts', {})):
# Do not save the country if it is the only field set -- we don't know the user even checked it!
self.cleaned_data['country'] = ''

View File

@@ -9,6 +9,7 @@ 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
@@ -25,7 +26,14 @@ def initial_user(apps, schema_editor):
user = User(email='admin@localhost')
user.is_staff = True
user.is_superuser = True
user.password = make_password('admin')
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.save()

View File

@@ -159,10 +159,24 @@ class Membership(models.Model):
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
return f'{self.membership_type.name}: {self.attendee_name} ({ds} {de})'
@property
def percentage_used(self):
if self.membership_type.max_usages and self.usages:
return int(self.usages / self.membership_type.max_usages * 100)
return 0
@property
def attendee_name(self):
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
@property
def expired(self):
return time_machine_now() > self.date_end
@property
def not_yet_valid(self):
return time_machine_now() < self.date_start
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
if valid_from_not_chosen:
return not self.canceled and self.date_end >= time_machine_now()

View File

@@ -3204,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=False)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
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)
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)

View File

@@ -0,0 +1,34 @@
#
# 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/>.
#
from django import template
from django.utils.html import format_html
register = template.Library()
@register.simple_tag
def icon(key, *args, **kwargs):
return format_html(
'<span class="fa fa-{} {}" aria-hidden="true"></span>',
key,
kwargs["class"] if "class" in kwargs else "",
)

View File

@@ -0,0 +1,42 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <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/>.
#
from django import template
from django.utils.html import format_html, mark_safe
register = template.Library()
@register.simple_tag
def textbubble(type, *args, **kwargs):
return format_html(
'<span class="textbubble-{}">{}',
type or "info",
"" if "icon" not in kwargs else format_html(
'<i class="fa fa-{}" aria-hidden="true"></i> ',
kwargs["icon"]
)
)
@register.simple_tag
def endtextbubble():
return mark_safe('</span>')

View File

@@ -22,16 +22,30 @@
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': []})
return JsonResponse({'data': [], **info, })
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)
]})
return JsonResponse({
'data': [
{'name': s.name, 'code': s.code[3:]}
for s in sorted(statelist, key=lambda s: s.name)
],
**info,
})

View File

@@ -61,6 +61,7 @@
<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 }}

View File

@@ -46,11 +46,13 @@
<div id="cp{{ pos.id }}">
<div class="panel-body">
{% for form in forms %}
{% if form.pos.item != pos.item %}
{# Add-Ons #}
<legend>+ {{ form.pos.item }}</legend>
{% endif %}
{% bootstrap_form form layout="control" %}
<div class="profile-scope">
{% if form.pos.item != pos.item %}
{# Add-Ons #}
<legend>+ {{ form.pos.item }}</legend>
{% endif %}
{% bootstrap_form form layout="control" %}
</div>
{% endfor %}
</div>
</div>

View File

@@ -8,16 +8,17 @@ 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: 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"
"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"
"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.0.1\n"
"X-Generator: Weblate 5.8.3\n"
#: pretix/_base_settings.py:79
msgid "English"
@@ -12461,7 +12462,7 @@ msgstr ""
#: pretix/control/forms/checkin.py:176
msgid "Barcode"
msgstr ""
msgstr "Čárový kód"
#: pretix/control/forms/checkin.py:179
msgid "Check-in time"
@@ -12474,6 +12475,8 @@ 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"
@@ -12508,11 +12511,11 @@ msgstr "Časové pásmo akce"
#: pretix/control/forms/event.py:140
msgid "I don't want to specify taxes now"
msgstr ""
msgstr "Přidat podrobnosti o daních později"
#: pretix/control/forms/event.py:141
msgid "You can always configure tax rates later."
msgstr ""
msgstr "Daňové sazby můžete nastavit kdykoliv."
#: pretix/control/forms/event.py:145
msgid "Sales tax rate"

View File

@@ -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-19 15:15+0000\n"
"PO-Revision-Date: 2024-11-27 03:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
@@ -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 "Limita a los productos"
msgstr "Límite 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 introductorio"
msgstr "Texto de introducción"
#: pretix/base/settings.py:1176
msgid "Will be printed on every invoice above the invoice rows."
@@ -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 ""
msgstr "No quiero especificar los impuestos ahora"
#: pretix/control/forms/event.py:141
msgid "You can always configure tax rates later."
msgstr ""
msgstr "Siempre puede configurar los tipos impositivos más adelante."
#: pretix/control/forms/event.py:145
msgid "Sales tax rate"
@@ -12879,6 +12879,8 @@ 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"

View File

@@ -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-19 15:15+0000\n"
"PO-Revision-Date: 2024-11-27 03:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
@@ -416,7 +416,9 @@ 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 "Il y a eu une erreur d'envoi du mail. Veuillez réessayer plus tard."
msgstr ""
"Une erreur s'est produite lors de l'envoi de l'e-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
@@ -1411,7 +1413,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
@@ -1495,7 +1497,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 "Société"
msgstr "Entreprise"
#: pretix/base/exporters/invoices.py:215 pretix/base/exporters/invoices.py:341
msgid "Street address"
@@ -2134,7 +2136,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 "Adresse de facturation"
msgstr "Nom de l'adresse de facturation"
#: pretix/base/exporters/orderlist.py:480
#: pretix/base/exporters/orderlist.py:683 pretix/base/models/orders.py:204
@@ -2233,7 +2235,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 "Adresse mail du participant"
msgstr "E-mail du participant"
#: pretix/base/exporters/orderlist.py:609 pretix/base/models/vouchers.py:312
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:5
@@ -2319,19 +2321,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"
@@ -3587,7 +3589,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 de langue valide."
msgstr "Veuillez saisir un code linguistique valide."
#: pretix/base/modelimport_orders.py:558 pretix/base/modelimport_orders.py:560
msgid "Please enter a valid sales channel."
@@ -3610,8 +3612,8 @@ msgstr "Aucun siège correspondant na été trouvé."
msgid ""
"The seat you selected has already been taken. Please select a different seat."
msgstr ""
"Le siège que vous avez sélectionné a déjà été pris. Veuillez sélectionner un "
"autre siège."
"La place que vous avez choisie est déjà occupée. Veuillez choisir une autre "
"place."
#: pretix/base/modelimport_orders.py:592 pretix/base/services/cart.py:209
msgid "You need to select a specific seat."
@@ -3729,7 +3731,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 "Tag"
msgstr "Balise"
#: pretix/base/modelimport_vouchers.py:334 pretix/base/models/vouchers.py:297
msgid "Shows hidden products that match this voucher"
@@ -3996,7 +3998,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 la société"
msgstr "Nom de l'entreprise"
#: pretix/base/models/customers.py:314 pretix/base/models/orders.py:1538
#: pretix/base/models/orders.py:3211 pretix/base/settings.py:81
@@ -5780,7 +5782,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 "Régionalisation"
msgstr "Langue"
#: pretix/base/models/orders.py:268 pretix/control/forms/filter.py:571
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:57
@@ -6066,7 +6068,7 @@ msgstr "Badge"
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:66
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:113
msgid "Ticket"
msgstr "Ticket"
msgstr "Billet"
#: pretix/base/models/orders.py:3405
msgid "Certificate"
@@ -6472,7 +6474,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/index.html:6
#: pretix/control/templates/pretixcontrol/vouchers/index.html:8
msgid "Vouchers"
msgstr "Bons de réduction"
msgstr "Bons d'achat"
#: pretix/base/models/vouchers.py:339
msgid "You cannot select a quota that belongs to a different event."
@@ -7176,7 +7178,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 participante"
msgstr "Entreprise du participant"
#: pretix/base/pdf.py:178 pretix/base/pdf.py:336
#: pretix/base/services/tickets.py:118 pretix/control/views/pdf.py:111
@@ -7320,7 +7322,7 @@ msgstr "Ville quelconque"
#: pretix/base/pdf.py:335
msgid "Invoice address company"
msgstr "Adresse de la facturation de l'entreprise"
msgstr "Adresse de facturation de l'entreprise"
#: pretix/base/pdf.py:341
msgid "Sesame Street 42"
@@ -7493,7 +7495,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. Dupont"
msgstr "M. Doe"
#: pretix/base/pdf.py:655 pretix/base/pdf.py:662
#: pretix/plugins/badges/exporters.py:501
@@ -12955,11 +12957,11 @@ msgstr "Fuseau horaire de l'événement"
#: pretix/control/forms/event.py:140
msgid "I don't want to specify taxes now"
msgstr ""
msgstr "Je ne veux pas spécifier les taxes maintenant"
#: pretix/control/forms/event.py:141
msgid "You can always configure tax rates later."
msgstr ""
msgstr "Vous pouvez toujours configurer les taux d'imposition ultérieurement."
#: pretix/control/forms/event.py:145
msgid "Sales tax rate"
@@ -13014,6 +13016,9 @@ 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"
@@ -13770,7 +13775,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 de début"
msgstr "Date à partir de"
#: pretix/control/forms/filter.py:1211 pretix/control/forms/filter.py:1214
#: pretix/control/forms/filter.py:1710 pretix/control/forms/filter.py:1713
@@ -16981,7 +16986,7 @@ msgstr "Widget"
#: pretix/control/templates/pretixcontrol/event/payment.html:47
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:12
msgid "Settings"
msgstr "Réglages"
msgstr "Paramètres"
#: pretix/control/navigation.py:164
msgid "Categories"
@@ -17015,7 +17020,7 @@ msgstr "Tous les bons de réduction"
#: pretix/control/navigation.py:284
msgid "Tags"
msgstr "Tags"
msgstr "Balises"
#: pretix/control/navigation.py:296
msgctxt "navigation"
@@ -17851,7 +17856,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 "Filtre"
msgstr "Filtrer"
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:50
msgid "Your search did not match any check-ins."
@@ -17912,7 +17917,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-papiers"
msgstr "Copier dans le presse-papier"
#: pretix/control/templates/pretixcontrol/checkin/index.html:7
#: pretix/control/templates/pretixcontrol/checkin/index.html:11
@@ -21942,7 +21947,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:229
msgid "Contact email"
msgstr "Email de contact"
msgstr "E-mail de contact"
#: pretix/control/templates/pretixcontrol/order/index.html:233
msgid ""
@@ -22204,21 +22209,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 des e-mails"
msgstr "Historique de l'e-mail"
#: 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 email a été envoyé avec une ancienne version de pretix. Nous ne sommes "
"Cet e-mail 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
@@ -22857,7 +22862,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 "Paramétrages dimportation"
msgstr "Importer les paramètres"
#: pretix/control/templates/pretixcontrol/orders/import_process.html:49
#: pretix/control/templates/pretixcontrol/vouchers/import_process.html:49
@@ -22865,13 +22870,13 @@ msgid ""
"The import will be performed regardless of your quotas, so it will be "
"possible to overbook your event using this option."
msgstr ""
"Limportation sera effectuée quels que soient vos quotas, il sera donc "
"possible de surréserver votre événement en utilisant cette option."
"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."
#: pretix/control/templates/pretixcontrol/orders/import_process.html:57
#: pretix/control/templates/pretixcontrol/vouchers/import_process.html:57
msgid "Perform import"
msgstr "Effectuer limportation"
msgstr "Réaliser l'importation"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:10
@@ -22885,15 +22890,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 den-tête. "
"Vous pourrez attribuer les significations 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 définir la signification 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 un fichier"
msgstr "Importer le fichier"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:25
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:25
@@ -22908,7 +22913,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 "Démarrer l'exportation"
msgstr "Lancer l'importation"
#: pretix/control/templates/pretixcontrol/orders/index.html:14
msgid "Nobody ordered a ticket yet."
@@ -25521,7 +25526,7 @@ msgstr "Créer plusieurs bons"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:12
msgid "Voucher codes"
msgstr "Codes de réduction"
msgstr "Codes de bons d'achat"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:17
msgid "Prefix (optional)"
@@ -25700,7 +25705,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"
@@ -26811,9 +26816,7 @@ 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 utilisateur non autorisé à accéder à cette "
"commande."
msgstr "Code de commande inconnu ou non autorisé pour 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."
@@ -29297,7 +29300,7 @@ msgstr "Date de téléchargement"
#: pretix/plugins/checkinlists/exporters.py:767
msgid "Upload time"
msgstr "Temps de téléchargement"
msgstr "Temps de chargement"
#: pretix/plugins/checkinlists/exporters.py:818
msgid "OK"
@@ -33144,7 +33147,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 bon d'achat"
msgstr "Utiliser le bon d'achat"
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:10
msgid "Change summary"

View File

@@ -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-15 20:00+0000\n"
"PO-Revision-Date: 2024-11-27 03:00+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix/"
"hu/>\n"
@@ -7720,8 +7720,7 @@ msgid ""
"You are receiving this email because someone placed an order for {event} for "
"you."
msgstr ""
"Azért küldtük ezt az e-mailt mert valaki rendelt neked jegyet a következő "
"eseményre: {event}"
"Ezt az emailt a következő eseményre való rendelésed kapcsán küldtük: {event}"
#: pretix/base/services/mail.py:282 pretix/base/services/mail.py:298
#, python-brace-format
@@ -9717,7 +9716,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 "
@@ -10257,7 +10256,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 "
@@ -28974,7 +28973,7 @@ msgstr "Összes jegy letöltése (PDF)"
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:66
msgid "Download ticket (PDF)"
msgstr ""
msgstr "Jegy PDF letöltése"
#: pretix/plugins/ticketoutputpdf/views.py:62
msgid "Default ticket layout"
@@ -31108,11 +31107,11 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/position.html:10
msgid "Your registration"
msgstr ""
msgstr "A rendelésed"
#: pretix/presale/templates/pretixpresale/event/position.html:31
msgid "Your items"
msgstr ""
msgstr "A tételeid"
#: pretix/presale/templates/pretixpresale/event/position.html:46
msgid "Additional information"

View File

@@ -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-10-01 22:52+0000\n"
"PO-Revision-Date: 2024-11-28 06:00+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.7.2\n"
"X-Generator: Weblate 5.8.3\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 ""
msgstr "Időzona:"
#: pretix/static/pretixpresale/js/ui/main.js:598
msgid "Your local time:"

View File

@@ -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-18 02:00+0000\n"
"Last-Translator: Damiano <estux@users.noreply.translate.pretix.eu>\n"
"PO-Revision-Date: 2024-11-24 01:00+0000\n"
"Last-Translator: gabriblas <github@unowen.simplelogin.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n"
"Language: it\n"
@@ -4424,6 +4424,9 @@ 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"
@@ -4587,6 +4590,10 @@ 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
@@ -4594,21 +4601,25 @@ 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 ""
msgstr "Categoria normale + cross-selling"
#: pretix/base/models/items.py:116 pretix/control/forms/item.py:107
#, fuzzy
msgid "Cross-selling category"
msgstr ""
msgstr "Categoria cross-selling"
#: pretix/base/models/items.py:124
msgid "Always show in cross-selling step"
msgstr ""
msgstr "Visualizza sempre nello step di cross-selling (vendita aggiuntiva)"
#: 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"
@@ -4616,7 +4627,7 @@ msgstr "Mostra solo se il carrello contiene uno dei seguenti prodotti"
#: pretix/base/models/items.py:129
msgid "Cross-selling condition"
msgstr ""
msgstr "Condizione per il cross-selling (vendita aggiuntiva)"
#: pretix/base/models/items.py:137
#, fuzzy
@@ -7156,7 +7167,7 @@ msgstr ""
#: pretix/base/pdf.py:500
msgid "Seat: row"
msgstr ""
msgstr "Posto: fila"
#: pretix/base/pdf.py:505
msgid "Seat: seat number"
@@ -7568,7 +7579,7 @@ msgstr ""
#: pretix/base/services/cart.py:210
msgid "Please select a valid seat."
msgstr ""
msgstr "Si prega di selezionare un posto a sedere valido."
#: pretix/base/services/cart.py:211
msgid "You can not select a seat for this position."
@@ -8015,7 +8026,7 @@ msgstr ""
#: pretix/base/services/modelimport.py:236
#, python-brace-format
msgid "Invalid data in row {row}: {message}"
msgstr ""
msgstr "Dati invalidi alla linea {row}: {message}"
#: pretix/base/services/modelimport.py:217
msgid "A voucher cannot be created without a code."

View File

@@ -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-10-22 17:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"PO-Revision-Date: 2024-11-21 12:58+0000\n"
"Last-Translator: Ryo <saremba@rami.io>\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.7.2\n"
"X-Generator: Weblate 5.8.3\n"
#: pretix/_base_settings.py:79
msgid "English"
@@ -44,7 +44,6 @@ msgid "Catalan"
msgstr "カタルーニャ語"
#: pretix/_base_settings.py:85
#, fuzzy
msgid "Chinese (simplified)"
msgstr "中国語(簡体字)"
@@ -57,7 +56,6 @@ msgid "Czech"
msgstr "チェコ"
#: pretix/_base_settings.py:88
#, fuzzy
msgid "Danish"
msgstr "デンマーク語"
@@ -123,11 +121,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"
@@ -284,7 +282,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 ""
@@ -378,11 +376,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
@@ -469,7 +467,7 @@ msgstr "外部からの払い戻し"
#: pretix/api/webhooks.py:285
msgid "Refund of payment requested by customer"
msgstr ""
msgstr "お客様から支払いの返金が要求されました"
#: pretix/api/webhooks.py:289
#, fuzzy
@@ -543,7 +541,8 @@ 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
@@ -567,7 +566,7 @@ msgstr "その金額がカードに請求されました。"
#: pretix/api/webhooks.py:370
msgid "Waiting list entry added"
msgstr ""
msgstr "ウェイティングリストにエントリーが追加されました"
#: pretix/api/webhooks.py:374
#, fuzzy
@@ -581,7 +580,7 @@ msgstr "その金額がカードに請求されました。"
#: pretix/api/webhooks.py:382
msgid "Waiting list entry received voucher"
msgstr ""
msgstr "ウェイティングリストのエントリーがバウチャーを受け取りました"
#: pretix/api/webhooks.py:386
#, fuzzy
@@ -608,15 +607,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 ""
msgstr "郵便番号をXXXの形式で入力してください。"
#: pretix/base/addressvalidation.py:222 pretix/base/addressvalidation.py:224
msgid "Enter a postal code in the format XXXX."
msgstr ""
msgstr "郵便番号をXXXXの形式で入力してください。"
#: pretix/base/auth.py:146
#, python-brace-format
@@ -658,7 +657,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
@@ -666,7 +665,8 @@ 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] ""
msgstr[0] "パスワードは、過去%(history_length)s件のいずれかのパスワードと同じにすること"
"はできません。"
#: pretix/base/channels.py:168
msgid "Online shop"
@@ -674,13 +674,14 @@ msgstr "オンラインショップ"
#: pretix/base/channels.py:174
msgid "API"
msgstr ""
msgstr "API"
#: pretix/base/channels.py:175
msgid ""
"API sales channels come with no built-in functionality, but may be used for "
"custom integrations."
msgstr ""
msgstr "API販売チャネルには組み込みの機能はありませんが、カスタムインテグレーションで"
"使用することができます。"
#: pretix/base/context.py:45
#, python-brace-format
@@ -699,25 +700,25 @@ msgstr "ソースコード"
#: pretix/base/customersso/oidc.py:61
#, python-brace-format
msgid "Configuration option \"{name}\" is missing."
msgstr ""
msgstr "設定オプション\"{name}\"がありません。"
#: 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 ""
msgstr "\"{url}\"から設定を取得できませんでした。エラーメッセージ:\"{error}\"。"
#: 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 ""
msgstr "互換性のないSSOプロバイダー: \"{error}\"。"
#: pretix/base/customersso/oidc.py:111
#, python-brace-format
msgid "You are not requesting \"{scope}\"."
msgstr ""
msgstr "\"{scope}\" をリクエストしていません。"
#: pretix/base/customersso/oidc.py:117
#, python-brace-format

View File

@@ -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)
return prev.get_step_url(request) + '?dir=prev'
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_next_url(request))
return redirect(self.get_prev_url(request) if request.GET.get('dir') == 'prev' else 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_attendees_required', as_type=bool) \
and (cp.street is None or cp.city is None or cp.country is None):
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 warn:
messages.warning(request, _('Please fill in answers to all required questions.'))
return False

View File

@@ -133,6 +133,7 @@ class InvoiceAddressForm(BaseInvoiceAddressForm):
class InvoiceNameForm(InvoiceAddressForm):
address_validation = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@@ -1,31 +1,29 @@
{% load i18n %}
{% load bootstrap3 %}
{% load textbubble %}
{# Changes should be replicated in pretixcontrol/orders/fragment_order_status.html and in pretix/base/models/orders.py #}
{% if order.status == "n" %}
{% if order.require_approval %}
{% trans "Approval pending" %}
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Approval pending" %}{% endtextbubble %}
{% elif order.total == 0 %}
{% trans "Confirmation pending" context "order state" %}
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Confirmation pending" context "order state" %}{% endtextbubble %}
{% elif event.settings.payment_pending_hidden %}
{# intentionally left blank #}
{% elif order.valid_if_pending %}
{% trans "Confirmed" context "order state" %}
{% textbubble "info" icon="info-circle" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
{% else %}
{% trans "Payment pending" %}
{% endif %}
{% if not event.settings.payment_pending_hidden %}
<i class="status-dot fa fa-circle {% if order.valid_if_pending %}text-info{% else %}text-warning{% endif %}" aria-hidden="true"></i>
{% textbubble "warning" icon="exclamation-triangle" %}{% trans "Payment pending" %}{% endtextbubble %}
{% endif %}
{% elif order.status == "p" %}
{% if order.count_positions == 0 %}
{% trans "Canceled (paid fee)" %} <i class="status-dot fa fa-info-circle text-info" aria-hidden="true"></i>
{% textbubble "info" icon="info-circle" %}{% trans "Canceled (paid fee)" %}{% endtextbubble %}
{% elif order.total == 0 %}
{% trans "Confirmed" context "order state" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
{% textbubble "success" icon="check" %}{% trans "Confirmed" context "order state" %}{% endtextbubble %}
{% else %}
{% trans "Paid" %} <i class="status-dot fa fa-check-circle text-success" aria-hidden="true"></i>
{% textbubble "success" icon="check" %}{% trans "Paid" %}{% endtextbubble %}
{% endif %}
{% elif order.status == "e" %}
{% trans "Expired" %} <i class="status-dot fa fa-minus-circle text-danger" aria-hidden="true"></i>
{% textbubble "danger" icon="minus" %}{% trans "Expired" %}{% endtextbubble %}
{% elif order.status == "c" %}
{% trans "Canceled" %} <i class="status-dot fa fa-times-circle text-danger" aria-hidden="true"></i>
{% textbubble "danger" icon="times" %}{% trans "Canceled" %}{% endtextbubble %}
{% endif %}

View File

@@ -20,4 +20,5 @@
<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 %}

View File

@@ -4,9 +4,9 @@
{% if request.organizer.settings.customer_accounts %}
<nav class="loginstatus" aria-label="{% trans "customer account" %}">
{% if request.customer %}
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}"
<a href="{% abseventurl request.organizer "presale:organizer.customer.index" %}"
aria-label="{% trans "View customer account" %}" data-placement="bottom"
title="{% trans "View user profile" %}" data-toggle="tooltip">
title="{% trans "View customer account" %}" data-toggle="tooltip">
<span class="fa fa-user" aria-hidden="true"></span>
{{ request.customer.name|default:request.customer.email }}</a>
<a href="{% if request.event_domain %}{% abseventurl request.event "presale:organizer.customer.logout" %}{% else %}{% abseventurl request.organizer "presale:organizer.customer.logout" %}{% endif %}?next={{ request.path|urlencode }}%3F{{ request.META.QUERY_STRING|urlencode }}"

View File

@@ -1,33 +1,39 @@
{% extends "pretixpresale/organizers/base.html" %}
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Delete address" %}{% endblock %}
{% block content %}
<h2>
{% trans "Delete address" %}
</h2>
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "address-card-o" %} <b>{% trans "Delete address" %}</b>
</h3>
</div>
<div class="panel-body account-addresses">
<p>
{% trans "Do you really want to delete the following address from your account?" %}
</p>
<address>
{{ address.describe|linebreaksbr }}
</address>
</div>
</div>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following address from your account?" %}
</p>
<address>
{{ address.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
href="{% abseventurl request.organizer "presale:organizer.customer.addresses" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% icon "trash" %}
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Addresses" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "address-card-o" %}
<b>{% trans "Addresses" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if invoice_addresses %}
<ul class="full-width-list alternating-rows account-addresses">
<li class="row">
{% for ia in invoice_addresses %}
{% if forloop.counter0 and forloop.counter0|divisibleby:4 %}
</li>
<li class="row">
{% endif %}
<div class="col-md-3 col-xs-12">
<address>{{ ia.describe|linebreaksbr }}</address>
<p class="blank-after">
<a href="{% abseventurl request.organizer "presale:organizer.customer.address.delete" id=ia.id %}"
class="btn btn-danger btn-sm">
{% icon "trash" %}
{% trans "Delete" %}
</a>
</p>
</div>
{% endfor %}
</li>
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any addresses in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -0,0 +1,55 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block content %}
<h2>
{% trans "Your account" %}
</h2>
<div class="blank-after">
<dl class="row">
<div class="col-sm-6">
<dt>{{ customer.name }}</dt>
<dd>{{ customer.email }}</dd>
{% if customer.phone %}
<dd>{% icon "phone" %} {{ customer.phone }}</dd>
{% endif %}
<dd>
<ul class="list-inline">
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.change" %}">
{% icon "edit" %}
{% trans "Change account information" %}
</a>
</li>
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.password" %}">
{% icon "key" %}
{% trans "Change password" %}
</a>
</li>
</ul>
</dd>
<dd>
</dd>
</div>
<div class="col-sm-6 text-right">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
</div>
</dl>
<nav class="subnav row" aria-label="{% trans "customer account information" %}">
<ul class="list-inline blank-after col-xs-12">
{% for nav in sub_nav %}
<li>
<a href="{{ nav.url }}"{% if nav.active %} class="active"{% endif %}>
{% icon nav.icon %}{{ nav.label }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
{% block inner %}
{% endblock %}
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load urlreplace %}
{% load bootstrap3 %}
{% block title %}{% trans "Log in" %}{% endblock %}
{% block content %}
@@ -18,6 +18,7 @@
{% bootstrap_form form %}
<div class="form-group buttons">
<button type="submit" class="btn btn-primary btn-lg btn-block">
{% icon "sign-in" %}
{% trans "Log in" %}
</button>
</div>
@@ -35,6 +36,7 @@
<div class="col-md-6">
<a class="btn btn-link btn-block"
href="{% eventurl request.organizer "presale:organizer.customer.register" %}">
{% icon "address-book-o" %}
{% trans "Create account" %}
</a>
</div>

View File

@@ -1,96 +1,127 @@
{% extends "pretixpresale/organizers/base.html" %}
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% load textbubble %}
{% block title %}{% trans "Your membership" %}{% endblock %}
{% block content %}
<h2>
{% trans "Your membership" %}
</h2>
<div class="panel panel-primary items">
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Details" %}
{% if membership.membership_type.transferable %}
{% icon "users" %}
{% else %}
{% icon "id-badge" %}
{% endif %}
<b>{% trans "Your membership" %}</b>
{% if membership.testmode %}
<span class="h6">
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</span>
{% endif %}
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Membership type" %}</dt>
<dd>{{ membership.membership_type.name }}</dd>
<dd>{% if membership.canceled %}<del>{% endif %}
{{ membership.membership_type.name }}
{% if membership.canceled %}</del>
<small>
{% textbubble "danger" icon="times" %}
{% trans "Canceled" %}
{% endtextbubble %}
</small>
{% endif %}
<br><small class="text-muted">
{% if membership.membership_type.transferable %}
({% trans "transferable" %})
{% else %}
({% trans "not transferable" %})
{% endif %}
</small>
</dd>
<dt>{% trans "Valid from" %}</dt>
<dd>{{ membership.date_start|date:"SHORT_DATETIME_FORMAT" }}
<dt>{% trans "Valid until" %}</dt>
<dd>{{ membership.date_end|date:"SHORT_DATETIME_FORMAT" }}
<dt>{% trans "Attendee name" %}</dt>
<dd>{{ membership.attendee_name }}
<dd>{{ membership.attendee_name|default_if_none:"" }}
<dt>{% trans "Maximum usages" %}</dt>
<dd>{{ membership.membership_type.max_usages|default_if_none:"" }}</dd>
</dl>
</div>
</div>
<div class="panel panel-default items">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Usages" %}
</h3>
</div>
<table class="panel-body table table-hover">
<caption class="sr-only">{% trans "Usages" %}</caption>
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "Event" %}</th>
<th>{% trans "Product" %}</th>
<th>{% trans "Order date" %}</th>
<th class="text-right">{% trans "Status" %}</th>
<th class="text-right"></th>
</tr>
</thead>
<tbody>
<div class="panel-body">
{% if usages %}
<ul class="full-width-list alternating-rows">
{% for op in usages %}
<tr>
<td>
<strong>
{{ op.order.code }}-{{ op.positionid }}
</strong>
{% if op.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ op.order.event }}
{% if op.subevent %}
<br>
{{ op.subevent|default:"" }}
{% endif %}
</td>
<td>
{{ op.item.name }}
{% if op.variation %} {{ op.variation }}{% endif %}
</td>
<td>
{{ op.order.datetime|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td class="text-right flip">
{% if op.canceled %}
{% trans "Canceled" %} <i class="{{ class }} fa fa-times-circle text-danger" aria-hidden="true"></i>
{% else %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=op.order %}
{% endif %}
</td>
<td class="text-right flip">
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}"
target="_blank"
class="btn btn-default">
{% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}
<li class="row">
<dl>
<div class="col-md-4 col-sm-5 col-xs-12">
<dt class="sr-only">{% trans "Order" %}</dt>
<dd><strong>
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}" target="_blank">
{% icon "shopping-cart" %}
{{ op.order.code }}-{{ op.positionid }}
</a>
</strong>
<small>{% include "pretixpresale/event/fragment_order_status.html" with order=op.order event=op.order.event %}</small>
</dd>
<dd><time datetime="{{ op.order.datetime|date:"Y-m-d H:i" }}" class="text-muted small">{{ op.order.datetime|date:"SHORT_DATETIME_FORMAT" }}</time></dd>
{% if op.order.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-md-6 col-sm-5 col-xs-8">
<dt class="sr-only">{% trans "Product" %}</dt>
<dd>{{ op.item.name }}
{% if op.variation %} - {{ op.variation }}{% endif %}
</dd>
<dt class="sr-only">{% trans "Event" %}</dt>
<dd>
<small class="text-muted">
{{ op.order.event }}
{% if op.subevent %}
<br>{{ op.subevent }}
{% endif %}
{% if not op.order.event.has_subevents and op.order.event.settings.show_dates_on_frontpage %}
<br>{{ op.order.event.get_date_range_display }}
{% endif %}
</small>
</dd>
</div>
<div class="col-sm-2 col-xs-4">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl op.order.event "presale:event.order" order=op.order.code secret=op.order.secret %}"
target="_blank">
{% icon "list-ul" %}
{% trans "Details" %}
</a></dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You havent used this membership yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -0,0 +1,111 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load textbubble %}
{% block title %}{% trans "Memberships" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "id-badge" %}
<b>{% trans "Memberships" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if memberships %}
<ul class="full-width-list alternating-rows">
{% for m in memberships %}
<li class="row">
<dl>
<div class="col-xs-5">
<dt>
{% if m.canceled %}<del>{% endif %}
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}">
{{ m.membership_type.name }}
</a>
{% if m.canceled %}</del>{% endif %}
{% if m.membership_type.transferable %}
<span class="text-muted" data-toggle="tooltip" title="{% trans "Membership is transferable" %}">
{% icon "users" %}
</span>
{% endif %}
</dt>
{% if m.attendee_name %}
<dd class="text-muted">
{% icon "id-badge" %}
<span class="sr-only">{% trans "Attendee name" %}:</span>
{{ m.attendee_name }}
</dd>
{% endif %}
<dd class="text-muted">
<small>
{% if m.canceled %}
{% textbubble "danger" icon="times" %}
{% trans "Canceled" %}
{% endtextbubble %}
{% elif m.expired %}
{% icon "minus-square-o" %}
{% trans "Expired since" %}
<time datetime="{{ m.date_end|date:"Y-m-d H:i" }}">
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% elif m.not_yet_valid %}
{% icon "clock-o" %}
{% trans "Valid from" %}
<time datetime="{{ m.date_start|date:"Y-m-d H:i" }}">
{{ m.date_start|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% else %}
{% icon "check" %}
{% trans "Valid until" %}
<time datetime="{{ m.date_end|date:"Y-m-d H:i" }}">
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</time>
{% endif %}
</small>
</dd>
{% if m.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-xs-5">
<dd>
<div class="quotabox full-width">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ m.percentage_used }}">
</div>
</div>
<div class="numbers">
{{ m.usages }} /
{{ m.membership_type.max_usages|default_if_none:"∞" }}
</div>
</div>
</dd>
</div>
<div class="col-xs-2">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}">
{% icon "list-ul" %}
{% trans "Details" %}
</a>
</dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any memberships in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -0,0 +1,83 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% load money %}
{% load textbubble %}
{% block title %}{% trans "Your account" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "shopping-cart" %}
<b>{% trans "Orders" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if orders %}
<ul class="full-width-list alternating-rows">
{% for o in orders %}
<li class="row">
<dl>
<div class="col-md-4 col-sm-5 col-xs-8">
<dt class="sr-only">{% trans "Order" %}</dt>
<dd><strong>
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}" target="_blank">
{% icon "shopping-cart" %}
{{ o.code }}</a>
</strong>
{% if o.customer_id != customer.pk %}
<span class="text-muted" data-toggle="tooltip"
title="{% trans "Matched to the account based on the email address." %}">
{% icon "compress" %}
</span>
{% endif %}
<small>{% include "pretixpresale/event/fragment_order_status.html" with order=o event=o.event %}</small>
</dd>
<dd><time datetime="{{ o.datetime|date:"Y-m-d H:i" }}" class="text-muted small">{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</time></dd>
{% if o.testmode %}
<dd>
<small>
{% textbubble "warning" %}
{% trans "TEST MODE" %}
{% endtextbubble %}
</small>
</dd>
{% endif %}
</div>
<div class="col-md-2 col-sm-2 col-xs-4 text-right">
<dt class="sr-only">{% trans "Order total" %}</dt>
<dd>{{ o.total|money:o.event.currency }}</dd>
<dt class="sr-only">{% trans "Positions" %}</dt>
<dd class="text-muted"><small>{% blocktranslate count counter=o.count_positions|default_if_none:0 %}{{ counter }} item{% plural %}{{ counter }} items{% endblocktranslate %}</small>
</dd>
</div>
<div class="col-md-4 col-sm-3 col-xs-8">
<dt class="sr-only">{% trans "Event" %}</dt>
<dd>
{{ o.event }}
{% if not o.event.has_subevents and o.event.settings.show_dates_on_frontpage %}
<br><small class="text-muted">{{ o.event.get_date_range_display }}</small>
{% endif %}
</dd>
</div>
<div class="col-sm-2 col-xs-4">
<dt class="sr-only">{% trans "Actions" %}</dt>
<dd class="text-right">
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}"
target="_blank">
{% icon "list-ul" %}
{% trans "Details" %}
</a></dd>
</div>
</dl>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-center">{% trans "You dont have any orders in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -1,253 +0,0 @@
{% extends "pretixpresale/organizers/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Your account" %}{% endblock %}
{% block content %}
<h2>
{% trans "Your account" %}
</h2>
<div class="panel panel-primary items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Account information" %}
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Customer ID" %}</dt>
<dd>#{{ customer.identifier }}</dd>
{% if customer.provider %}
<dt>{% trans "Login method" %}</dt>
<dd>{{ customer.provider.name }}</dd>
{% endif %}
<dt>{% trans "Email" %}</dt>
<dd>{{ customer.email }}
</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
{% if customer.phone %}
<dt>{% trans "Phone" %}</dt>
<dd>{{ customer.phone }}</dd>
{% endif %}
</dl>
<div class="text-right">
<a href="{% eventurl request.organizer "presale:organizer.customer.change" %}"
class="btn btn-default">
{% trans "Change account information" %}
</a>
{% if not customer.provider %}
<a href="{% eventurl request.organizer "presale:organizer.customer.password" %}"
class="btn btn-default">
{% trans "Change password" %}
</a>
{% endif %}
</div>
</div>
</div>
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#orders" aria-controls="orders" role="tab" data-toggle="tab">{% trans "Orders" %}</a>
</li>
<li role="presentation">
<a href="#memberships" aria-controls="memberships" role="tab" data-toggle="tab">{% trans "Memberships" %}</a>
</li>
<li role="presentation">
<a href="#addresses" aria-controls="addresses" role="tab" data-toggle="tab">{% trans "Addresses" %}</a>
</li>
<li role="presentation">
<a href="#profiles" aria-controls="profiles" role="tab" data-toggle="tab">{% trans "Attendee profiles" %}</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="orders">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "Event" %}</th>
<th>{% trans "Order date" %}</th>
<th class="text-right">{% trans "Order total" %}</th>
<th class="text-right">{% trans "Positions" %}</th>
<th class="text-right">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for o in orders %}
<tr>
<td>
<strong>
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}" target="_blank">
{{ o.code }}
</a>
</strong>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ o.event }}
{% if not o.event.has_subevents and o.event.settings.show_dates_on_frontpage %}
<br><small class="text-muted">{{ o.event.get_date_range_display }}</small>
{% endif %}
</td>
<td>
{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if o.customer_id != customer.pk %}
<span class="fa fa-link text-muted"
data-toggle="tooltip"
title="{% trans "Matched to the account based on the email address." %}"
></span>
{% endif %}
</td>
<td class="text-right flip">
{{ o.total|money:o.event.currency }}
</td>
<td class="text-right flip">{{ o.count_positions|default_if_none:"0" }}</td>
<td class="text-right flip">{% include "pretixpresale/event/fragment_order_status.html" with order=o event=o.event %}</td>
<td class="text-right flip">
<a href="{% abseventurl o.event "presale:event.order" order=o.code secret=o.secret %}"
target="_blank"
class="btn btn-default">
{% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}
</div>
<div role="tabpanel" class="tab-pane" id="memberships">
<table class="panel-body table table-hover">
<caption class="sr-only">{% trans "Memberships" %}</caption>
<thead>
<tr>
<th>{% trans "Membership type" %}</th>
<th>{% trans "Valid from" %}</th>
<th>{% trans "Valid until" %}</th>
<th>{% trans "Attendee name" %}</th>
<th>{% trans "Usages" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for m in memberships %}
<tr>
<td>
{% if m.canceled %}<del>{% endif %}
{{ m.membership_type.name }}
{% if m.canceled %}</del>{% endif %}
{% if m.testmode %}<span class="label label-warning">{% trans "TEST MODE" %}</span>{% endif %}
</td>
<td>
{{ m.date_start|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td>
{{ m.date_end|date:"SHORT_DATETIME_FORMAT" }}
</td>
<td>
{{ m.attendee_name }}
</td>
<td>
<div class="quotabox">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ m.percent }}">
</div>
</div>
<div class="numbers">
{{ m.usages }} /
{{ m.membership_type.max_usages|default_if_none:"∞" }}
</div>
</div>
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.membership" id=m.id %}"
data-toggle="tooltip"
title="{% trans "Details" %}"
class="btn btn-default">
<i class="fa fa-list"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No memberships are stored in your account." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="addresses">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Address" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ia in invoice_addresses %}
<tr>
<td>
{{ ia.describe|linebreaksbr }}
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.address.delete" id=ia.id %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center">
{% trans "No addresses are stored in your account." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="profiles">
<table class="panel-body table table-hover">
<thead>
<tr>
<th>{% trans "Profile" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ap in customer.attendee_profiles.all %}
<tr>
<td>
{{ ap.describe|linebreaksbr }}
</td>
<td class="text-right flip">
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile.delete" id=ap.id %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center">
{% trans "No attendee profiles are stored in your account." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,33 +1,39 @@
{% extends "pretixpresale/organizers/base.html" %}
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Delete profile" %}{% endblock %}
{% block content %}
<h2>
{% trans "Delete profile" %}
</h2>
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "user" %} <b>{% trans "Delete profile" %}</b>
</h3>
</div>
<div class="panel-body">
<p>
{% trans "Do you really want to delete the following profile from your account?" %}
</p>
<p>
{{ profile.describe|linebreaksbr }}
</p>
</div>
</div>
<form method="post">
{% csrf_token %}
<p>
{% trans "Do you really want to delete the following profile from your account?" %}
</p>
<address>
{{ profile.describe|linebreaksbr }}
</address>
<div class="row">
<div class="col-md-4 col-sm-6">
<a class="btn btn-block btn-default btn-lg"
href="{% abseventurl request.organizer "presale:organizer.customer.profile" %}">
href="{% abseventurl request.organizer "presale:organizer.customer.profiles" %}">
{% trans "Go back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4 col-sm-6">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% icon "trash" %}
{% trans "Delete" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "pretixpresale/organizers/customer_base.html" %}
{% load i18n %}
{% load icon %}
{% load eventurl %}
{% block title %}{% trans "Attendee profiles" %}{% endblock %}
{% block inner %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% icon "users" %}
<b>{% trans "Attendee profiles" %}</b> ({{ page_obj.paginator.count }})
</h3>
</div>
<div class="panel-body">
{% if attendee_profiles %}
<ol class="full-width-list alternating-rows">
<li class="row">
{% for ap in attendee_profiles %}
{% if forloop.counter0 and forloop.counter0|divisibleby:4 %}
</li>
<li class="row">
{% endif %}
<div class="col-md-3 col-xs-12">
<p>{{ ap.describe|linebreaksbr }}</p>
<p class="blank-after">
<a href="{% abseventurl request.organizer "presale:organizer.customer.profile.delete" id=ap.id %}"
class="btn btn-danger btn-sm">
{% icon "trash" %}
{% trans "Delete" %}
</a>
</p>
</div>
{% endfor %}
</li>
</ol>
{% else %}
<p class="text-center">{% trans "You dont have any attendee profiles in your account yet." %}</p>
{% endif %}
</div>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}

View File

@@ -210,10 +210,13 @@ organizer_patterns = [
re_path(r'^account/password$', pretix.presale.views.customer.ChangePasswordView.as_view(), name='organizer.customer.password'),
re_path(r'^account/change$', pretix.presale.views.customer.ChangeInformationView.as_view(), name='organizer.customer.change'),
re_path(r'^account/confirmchange$', pretix.presale.views.customer.ConfirmChangeView.as_view(), name='organizer.customer.change.confirm'),
re_path(r'^account/membership/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
re_path(r'^account/memberships$', pretix.presale.views.customer.MembershipView.as_view(), name='organizer.customer.memberships'),
re_path(r'^account/memberships/(?P<id>\d+)/$', pretix.presale.views.customer.MembershipUsageView.as_view(), name='organizer.customer.membership'),
re_path(r'^account/addresses$', pretix.presale.views.customer.AddressView.as_view(), name='organizer.customer.addresses'),
re_path(r'^account/addresses/(?P<id>\d+)/delete$', pretix.presale.views.customer.AddressDeleteView.as_view(), name='organizer.customer.address.delete'),
re_path(r'^account/profiles$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profiles'),
re_path(r'^account/profiles/(?P<id>\d+)/delete$', pretix.presale.views.customer.ProfileDeleteView.as_view(), name='organizer.customer.profile.delete'),
re_path(r'^account/$', pretix.presale.views.customer.ProfileView.as_view(), name='organizer.customer.profile'),
re_path(r'^account/$', pretix.presale.views.customer.OrderView.as_view(), name='organizer.customer.index'),
re_path(r'^oauth2/v1/authorize$', pretix.presale.views.oidc_op.AuthorizeView.as_view(),
name='organizer.oauth2.v1.authorize'),

View File

@@ -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)
step.c_resolved_url = step.get_step_url(request) + '?dir=prev'
raise Http404()
def redirect(self, url):

View File

@@ -134,7 +134,7 @@ class LoginView(RedirectBackMixin, FormView):
url = self.get_redirect_url()
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
if self.request.GET.get("request_cross_domain_customer_auth") == "true":
otpstore = SessionStore()
@@ -350,8 +350,42 @@ class CustomerRequiredMixin:
return super().dispatch(request, *args, **kwargs)
class ProfileView(CustomerRequiredMixin, ListView):
template_name = 'pretixpresale/organizers/customer_profile.html'
class CustomerAccountBaseMixin(CustomerRequiredMixin):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['customer'] = self.request.customer
url_name = self.request.resolver_match.url_name
ctx['sub_nav'] = [
{
'label': _('Orders'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={}),
'active': url_name == 'organizer.customer.index',
'icon': 'shopping-cart',
},
{
'label': _('Memberships'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.memberships', kwargs={}),
'active': url_name.startswith('organizer.customer.membership'),
'icon': 'id-badge',
},
{
'label': _('Addresses'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.addresses', kwargs={}),
'active': url_name.startswith('organizer.customer.address'),
'icon': 'address-card-o',
},
{
'label': _('Attendee profiles'),
'url': eventreverse(self.request.organizer, 'presale:organizer.customer.profiles', kwargs={}),
'active': url_name.startswith('organizer.customer.profile'),
'icon': 'user',
},
]
return ctx
class OrderView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_orders.html'
context_object_name = 'orders'
paginate_by = 20
@@ -369,18 +403,6 @@ class ProfileView(CustomerRequiredMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['customer'] = self.request.customer
ctx['memberships'] = self.request.customer.memberships.with_usages().select_related(
'membership_type', 'granted_in', 'granted_in__order', 'granted_in__order__event'
)
ctx['invoice_addresses'] = InvoiceAddress.profiles.filter(customer=self.request.customer)
ctx['is_paginated'] = True
for m in ctx['memberships']:
if m.membership_type.max_usages:
m.percent = int(m.usages / m.membership_type.max_usages * 100)
else:
m.percent = 0
s = OrderPosition.objects.filter(
order=OuterRef('pk')
@@ -404,7 +426,18 @@ class ProfileView(CustomerRequiredMixin, ListView):
return ctx
class MembershipUsageView(CustomerRequiredMixin, ListView):
class MembershipView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_memberships.html'
context_object_name = 'memberships'
paginate_by = 20
def get_queryset(self):
return self.request.customer.memberships.with_usages().select_related(
'membership_type', 'granted_in', 'granted_in__order', 'granted_in__order__event'
)
class MembershipUsageView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_membership.html'
context_object_name = 'usages'
paginate_by = 20
@@ -428,7 +461,16 @@ class MembershipUsageView(CustomerRequiredMixin, ListView):
return ctx
class AddressDeleteView(CustomerRequiredMixin, CompatDeleteView):
class AddressView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_addresses.html'
context_object_name = 'invoice_addresses'
paginate_by = 20
def get_queryset(self):
return InvoiceAddress.profiles.filter(customer=self.request.customer)
class AddressDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
template_name = 'pretixpresale/organizers/customer_address_delete.html'
context_object_name = 'address'
@@ -436,10 +478,19 @@ class AddressDeleteView(CustomerRequiredMixin, CompatDeleteView):
return get_object_or_404(InvoiceAddress.profiles, customer=self.request.customer, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.addresses', kwargs={})
class ProfileDeleteView(CustomerRequiredMixin, CompatDeleteView):
class ProfileView(CustomerAccountBaseMixin, ListView):
template_name = 'pretixpresale/organizers/customer_profiles.html'
context_object_name = 'attendee_profiles'
paginate_by = 20
def get_queryset(self):
return self.request.customer.attendee_profiles.all()
class ProfileDeleteView(CustomerAccountBaseMixin, CompatDeleteView):
template_name = 'pretixpresale/organizers/customer_profile_delete.html'
context_object_name = 'profile'
@@ -447,10 +498,10 @@ class ProfileDeleteView(CustomerRequiredMixin, CompatDeleteView):
return get_object_or_404(self.request.customer.attendee_profiles, pk=self.kwargs.get('id'))
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.profiles', kwargs={})
class ChangePasswordView(CustomerRequiredMixin, FormView):
class ChangePasswordView(CustomerAccountBaseMixin, FormView):
template_name = 'pretixpresale/organizers/customer_password.html'
form_class = ChangePasswordForm
@@ -465,7 +516,7 @@ class ChangePasswordView(CustomerRequiredMixin, FormView):
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
@transaction.atomic()
def form_valid(self, form):
@@ -483,7 +534,7 @@ class ChangePasswordView(CustomerRequiredMixin, FormView):
return kwargs
class ChangeInformationView(CustomerRequiredMixin, FormView):
class ChangeInformationView(CustomerAccountBaseMixin, FormView):
template_name = 'pretixpresale/organizers/customer_info.html'
form_class = ChangeInfoForm
@@ -498,7 +549,7 @@ class ChangeInformationView(CustomerRequiredMixin, FormView):
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
def form_valid(self, form):
if form.cleaned_data['email'] != self.initial_email and not self.request.customer.provider:
@@ -581,7 +632,7 @@ class ConfirmChangeView(View):
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
class SSOLoginView(RedirectBackMixin, View):
@@ -641,7 +692,7 @@ class SSOLoginView(RedirectBackMixin, View):
url = self.get_redirect_url()
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
return url
@@ -864,7 +915,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
url = self.get_redirect_url(redirect_to)
if not url:
return eventreverse(self.request.organizer, 'presale:organizer.customer.profile', kwargs={})
return eventreverse(self.request.organizer, 'presale:organizer.customer.index', kwargs={})
else:
if self.request.session.get(f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'):
otpstore = SessionStore()

View File

@@ -726,7 +726,11 @@ 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",
*(
["django.contrib.auth.hashers.Argon2PasswordHasher"]
if config.getboolean('django', 'passwords_argon2', fallback=True)
else []
),
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",

View File

@@ -0,0 +1,64 @@
$(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);
});
});

View File

@@ -8,7 +8,7 @@ $gray-lightest: lighten(#000, 97.25%);
$font-family-sans-serif: var(--pretix-font-family-sans-serif);
$text-color: #222222 !default;
$text-muted: #767676 !default;
$text-muted: #737373 !default;
$input-color-placeholder: lighten(#000, 70%) !default;
$border-radius-base: var(--pretix-border-radius-base);

View File

@@ -434,60 +434,6 @@ 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;

View File

@@ -517,65 +517,6 @@ $(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()

View File

@@ -30,9 +30,6 @@ $body-bg: #f5f5f5 !default;
float: left;
margin-right: .25em;
}
.status-dot {
font-size: 1.2em;
}
.text-warning { color: $brand-warning; }
.text-info { color: $brand-info; }
.text-success { color: $brand-success; }
@@ -98,6 +95,9 @@ footer nav .btn-link {
vertical-align: baseline;
}
}
.subnav a.active {
font-weight: bold;
}
.locales ul {
display: inline;
list-style: none;
@@ -499,6 +499,9 @@ h2 .label {
&.availability .progress-bar-success {
background: var(--pretix-brand-success-lighten-20);
}
&.full-width {
width: 100%;
}
}
.nav-tabs {
@@ -527,6 +530,76 @@ h2 .label {
font-size: 30px;
}
.textbubble-success, .textbubble-info, .textbubble-warning, .textbubble-danger {
padding: 0 .4em;
border-radius: $border-radius-base;
font-weight: bold;
white-space: nowrap;
&:has(>.fa:first-child) {
padding-left: .25em;
}
}
.textbubble-success {
color: var(--pretix-brand-success-shade-42);
background: var(--pretix-brand-success-tint-85);
.fa {
color: var(--pretix-brand-success);
}
}
.textbubble-info {
color: var(--pretix-brand-info-shade-42);
background: var(--pretix-brand-info-tint-85);
.fa {
color: var(--pretix-brand-info);
}
}
.textbubble-warning {
color: var(--pretix-brand-warning-shade-42);
background: var(--pretix-brand-warning-tint-85);
.fa {
color: var(--pretix-brand-warning);
}
}
.textbubble-danger {
color: var(--pretix-brand-danger-shade-42);
background: var(--pretix-brand-danger-tint-85);
.fa {
color: var(--pretix-brand-danger);
}
}
.full-width-list {
list-style: none;
margin: 0;
padding: 0;
li {
padding: 15px 0;
}
li+li {
border-top: 1px solid $table-border-color;
}
}
.panel-body:has(>.full-width-list:first-child) {
padding-top: 0;
}
.panel-body:has(>.full-width-list:last-child) {
padding-bottom: 0;
}
.alternating-rows .row:nth-child(even) {
background-color: $table-bg-accent;
}
.account-addresses address::first-line {
font-weight: bold;
}
@import "_iframe.scss";
@import "_a11y.scss";
@import "_print.scss";

View File

@@ -0,0 +1,32 @@
#
# 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/>.
#
from pretix.base.forms.questions import name_parts_is_empty
def test_name_parts_is_empty():
assert name_parts_is_empty({}) is True
assert name_parts_is_empty({"_scheme": "foo"}) is True
assert name_parts_is_empty({"_scheme": "foo", "full_name": ""}) is True
assert name_parts_is_empty({"full_name": None}) is True
assert name_parts_is_empty({"full_name": "Flora Nord"}) is False
assert name_parts_is_empty({"_scheme": "foo", "given_name": "Alice"}) is False
assert name_parts_is_empty({"_legacy": "Alice"}) is False

View File

@@ -1207,6 +1207,65 @@ 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

View File

@@ -433,7 +433,7 @@ def test_org_sso_login_new_customer_email_conflict(env, client, provider):
@pytest.mark.django_db
@pytest.mark.parametrize("url", [
"account/change",
"account/membership/1/",
"account/memberships/1/",
"account/",
])
def test_login_required(client, env, url):