diff --git a/pyproject.toml b/pyproject.toml
index f794846ac5..8f7185ace3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,7 +44,7 @@ dependencies = [
"django-formtools==2.5.1",
"django-hierarkey==1.2.*",
"django-hijack==3.7.*",
- "django-i18nfield==1.9.*,>=1.9.4",
+ "django-i18nfield==1.9.*,>=1.9.5",
"django-libsass==0.9",
"django-localflavor==4.0",
"django-markup",
diff --git a/src/pretix/api/serializers/i18n.py b/src/pretix/api/serializers/i18n.py
index 80a94c016b..3fc91e0416 100644
--- a/src/pretix/api/serializers/i18n.py
+++ b/src/pretix/api/serializers/i18n.py
@@ -19,57 +19,8 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# .
#
-from django.conf import settings
from django.core.validators import URLValidator
-from i18nfield.fields import I18nCharField, I18nTextField
-from i18nfield.strings import LazyI18nString
-from rest_framework.exceptions import ValidationError
-from rest_framework.fields import Field
-from rest_framework.serializers import ModelSerializer
-
-
-class I18nField(Field):
- def __init__(self, **kwargs):
- self.allow_blank = kwargs.pop('allow_blank', False)
- self.trim_whitespace = kwargs.pop('trim_whitespace', True)
- self.max_length = kwargs.pop('max_length', None)
- self.min_length = kwargs.pop('min_length', None)
- super().__init__(**kwargs)
-
- def to_representation(self, value):
- if hasattr(value, 'data'):
- if isinstance(value.data, dict):
- return value.data
- elif value.data is None:
- return None
- else:
- return {
- settings.LANGUAGE_CODE: str(value.data)
- }
- elif value is None:
- return None
- else:
- return {
- settings.LANGUAGE_CODE: str(value)
- }
-
- def to_internal_value(self, data):
- if isinstance(data, str):
- return LazyI18nString(data)
- elif isinstance(data, dict):
- if any([k not in dict(settings.LANGUAGES) for k in data.keys()]):
- raise ValidationError('Invalid languages included.')
- return LazyI18nString(data)
- else:
- raise ValidationError('Invalid data type.')
-
-
-class I18nAwareModelSerializer(ModelSerializer):
- pass
-
-
-I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
-I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
+from i18nfield.rest_framework import I18nAwareModelSerializer, I18nField
class I18nURLField(I18nField):
@@ -84,3 +35,10 @@ class I18nURLField(I18nField):
else:
URLValidator()(value.data)
return value
+
+
+__all__ = [
+ "I18nAwareModelSerializer", # for backwards compatibility
+ "I18nField", # for backwards compatibility
+ "I18nURLField",
+]
diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py
index 26d6824cbc..7a4d29eb09 100644
--- a/src/pretix/base/settings.py
+++ b/src/pretix/base/settings.py
@@ -56,6 +56,7 @@ from django.utils.translation import (
from django_countries.fields import Country
from hierarkey.models import GlobalSettingsBase, Hierarkey
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
+from i18nfield.rest_framework import I18nField
from i18nfield.strings import LazyI18nString
from phonenumbers import PhoneNumber, parse
from rest_framework import serializers
@@ -63,7 +64,7 @@ from rest_framework import serializers
from pretix.api.serializers.fields import (
ListMultipleChoiceField, UploadedFileField,
)
-from pretix.api.serializers.i18n import I18nField, I18nURLField
+from pretix.api.serializers.i18n import I18nURLField
from pretix.base.forms import I18nMarkdownTextarea, I18nURLFormField
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
from pretix.base.reldate import (
diff --git a/src/pretix/base/templatetags/money.py b/src/pretix/base/templatetags/money.py
index fe9ff7d4b5..ad4eff1ccf 100644
--- a/src/pretix/base/templatetags/money.py
+++ b/src/pretix/base/templatetags/money.py
@@ -52,12 +52,12 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
# would make the numbers incorrect. If this branch executes, it's likely a bug in
# pretix, but we won't show wrong numbers!
if hide_currency:
- return floatformat(value, 2)
+ return floatformat(value, "2g")
else:
- return '{} {}'.format(arg, floatformat(value, 2))
+ return '{} {}'.format(arg, floatformat(value, "2g"))
if hide_currency:
- return floatformat(value, places)
+ return floatformat(value, f"{places}g")
locale_parts = translation.get_language().split("-", 1)
locale = locale_parts[0]
@@ -70,7 +70,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
try:
return format_currency(value, arg, locale=locale)
except:
- return '{} {}'.format(arg, floatformat(value, places))
+ return '{} {}'.format(arg, floatformat(value, f"{places}g"))
@register.filter("money_numberfield")
diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py
index 1b5f628e6e..8b5b47e2ae 100644
--- a/src/pretix/control/views/dashboards.py
+++ b/src/pretix/control/views/dashboards.py
@@ -38,6 +38,7 @@ from zoneinfo import ZoneInfo
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
+from django.contrib.humanize.templatetags.humanize import intcomma
from django.db.models import (
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum,
)
@@ -47,7 +48,6 @@ from django.http import JsonResponse
from django.shortcuts import render
from django.template.loader import get_template
from django.urls import reverse
-from django.utils import formats
from django.utils.formats import date_format
from django.utils.html import escape
from django.utils.timezone import now
@@ -67,6 +67,7 @@ from pretix.control.signals import (
from pretix.helpers.daterange import daterange
from ...base.models.orders import CancellationRequest
+from ...base.templatetags.money import money_filter
from ..logdisplay import OVERVIEW_BANLIST
NUM_WIDGET = '
{num}{text}
'
@@ -111,7 +112,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
return [
{
- 'content': None if lazy else NUM_WIDGET.format(num=tickc, text=_('Attendees (ordered)')),
+ 'content': None if lazy else NUM_WIDGET.format(num=intcomma(tickc), text=_('Attendees (ordered)')),
'lazy': 'attendees-ordered',
'display_size': 'small',
'priority': 100,
@@ -121,7 +122,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
- 'content': None if lazy else NUM_WIDGET.format(num=paidc, text=_('Attendees (paid)')),
+ 'content': None if lazy else NUM_WIDGET.format(num=intcomma(paidc), text=_('Attendees (paid)')),
'lazy': 'attendees-paid',
'display_size': 'small',
'priority': 100,
@@ -132,7 +133,9 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
},
{
'content': None if lazy else NUM_WIDGET.format(
- num=formats.localize(round_decimal(rev, sender.currency)), text=_('Total revenue ({currency})').format(currency=sender.currency)),
+ num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
+ text=_('Total revenue ({currency})').format(currency=sender.currency)
+ ),
'lazy': 'total-revenue',
'display_size': 'small',
'priority': 100,
@@ -207,7 +210,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets.append({
'content': None if lazy else NUM_WIDGET.format(
- num=str(happy), text=_('available to give to people on waiting list')
+ num=intcomma(happy), text=_('available to give to people on waiting list')
),
'lazy': 'waitinglist-avail',
'priority': 50,
@@ -217,7 +220,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
})
})
widgets.append({
- 'content': None if lazy else NUM_WIDGET.format(num=str(wles.count()), text=_('total waiting list length')),
+ 'content': None if lazy else NUM_WIDGET.format(num=intcomma(wles.count()), text=_('total waiting list length')),
'lazy': 'waitinglist-length',
'display_size': 'small',
'priority': 50,
@@ -245,7 +248,7 @@ def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
widgets.append({
'content': None if lazy else NUM_WIDGET.format(
- num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
+ num='{}/{}'.format(intcomma(left), intcomma(q.size)) if q.size is not None else '\u221e',
text=_('{quota} left').format(quota=escape(q.name))
),
'lazy': 'quota-{}'.format(q.pk),
@@ -297,7 +300,7 @@ def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
for cl in qs:
widgets.append({
'content': None if lazy else NUM_WIDGET.format(
- num='{}/{}'.format(cl.inside_count, cl.position_count),
+ num='{}/{}'.format(intcomma(cl.inside_count), intcomma(cl.position_count)),
text=_('Present – {list}').format(list=escape(cl.name))
),
'lazy': 'checkin-{}'.format(cl.pk),
diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html b/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
index b0b5bab565..5498b63fc9 100644
--- a/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
+++ b/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html
@@ -6,13 +6,14 @@
{% load eventsignal %}
{% load rich_text %}
{% for c in form.categories %}
+ {% with category_idx=forloop.counter %}
{% endif %}
{% else %}
@@ -250,7 +253,7 @@
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
-
+
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
@@ -341,12 +344,13 @@
name="cp_{{ form.pos.pk }}_item_{{ item.id }}"
id="cp_{{ form.pos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
- {% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.id }}-description"{% endif %}>
+ aria-describedby="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc">
{% trans "Select" context "checkbox" %}
{% else %}
-
+
+
+ aria-label="{% trans "Quantity" %}">
-
+
{% endif %}
{% else %}
@@ -370,6 +373,7 @@
{% endif %}
{% endfor %}
+ {% endwith %}
{% empty %}
{% trans "There are no add-ons available for this product." %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html
index 7f7d487b1a..a3834b9d46 100644
--- a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html
+++ b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html
@@ -219,7 +219,8 @@
{% trans "Select" context "checkbox" %}
{% else %}
-
+
+
+ aria-label="{% trans "Quantity" %}">
-
+
{% endif %}
{% else %}
@@ -370,7 +371,8 @@
{% trans "Select" context "checkbox" %}
{% else %}
-
+
+
+ aria-label="{% trans "Quantity" %}">
-
+
{% endif %}
{% else %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/voucher.html b/src/pretix/presale/templates/pretixpresale/event/voucher.html
index 706300f30b..556222f228 100644
--- a/src/pretix/presale/templates/pretixpresale/event/voucher.html
+++ b/src/pretix/presale/templates/pretixpresale/event/voucher.html
@@ -231,16 +231,17 @@
{% trans "Select" context "checkbox" %}
{% else %}
-
+
+
+ aria-label="{% trans "Quantity" %}">
-
+
{% endif %}
{% else %}
{% else %}
-
+
+
+ aria-label="{% trans "Quantity" %}">
-
+
{% endif %}
{% else %}