Compare commits

..

43 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
Richard Schreiber
391eda25da [A11y] Improve color combinations for alerts 2024-11-21 13:58:19 +01:00
Raphael Michel
fcff5a522d Fix inconsistent labels 2024-11-19 16:36:09 +01:00
Raphael Michel
7e93d38a01 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-19 16:33:52 +01:00
Raphael Michel
6469381899 Translations: Update German
Currently translated at 100.0% (5782 of 5782 strings)

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

powered by weblate
2024-11-19 16:33:52 +01:00
Raphael Michel
761706c60c Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-11-19 16:16:36 +01:00
CVZ-es
f91315c88e Translations: Update Spanish
Currently translated at 100.0% (5809 of 5809 strings)

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

powered by weblate
2024-11-19 16:16:13 +01:00
CVZ-es
bc05afeab9 Translations: Update French
Currently translated at 100.0% (5779 of 5779 strings)

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

powered by weblate
2024-11-19 16:16:13 +01:00
Raphael Michel
02d495d287 Revert "Update po files"
This reverts commit 894878d9da.
2024-11-19 16:15:55 +01:00
Raphael Michel
894878d9da Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-11-19 16:15:26 +01:00
Raphael Michel
5896ca0197 Event creation: Prevent accidentally creating events without tax rate (#4623)
* Event creation: Prevent accidentally creating events without tax rate

* Update src/pretix/control/forms/event.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Fix tests

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-11-19 16:14:56 +01:00
Raphael Michel
fe6fc8df32 Fix placehodler sample in empty series (PRETIXEU-ATN) 2024-11-19 16:14:13 +01:00
dependabot[bot]
9de8f3a775 Update aiohttp requirement from ==3.10.* to ==3.11.*
Updates the requirements on [aiohttp](https://github.com/aio-libs/aiohttp) to permit the latest version.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.0b0...v3.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-19 15:08:00 +01:00
Martin Gross
c92bb9cb8b Stripe: (FIX) Make MobilePay optional 2024-11-19 13:17:52 +01:00
Raphael Michel
76ecec8b98 Scheduled mails: Allow subevent-dependent placeholders (Z#23171818) (#4629) 2024-11-19 10:50:10 +01:00
Mira
4b8416df8f docs: update nginx config example (#4640) 2024-11-19 09:28:01 +01:00
91 changed files with 15036 additions and 14284 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

@@ -231,11 +231,10 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;

View File

@@ -216,11 +216,10 @@ The following snippet is an example on how to configure a nginx proxy for pretix
}
}
server {
listen 443 default_server;
listen [::]:443 ipv6only=on default_server;
listen 443 ssl default_server;
listen [::]:443 ipv6only=on ssl default_server;
server_name pretix.mydomain.com;
ssl on;
ssl_certificate /path/to/cert.chain.pem;
ssl_certificate_key /path/to/key.pem;

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",
@@ -107,7 +107,7 @@ dependencies = [
[project.optional-dependencies]
memcached = ["pylibmc"]
dev = [
"aiohttp==3.10.*",
"aiohttp==3.11.*",
"coverage",
"coveralls",
"fakeredis==2.26.*",

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

@@ -277,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
@@ -1143,15 +1147,16 @@ 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({"company": _('You need to provide a company name.')})
if not data.get('is_business') and not data.get('name_parts'):
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.')})
@@ -1166,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

@@ -209,13 +209,24 @@ def get_best_name(position_or_address, parts=False):
def base_placeholders(sender, **kwargs):
from pretix.multidomain.urlreverse import build_absolute_uri
def _event_sample(event):
if event.has_subevents:
se = event.subevents.first()
if se:
return se.name
return event.name
ph = [
SimpleFunctionalTextPlaceholder(
'event', ['event'], lambda event: event.name, lambda event: event.name
),
SimpleFunctionalTextPlaceholder(
'event', ['event_or_subevent'], lambda event_or_subevent: event_or_subevent.name,
lambda event_or_subevent: event_or_subevent.name
_event_sample,
),
SimpleFunctionalTextPlaceholder(
'event_series_name', ['event', 'event_or_subevent'], lambda event, event_or_subevent: event.name,
lambda event: event.name
),
SimpleFunctionalTextPlaceholder(
'event_slug', ['event'], lambda event: event.slug, lambda event: event.slug

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

@@ -136,6 +136,11 @@ class EventWizardBasicsForm(I18nModelForm):
choices=settings.LANGUAGES,
label=_("Default language"),
)
no_taxes = forms.BooleanField(
label=_("I don't want to specify taxes now"),
help_text=_("You can always configure tax rates later."),
required=False,
)
tax_rate = forms.DecimalField(
label=_("Sales tax rate"),
help_text=_("Do you need to pay sales tax on your tickets? In this case, please enter the applicable tax rate "
@@ -223,6 +228,11 @@ class EventWizardBasicsForm(I18nModelForm):
raise ValidationError({
'timezone': _('Your default locale must be specified.')
})
if not data.get("no_taxes") and not data.get("tax_rate"):
raise ValidationError({
'tax_rate': _('You have not specified a tax rate. If you do not want us to compute sales taxes, please '
'check "{field}" above.').format(field=self.fields["no_taxes"].label)
})
# change timezone
zone = ZoneInfo(data.get('timezone'))

View File

@@ -41,7 +41,10 @@
{% endif %}
{% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
{% bootstrap_field form.no_taxes layout="control" %}
<div data-display-dependency="#{{ form.no_taxes.id_for_label }}" data-inverse>
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
</div>
</fieldset>
<fieldset>
<legend>{% trans "Display settings" %}</legend>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-18 15:30+0000\n"
"POT-Creation-Date: 2024-11-19 15:34+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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:"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -381,8 +381,8 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
]
)
self._set_field_placeholders('subject', ['event', 'order'])
self._set_field_placeholders('template', ['event', 'order'])
self._set_field_placeholders('subject', ['event', 'order', 'event_or_subevent'])
self._set_field_placeholders('template', ['event', 'order', 'event_or_subevent'])
choices = [(e, l) for e, l in Order.STATUS_CHOICE if e != 'n']
choices.insert(0, ('n__valid_if_pending', _('payment pending but already confirmed')))

View File

@@ -0,0 +1,33 @@
# Generated by Django 4.2.16 on 2024-11-13 13:43
from django.db import migrations
from django.db.models import Q
from i18nfield.strings import LazyI18nString
def migrate_placeholders(apps, schema_editor):
Rule = apps.get_model("sendmail", "Rule")
for r in Rule.objects.filter(
Q(template__contains="{event}") | Q(subject__contains="{event}"),
event__has_subevents=True
):
r.template = LazyI18nString({
k: v.replace("{event}", "{event_series_name}") for k, v in r.template.data.items()
})
r.subject = LazyI18nString({
k: v.replace("{event}", "{event_series_name}") for k, v in r.subject.data.items()
})
r.save(update_fields=["template", "subject"])
class Migration(migrations.Migration):
dependencies = [
("sendmail", "008_remove_scheduled_mails"),
]
operations = [
migrations.RunPython(
migrate_placeholders,
migrations.RunPython.noop,
)
]

View File

@@ -174,7 +174,12 @@ class ScheduledMail(models.Model):
ia = InvoiceAddress(order=o)
if send_to_orders and o.email:
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
try:
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
@@ -192,12 +197,23 @@ class ScheduledMail(models.Model):
for p in positions:
try:
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
email_ctx = get_email_context(event=e, order=o, invoice_address=ia, position=p)
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
position=p,
event_or_subevent=self.subevent or e,
)
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
elif not o_sent and o.email:
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')

View File

@@ -492,12 +492,18 @@ class StripeSettingsHolder(BasePaymentProvider):
# label=_('PayPal'),
# disabled=self.event.currency not in [
# 'EUR', 'GBP', 'USD', 'CHF', 'CZK', 'DKK', 'NOK', 'PLN', 'SEK', 'AUD', 'CAD', 'HKD', 'NZD', 'SGD'
# ]
# ],
# help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
# 'before they work properly.'),
# required=False,
# )),
('method_mobilepay',
forms.BooleanField(
label=_('MobilePay'),
disabled=self.event.currency not in ['DKK', 'EUR', 'NOK', 'SEK'],
help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
'before they work properly.'),
required=False,
)),
] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
)

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):

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

@@ -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

@@ -20,7 +20,7 @@ $(function () {
xhr.abort();
}
for (var k in dependents) dependents[k].prop("disabled", true);
loader.fadeIn();
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", "");
@@ -43,9 +43,17 @@ $(function () {
}
for (var k in dependents) dependents[k].prop("disabled", false);
}).always(function() {
loader.fadeOut();
loader.hide();
}).fail(function(){
// TODO: handle failed request
// 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());

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);
@@ -122,29 +122,29 @@ $label-warning-bg-hover: var(--pretix-brand-warning-darken-10);
$label-danger-bg: var(--pretix-brand-danger);
$label-danger-bg-hover: var(--pretix-brand-danger-darken-10);
$alert-success-bg: var(--pretix-brand-success-lighten-48);
$alert-success-text: var(--pretix-brand-success-darken-10);
$alert-success-bg: var(--pretix-brand-success-tint-85);
$alert-success-text: var(--pretix-brand-success-shade-42);
$alert-success-border: var(--pretix-brand-success);
$alert-success-hr: var(--pretix-brand-success-darken-5);
$alert-success-link: var(--pretix-brand-success-darken-20);
$alert-success-link: var(--pretix-brand-success-shade-42);
$alert-info-bg: var(--pretix-brand-info-lighten-33);
$alert-info-text: var(--pretix-brand-info-darken-20);
$alert-info-bg: var(--pretix-brand-info-tint-85);
$alert-info-text: var(--pretix-brand-info-shade-42);
$alert-info-border: var(--pretix-brand-info);
$alert-info-hr: var(--pretix-brand-info-darken-5);
$alert-info-link: var(--pretix-brand-info-darken-30);
$alert-info-link: var(--pretix-brand-info-shade-42);
$alert-warning-bg: var(--pretix-brand-warning-lighten-41);
$alert-warning-text: var(--pretix-brand-warning-darken-25);
$alert-warning-bg: var(--pretix-brand-warning-tint-85);
$alert-warning-text: var(--pretix-brand-warning-shade-42);
$alert-warning-border: var(--pretix-brand-warning);
$alert-warning-hr: var(--pretix-brand-warning-darken-5);
$alert-warning-link: var(--pretix-brand-warning-darken-35);
$alert-warning-link: var(--pretix-brand-warning-shade-42);
$alert-danger-bg: var(--pretix-brand-danger-lighten-43);
$alert-danger-text: var(--pretix-brand-danger-darken-5);
$alert-danger-bg: var(--pretix-brand-danger-tint-85);
$alert-danger-text: var(--pretix-brand-danger-shade-42);
$alert-danger-border: var(--pretix-brand-danger);
$alert-danger-hr: var(--pretix-brand-danger-darken-5);
$alert-danger-link: var(--pretix-brand-danger-darken-15);
$alert-danger-link: var(--pretix-brand-danger-shade-42);
$main-box-bg: #FFFFFF;
@@ -156,4 +156,4 @@ $slider-secondary-bottom: var(--pretix-brand-primary-lighten-23-saturate-2);
/* The following vars default to $body-bg in bootstrap, which we don't want */
$nav-tabs-active-link-hover-bg: #FFFFFF;
$nav-tabs-justified-active-link-border-color: #FFFFFF;
$thumbnail-bg: #FFFFFF;
$thumbnail-bg: #FFFFFF;

View File

@@ -73,7 +73,9 @@ $in-border-radius-small: 2px !default;
--pretix-brand-success-darken-20: #{darken($in-brand-success, 20%)};
--pretix-brand-success-darken-30: #{darken($in-brand-success, 30%)};
--pretix-brand-success-tint-50: #{tint($in-brand-success, 50%)};
--pretix-brand-success-tint-85: #{tint($in-brand-success, 85%)};
--pretix-brand-success-shade-25: #{shade($in-brand-success, 25%)};
--pretix-brand-success-shade-42: #{shade($in-brand-success, 42%)};
--pretix-brand-info-lighten-23: #{lighten($in-brand-info, 23%)};
--pretix-brand-info-lighten-25: #{lighten($in-brand-info, 25%)};
@@ -84,7 +86,9 @@ $in-border-radius-small: 2px !default;
--pretix-brand-info-darken-17: #{darken($in-brand-info, 17%)};
--pretix-brand-info-darken-20: #{darken($in-brand-info, 20%)};
--pretix-brand-info-darken-30: #{darken($in-brand-info, 30%)};
--pretix-brand-info-tint-85: #{tint($in-brand-info, 85%)};
--pretix-brand-info-shade-25: #{shade($in-brand-info, 25%)};
--pretix-brand-info-shade-42: #{shade($in-brand-info, 42%)};
--pretix-brand-warning-lighten-12: #{lighten($in-brand-warning, 12%)};
--pretix-brand-warning-lighten-31: #{lighten($in-brand-warning, 31%)};
@@ -101,7 +105,9 @@ $in-border-radius-small: 2px !default;
--pretix-brand-warning-darken-30: #{darken($in-brand-warning, 30%)};
--pretix-brand-warning-darken-35: #{darken($in-brand-warning, 35%)};
--pretix-brand-warning-tint-50: #{tint($in-brand-warning, 50%)};
--pretix-brand-warning-tint-85: #{tint($in-brand-warning, 85%)};
--pretix-brand-warning-shade-25: #{shade($in-brand-warning, 25%)};
--pretix-brand-warning-shade-42: #{shade($in-brand-warning, 42%)};
--pretix-brand-warning-transparent-60: #{transparentize($in-brand-warning, 0.6)};
--pretix-brand-danger-lighten-5: #{lighten($in-brand-danger, 5%)};
@@ -118,7 +124,9 @@ $in-border-radius-small: 2px !default;
--pretix-brand-danger-darken-20: #{darken($in-brand-danger, 20%)};
--pretix-brand-danger-darken-30: #{darken($in-brand-danger, 30%)};
--pretix-brand-danger-tint-50: #{tint($in-brand-danger, 50%)};
--pretix-brand-danger-tint-85: #{tint($in-brand-danger, 85%)};
--pretix-brand-danger-shade-25: #{shade($in-brand-danger, 25%)};
--pretix-brand-danger-shade-42: #{shade($in-brand-danger, 42%)};
--pretix-border-radius-base: #{$in-border-radius-base};
--pretix-border-radius-large: #{$in-border-radius-large};
@@ -140,4 +148,4 @@ $in-border-radius-small: 2px !default;
--pretix-body-bg-white-0: 1;
}
}
}
}

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

@@ -763,6 +763,7 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start': '2016-11-01 10:00:00',
@@ -792,6 +793,7 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -888,6 +890,7 @@ class EventsTest(SoupTest):
'basics-location_1': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1073,6 +1076,7 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'UTC',
'basics-presale_start_0': '',
@@ -1121,6 +1125,7 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'UTC',
'basics-presale_start_0': '',
@@ -1171,6 +1176,7 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'EUR',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1200,6 +1206,7 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': '$',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',
@@ -1229,6 +1236,7 @@ class EventsTest(SoupTest):
'basics-location_0': 'Hamburg',
'basics-currency': 'ASD',
'basics-tax_rate': '',
'basics-no_taxes': 'on',
'basics-locale': 'en',
'basics-timezone': 'Europe/Berlin',
'basics-presale_start_0': '2016-11-01',

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):