Compare commits

...

13 Commits

Author SHA1 Message Date
Mira
4aeab2b017 Merge branch 'master' into fieldset-panels-anim 2024-12-06 12:45:39 +01:00
Richard Schreiber
ae29240e58 [A11y] Improve number inputs for screen-readers 2024-12-06 11:11:36 +01:00
Richard Schreiber
74edf10b04 Move cursor fix for disabled fieldsets from bootstrap to presale 2024-12-06 11:07:11 +01:00
Richard Schreiber
e2e0eca872 Fix accordion-radio fieldset css 2024-12-06 10:13:32 +01:00
Raphael Michel
6132e4a2c4 Remove re-implementations if i18nfield API integration 2024-12-05 17:56:30 +01:00
Raphael Michel
7df7d28518 Bump django-i18nfield to 1.9.5 2024-12-05 17:56:30 +01:00
Raphael Michel
11ab5c5eeb Event dashboard: Use intcomma in numbers (Z#23175343) (#4687) 2024-12-05 17:11:57 +01:00
Mira Weller
d8f3875db0 Animation in <fieldset> accordion 2024-12-05 14:24:39 +01:00
CVZ-es
20211d2097 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-12-05 13:46:11 +01:00
CVZ-es
d760ad38bf Translations: Update French
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-12-05 13:46:11 +01:00
CVZ-es
69af2cee93 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-12-05 13:46:11 +01:00
Raphael Michel
6b199a2b9c Allow to enter gift cards into the voucher input (Z#23171961) (#4670) 2024-12-05 13:43:46 +01:00
Richard Schreiber
94a64ba53a Hide empty panels (#4684) 2024-12-05 12:08:33 +01:00
26 changed files with 294 additions and 182 deletions

View File

@@ -44,7 +44,7 @@ dependencies = [
"django-formtools==2.5.1", "django-formtools==2.5.1",
"django-hierarkey==1.2.*", "django-hierarkey==1.2.*",
"django-hijack==3.7.*", "django-hijack==3.7.*",
"django-i18nfield==1.9.*,>=1.9.4", "django-i18nfield==1.9.*,>=1.9.5",
"django-libsass==0.9", "django-libsass==0.9",
"django-localflavor==4.0", "django-localflavor==4.0",
"django-markup", "django-markup",

View File

@@ -19,57 +19,8 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
from django.conf import settings
from django.core.validators import URLValidator from django.core.validators import URLValidator
from i18nfield.fields import I18nCharField, I18nTextField from i18nfield.rest_framework import I18nAwareModelSerializer, I18nField
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
class I18nURLField(I18nField): class I18nURLField(I18nField):
@@ -84,3 +35,10 @@ class I18nURLField(I18nField):
else: else:
URLValidator()(value.data) URLValidator()(value.data)
return value return value
__all__ = [
"I18nAwareModelSerializer", # for backwards compatibility
"I18nField", # for backwards compatibility
"I18nURLField",
]

View File

@@ -1419,50 +1419,51 @@ class GiftCardPayment(BasePaymentProvider):
def payment_refund_supported(self, payment: OrderPayment) -> bool: def payment_refund_supported(self, payment: OrderPayment) -> bool:
return True return True
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]: def _add_giftcard_to_cart(self, cs, gc):
from pretix.base.services.cart import add_payment_to_cart from pretix.base.services.cart import add_payment_to_cart_session
if gc.currency != self.event.currency:
raise ValidationError(_("This gift card does not support this currency."))
if gc.testmode and not self.event.testmode:
raise ValidationError(_("This gift card can only be used in test mode."))
if not gc.testmode and self.event.testmode:
raise ValidationError(_("Only test gift cards can be used in test mode."))
if gc.expires and gc.expires < time_machine_now():
raise ValidationError(_("This gift card is no longer valid."))
if gc.value <= Decimal("0.00"):
raise ValidationError(_("All credit on this gift card has been used."))
for p in cs.get('payments', []):
if p['provider'] == self.identifier and p['info_data']['gift_card'] == gc.pk:
raise ValidationError(_("This gift card is already used for your payment."))
add_payment_to_cart_session(
cs,
self,
max_value=gc.value,
info_data={
'gift_card': gc.pk,
'gift_card_secret': gc.secret,
}
)
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
for p in get_cart(request): for p in get_cart(request):
if p.item.issue_giftcard: if p.item.issue_giftcard:
messages.error(request, _("You cannot pay with gift cards when buying a gift card.")) messages.error(request, _("You cannot pay with gift cards when buying a gift card."))
return return
cs = cart_session(request)
try: try:
gc = self.event.organizer.accepted_gift_cards.get( gc = self.event.organizer.accepted_gift_cards.get(
secret=request.POST.get("giftcard").strip() secret=request.POST.get("giftcard").strip()
) )
if gc.currency != self.event.currency: cs = cart_session(request)
messages.error(request, _("This gift card does not support this currency.")) try:
self._add_giftcard_to_cart(cs, gc)
return True
except ValidationError as e:
messages.error(request, str(e.message))
return return
if gc.testmode and not self.event.testmode:
messages.error(request, _("This gift card can only be used in test mode."))
return
if not gc.testmode and self.event.testmode:
messages.error(request, _("Only test gift cards can be used in test mode."))
return
if gc.expires and gc.expires < time_machine_now():
messages.error(request, _("This gift card is no longer valid."))
return
if gc.value <= Decimal("0.00"):
messages.error(request, _("All credit on this gift card has been used."))
return
for p in cs.get('payments', []):
if p['provider'] == self.identifier and p['info_data']['gift_card'] == gc.pk:
messages.error(request, _("This gift card is already used for your payment."))
return
add_payment_to_cart(
request,
self,
max_value=gc.value,
info_data={
'gift_card': gc.pk,
'gift_card_secret': gc.secret,
}
)
return True
except GiftCard.DoesNotExist: except GiftCard.DoesNotExist:
if self.event.vouchers.filter(code__iexact=request.POST.get("giftcard")).exists(): if self.event.vouchers.filter(code__iexact=request.POST.get("giftcard")).exists():
messages.warning(request, _("You entered a voucher instead of a gift card. Vouchers can only be entered on the first page of the shop below " messages.warning(request, _("You entered a voucher instead of a gift card. Vouchers can only be entered on the first page of the shop below "

View File

@@ -1426,6 +1426,28 @@ class CartManager:
raise CartError(err) raise CartError(err)
def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
"""
:param cart_session: The current cart session.
:param provider: The instance of your payment provider.
:param min_value: The minimum value this payment instrument supports, or ``None`` for unlimited.
:param max_value: The maximum value this payment instrument supports, or ``None`` for unlimited. Highly discouraged
to use for payment providers which charge a payment fee, as this can be very user-unfriendly if
users need a second payment method just for the payment fee of the first method.
:param info_data: A dictionary of information that will be passed through to the ``OrderPayment.info_data`` attribute.
:return:
"""
cart_session.setdefault('payments', [])
cart_session['payments'].append({
'id': str(uuid.uuid4()),
'provider': provider.identifier,
'multi_use_supported': provider.multi_use_supported,
'min_value': str(min_value) if min_value is not None else None,
'max_value': str(max_value) if max_value is not None else None,
'info_data': info_data or {},
})
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None): def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
""" """
:param request: The current HTTP request context. :param request: The current HTTP request context.
@@ -1440,16 +1462,7 @@ def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: D
from pretix.presale.views.cart import cart_session from pretix.presale.views.cart import cart_session
cs = cart_session(request) cs = cart_session(request)
cs.setdefault('payments', []) add_payment_to_cart_session(cs, provider, min_value, max_value, info_data)
cs['payments'].append({
'id': str(uuid.uuid4()),
'provider': provider.identifier,
'multi_use_supported': provider.multi_use_supported,
'min_value': str(min_value) if min_value is not None else None,
'max_value': str(max_value) if max_value is not None else None,
'info_data': info_data or {},
})
def get_fees(event, request, total, invoice_address, payments, positions): def get_fees(event, request, total, invoice_address, payments, positions):

View File

@@ -56,6 +56,7 @@ from django.utils.translation import (
from django_countries.fields import Country from django_countries.fields import Country
from hierarkey.models import GlobalSettingsBase, Hierarkey from hierarkey.models import GlobalSettingsBase, Hierarkey
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.rest_framework import I18nField
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from phonenumbers import PhoneNumber, parse from phonenumbers import PhoneNumber, parse
from rest_framework import serializers from rest_framework import serializers
@@ -63,7 +64,7 @@ from rest_framework import serializers
from pretix.api.serializers.fields import ( from pretix.api.serializers.fields import (
ListMultipleChoiceField, UploadedFileField, 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.forms import I18nMarkdownTextarea, I18nURLFormField
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
from pretix.base.reldate import ( from pretix.base.reldate import (

View File

@@ -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 # would make the numbers incorrect. If this branch executes, it's likely a bug in
# pretix, but we won't show wrong numbers! # pretix, but we won't show wrong numbers!
if hide_currency: if hide_currency:
return floatformat(value, 2) return floatformat(value, "2g")
else: else:
return '{} {}'.format(arg, floatformat(value, 2)) return '{} {}'.format(arg, floatformat(value, "2g"))
if hide_currency: if hide_currency:
return floatformat(value, places) return floatformat(value, f"{places}g")
locale_parts = translation.get_language().split("-", 1) locale_parts = translation.get_language().split("-", 1)
locale = locale_parts[0] locale = locale_parts[0]
@@ -70,7 +70,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
try: try:
return format_currency(value, arg, locale=locale) return format_currency(value, arg, locale=locale)
except: except:
return '{} {}'.format(arg, floatformat(value, places)) return '{} {}'.format(arg, floatformat(value, f"{places}g"))
@register.filter("money_numberfield") @register.filter("money_numberfield")

View File

@@ -38,6 +38,7 @@ from zoneinfo import ZoneInfo
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import intcomma
from django.db.models import ( from django.db.models import (
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum, 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.shortcuts import render
from django.template.loader import get_template from django.template.loader import get_template
from django.urls import reverse from django.urls import reverse
from django.utils import formats
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.html import escape from django.utils.html import escape
from django.utils.timezone import now from django.utils.timezone import now
@@ -67,6 +67,7 @@ from pretix.control.signals import (
from pretix.helpers.daterange import daterange from pretix.helpers.daterange import daterange
from ...base.models.orders import CancellationRequest from ...base.models.orders import CancellationRequest
from ...base.templatetags.money import money_filter
from ..logdisplay import OVERVIEW_BANLIST from ..logdisplay import OVERVIEW_BANLIST
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>' NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
@@ -111,7 +112,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
return [ 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', 'lazy': 'attendees-ordered',
'display_size': 'small', 'display_size': 'small',
'priority': 100, 'priority': 100,
@@ -121,7 +122,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
}) + ('?subevent={}'.format(subevent.pk) if subevent else '') }) + ('?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', 'lazy': 'attendees-paid',
'display_size': 'small', 'display_size': 'small',
'priority': 100, 'priority': 100,
@@ -132,7 +133,9 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
}, },
{ {
'content': None if lazy else NUM_WIDGET.format( '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', 'lazy': 'total-revenue',
'display_size': 'small', 'display_size': 'small',
'priority': 100, 'priority': 100,
@@ -207,7 +210,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets.append({ widgets.append({
'content': None if lazy else NUM_WIDGET.format( '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', 'lazy': 'waitinglist-avail',
'priority': 50, 'priority': 50,
@@ -217,7 +220,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
}) })
}) })
widgets.append({ 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', 'lazy': 'waitinglist-length',
'display_size': 'small', 'display_size': 'small',
'priority': 50, '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) status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
widgets.append({ widgets.append({
'content': None if lazy else NUM_WIDGET.format( '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)) text=_('{quota} left').format(quota=escape(q.name))
), ),
'lazy': 'quota-{}'.format(q.pk), 'lazy': 'quota-{}'.format(q.pk),
@@ -297,7 +300,7 @@ def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
for cl in qs: for cl in qs:
widgets.append({ widgets.append({
'content': None if lazy else NUM_WIDGET.format( '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)) text=_('Present {list}').format(list=escape(cl.name))
), ),
'lazy': 'checkin-{}'.format(cl.pk), 'lazy': 'checkin-{}'.format(cl.pk),

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-19 15:34+0000\n" "POT-Creation-Date: 2024-11-19 15:34+0000\n"
"PO-Revision-Date: 2024-11-29 23:00+0000\n" "PO-Revision-Date: 2024-12-03 20:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n" "Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/" "Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n" "es/>\n"
@@ -31498,7 +31498,7 @@ msgstr "Lo sentimos, se ha producido un error en el proceso de pago."
#: pretix/plugins/ticketoutputpdf/apps.py:44 #: pretix/plugins/ticketoutputpdf/apps.py:44
#: pretix/plugins/ticketoutputpdf/apps.py:47 #: pretix/plugins/ticketoutputpdf/apps.py:47
msgid "PDF ticket output" msgid "PDF ticket output"
msgstr "Salida de entradas de PDF" msgstr "Salida de entradas en PDF"
#: pretix/plugins/ticketoutputpdf/apps.py:52 #: pretix/plugins/ticketoutputpdf/apps.py:52
msgid "" msgid ""

View File

@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-19 15:34+0000\n" "POT-Creation-Date: 2024-11-19 15:34+0000\n"
"PO-Revision-Date: 2024-11-29 23:00+0000\n" "PO-Revision-Date: 2024-12-03 20:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n" "Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/" "Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n" ">\n"
@@ -8954,7 +8954,7 @@ msgstr "Nombre maximum d'articles par commande"
#: pretix/base/settings.py:316 #: pretix/base/settings.py:316
msgid "Add-on products will not be counted." msgid "Add-on products will not be counted."
msgstr "Les Add-Ons ne seront pas pris en compte." msgstr "Les Add-Ons e sont pas comptabilisés."
#: pretix/base/settings.py:325 #: pretix/base/settings.py:325
msgid "" msgid ""
@@ -20630,17 +20630,16 @@ msgid ""
"product. You can also specify the minimum and maximum number of add-ons of " "product. You can also specify the minimum and maximum number of add-ons of "
"the given category that can or need to be chosen." "the given category that can or need to be chosen."
msgstr "" msgstr ""
"Avec les modules complémentaires, vous pouvez spécifier des produits qui " "Avec les add-ons, vous pouvez spécifier des produits qui peuvent être "
"peuvent être achetés en complément de ce produit. Par exemple, si vous " "achetés en complément de ce produit. Par exemple, si vous organisez une "
"organisez une conférence avec un ticket de conférence de base et un certain " "conférence avec un ticket de conférence de base et un certain nombre d"
"nombre dateliers, vous pouvez définir les ateliers comme des modules " "ateliers, vous pouvez définir les ateliers comme des modules complémentaires "
"complémentaires au ticket de conférence. Avec cette configuration, les " "au ticket de conférence. Avec cette configuration, les ateliers ne peuvent "
"ateliers ne peuvent pas être achetés seuls, mais uniquement en combinaison " "pas être achetés seuls, mais uniquement en combinaison avec un billet de "
"avec un billet de conférence. Vous pouvez spécifier ici des catégories de " "conférence. Vous pouvez spécifier ici des catégories de produits qui peuvent "
"produits qui peuvent être utilisés comme modules complémentaires à ce " "être utilisés comme modules complémentaires à ce produit. Vous pouvez "
"produit. Vous pouvez également spécifier le nombre minimal et maximal de " "également spécifier le nombre minimal et maximal de modules complémentaires "
"modules complémentaires de la catégorie donnée qui peuvent ou doivent être " "de la catégorie donnée qui peuvent ou doivent être choisis."
"choisis."
#: pretix/control/templates/pretixcontrol/item/include_addons.html:28 #: pretix/control/templates/pretixcontrol/item/include_addons.html:28
#: pretix/control/templates/pretixcontrol/item/include_addons.html:62 #: pretix/control/templates/pretixcontrol/item/include_addons.html:62
@@ -31762,7 +31761,7 @@ msgstr "Désolé, une erreur sest produite dans le processus de paiement."
#: pretix/plugins/ticketoutputpdf/apps.py:44 #: pretix/plugins/ticketoutputpdf/apps.py:44
#: pretix/plugins/ticketoutputpdf/apps.py:47 #: pretix/plugins/ticketoutputpdf/apps.py:47
msgid "PDF ticket output" msgid "PDF ticket output"
msgstr "Sortie du ticket PDF" msgstr "Génération de billets au format PDF"
#: pretix/plugins/ticketoutputpdf/apps.py:52 #: pretix/plugins/ticketoutputpdf/apps.py:52
msgid "" msgid ""

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: French\n" "Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-08 13:45+0000\n" "POT-Creation-Date: 2024-11-08 13:45+0000\n"
"PO-Revision-Date: 2024-11-16 05:00+0000\n" "PO-Revision-Date: 2024-12-03 20:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n" "Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n" "fr/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n" "Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.8.3\n" "X-Generator: Weblate 5.8.4\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -150,7 +150,7 @@ msgstr "Méthode de paiement non disponible"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Placed orders" msgid "Placed orders"
msgstr "Commandes placées" msgstr "Commandes réalisées"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39

View File

@@ -6,13 +6,14 @@
{% load eventsignal %} {% load eventsignal %}
{% load rich_text %} {% load rich_text %}
{% for c in form.categories %} {% for c in form.categories %}
{% with category_idx=forloop.counter %}
<fieldset data-addon-max-count="{{ c.max_count }}"{% if c.multi_allowed %} data-addon-multi-allowed{% endif %}> <fieldset data-addon-max-count="{{ c.max_count }}"{% if c.multi_allowed %} data-addon-multi-allowed{% endif %}>
<legend>{{ c.category.name }}</legend> <legend>{{ c.category.name }}</legend>
{% if c.category.description %} {% if c.category.description %}
{{ c.category.description|rich_text }} {{ c.category.description|rich_text }}
{% endif %} {% endif %}
{% if c.min_count == c.max_count %} {% if c.min_count == c.max_count %}
<p class="addon-count-desc"> <p class="addon-count-desc" id="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc">
{% blocktrans trimmed count min_count=c.min_count %} {% blocktrans trimmed count min_count=c.min_count %}
You need to choose exactly one option from this category. You need to choose exactly one option from this category.
{% plural %} {% plural %}
@@ -21,7 +22,7 @@
</p> </p>
{% elif c.min_count == 0 and c.max_count >= c.items|length and not c.multi_allowed %} {% elif c.min_count == 0 and c.max_count >= c.items|length and not c.multi_allowed %}
{% elif c.min_count == 0 %} {% elif c.min_count == 0 %}
<p class="addon-count-desc"> <p class="addon-count-desc" id="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc">
{% blocktrans trimmed count max_count=c.max_count %} {% blocktrans trimmed count max_count=c.max_count %}
You can choose {{ max_count }} option from this category. You can choose {{ max_count }} option from this category.
{% plural %} {% plural %}
@@ -29,7 +30,7 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% else %} {% else %}
<p class="addon-count-desc"> <p class="addon-count-desc" id="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc">
{% blocktrans trimmed with min_count=c.min_count max_count=c.max_count %} {% blocktrans trimmed with min_count=c.min_count max_count=c.max_count %}
You can choose between {{ min_count }} and {{ max_count }} options from You can choose between {{ min_count }} and {{ max_count }} options from
this category. this category.
@@ -58,7 +59,7 @@
</div> </div>
{% endif %} {% endif %}
{% if item.min_per_order and item.min_per_order > 1 %} {% if item.min_per_order and item.min_per_order > 1 %}
<p> <p id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-min-order">
<small> <small>
{% blocktrans trimmed with num=item.min_per_order %} {% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }} minimum amount to order: {{ num }}
@@ -196,12 +197,14 @@
{% endif %} {% endif %}
id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}"> aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}"
aria-describedby="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc">
<i class="fa fa-shopping-cart" aria-hidden="true"></i> <i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group" aria-describedby="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc cp-{{ form.pos.pk }}-item-{{ item.pk }}-min-order">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button> <button type="button" data-step="-1" data-controls="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0" <input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if var.initial %}value="{{ var.initial }}"{% endif %} {% if var.initial %}value="{{ var.initial }}"{% endif %}
@@ -211,9 +214,9 @@
max="{{ c.max_count }}" max="{{ c.max_count }}"
id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
aria-label="{% blocktrans with item=item.name var=var %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}"> aria-label="{% trans "Quantity" %}">
<button type="button" data-step="1" data-controls="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button> <button type="button" data-step="1" data-controls="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button>
</div> </fieldset>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
@@ -250,7 +253,7 @@
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %} {% endif %}
{% if item.min_per_order and item.min_per_order > 1 %} {% if item.min_per_order and item.min_per_order > 1 %}
<p> <p id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-min-order">
<small> <small>
{% blocktrans trimmed with num=item.min_per_order %} {% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }} minimum amount to order: {{ num }}
@@ -341,12 +344,13 @@
name="cp_{{ form.pos.pk }}_item_{{ item.id }}" name="cp_{{ form.pos.pk }}_item_{{ item.id }}"
id="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 %}" 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">
<i class="fa fa-shopping-cart" aria-hidden="true"></i> <i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group" aria-describedby="c-{{ form.pos.pk }}-{{ category_idx }}-addon-count-desc cp-{{ form.pos.pk }}-item-{{ item.pk }}-min-order">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="cp_{{ form.pos.pk }}_item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button> <button type="button" data-step="-1" data-controls="cp_{{ form.pos.pk }}_item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0" <input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if item.free_price %} {% if item.free_price %}
@@ -356,10 +360,9 @@
{% if item.initial %}value="{{ item.initial }}"{% endif %} {% if item.initial %}value="{{ item.initial }}"{% endif %}
name="cp_{{ form.pos.pk }}_item_{{ item.id }}" name="cp_{{ form.pos.pk }}_item_{{ item.id }}"
id="cp_{{ form.pos.pk }}_item_{{ item.id }}" id="cp_{{ form.pos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}" aria-label="{% trans "Quantity" %}">
{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.id }}-description"{% endif %}>
<button type="button" data-step="1" data-controls="cp_{{ form.pos.pk }}_item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button> <button type="button" data-step="1" data-controls="cp_{{ form.pos.pk }}_item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button>
</div> </fieldset>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
@@ -370,6 +373,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</fieldset> </fieldset>
{% endwith %}
{% empty %} {% empty %}
<em> <em>
{% trans "There are no add-ons available for this product." %} {% trans "There are no add-ons available for this product." %}

View File

@@ -10,18 +10,41 @@
<i class="fa fa-shopping-cart" aria-hidden="true"></i> <i class="fa fa-shopping-cart" aria-hidden="true"></i>
<strong>{% trans "Your cart" %}</strong> <strong>{% trans "Your cart" %}</strong>
</span> </span>
<strong id="cart-deadline-short" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" aria-hidden="true"> {% if cart.positions %}
{% if cart.minutes_left > 0 or cart.seconds_left > 0 %} <strong id="cart-deadline-short" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" aria-hidden="true">
{{ cart.minutes_left|stringformat:"02d" }}:{{ cart.seconds_left|stringformat:"02d" }} {% if cart.minutes_left > 0 or cart.seconds_left > 0 %}
{% else %} {{ cart.minutes_left|stringformat:"02d" }}:{{ cart.seconds_left|stringformat:"02d" }}
{% trans "Cart expired" %} {% else %}
{% endif %} {% trans "Cart expired" %}
</strong> {% endif %}
</strong>
{% endif %}
</h2> </h2>
</summary> </summary>
<div> <div>
<div class="panel-body"> <div class="panel-body">
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %} {% if cart.positions %}
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %}
{% endif %}
{% if cart.current_selected_payments %}
<p>{% trans "You already selected the following payment methods:" %}</p>
<div class="list-group">
{% for p in cart.current_selected_payments %}
<div class="list-group-item">
<div class="row">
<div class="col-xs-9">
{{ p.provider_name }}
</div>
<div class="col-xs-3 text-right">
{% if p.payment_amount %}
{{ p.payment_amount|money:request.event.currency }}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<div class="checkout-button-row"> <div class="checkout-button-row">
<form class="checkout-button-primary" method="get" action="{% eventurl request.event "presale:event.checkout.start" cart_namespace=cart_namespace %}"> <form class="checkout-button-primary" method="get" action="{% eventurl request.event "presale:event.checkout.start" cart_namespace=cart_namespace %}">
<p><button class="btn btn-primary btn-lg" type="submit"{% if has_addon_choices or cart.total == 0 %} aria-label="{% trans "Continue with order process" %}"{% endif %}> <p><button class="btn btn-primary btn-lg" type="submit"{% if has_addon_choices or cart.total == 0 %} aria-label="{% trans "Continue with order process" %}"{% endif %}>

View File

@@ -219,7 +219,8 @@
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}" <button type="button" data-step="-1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button> {% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0" <input type="number" class="form-control input-item-count" placeholder="0" min="0"
@@ -230,10 +231,10 @@
max="{{ var.order_max }}" max="{{ var.order_max }}"
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-label="{% blocktrans with item=item.name var=var.name %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}"> aria-label="{% trans "Quantity" %}">
<button type="button" data-step="1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}" <button type="button" data-step="1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button> {% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</div> </fieldset>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
@@ -370,7 +371,8 @@
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}" <button type="button" data-step="-1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button> {% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0" <input type="number" class="form-control input-item-count" placeholder="0" min="0"
@@ -382,11 +384,10 @@
max="{{ item.order_max }}" max="{{ item.order_max }}"
name="{{ form_prefix }}item_{{ item.id }}" name="{{ form_prefix }}item_{{ item.id }}"
id="{{ form_prefix }}item_{{ item.id }}" id="{{ form_prefix }}item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}" aria-label="{% trans "Quantity" %}">
{% if item.description %} aria-describedby="{{ form_prefix }}item-{{ item.id }}-description"{% endif %}>
<button type="button" data-step="1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}" <button type="button" data-step="1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button> {% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</div> </fieldset>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}

View File

@@ -231,16 +231,17 @@
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button> <button type="button" data-step="-1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0" <input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ var.order_max }}" max="{{ var.order_max }}"
id="variation_{{ item.id }}_{{ var.id }}" id="variation_{{ item.id }}_{{ var.id }}"
name="variation_{{ item.id }}_{{ var.id }}" name="variation_{{ item.id }}_{{ var.id }}"
{% if options == 1 %}value="1"{% endif %} {% if options == 1 %}value="1"{% endif %}
aria-label="{% blocktrans with item=item.name var=var.name %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}"> aria-label="{% trans "Quantity" %}">
<button type="button" data-step="1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button> <button type="button" data-step="1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button>
</div> </fieldset>
{% endif %} {% endif %}
{% else %} {% else %}
<label> <label>
@@ -385,7 +386,8 @@
{% trans "Select" context "checkbox" %} {% trans "Select" context "checkbox" %}
</label> </label>
{% else %} {% else %}
<div class="input-item-count-group"> <fieldset class="input-item-count-group">
<legend class="sr-only">{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}</legend>
<button type="button" data-step="-1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button> <button type="button" data-step="-1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="{% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count" <input type="number" class="form-control input-item-count"
placeholder="0" min="0" placeholder="0" min="0"
@@ -393,10 +395,9 @@
id="item_{{ item.id }}" id="item_{{ item.id }}"
name="item_{{ item.id }}" name="item_{{ item.id }}"
{% if options == 1 %}value="1"{% endif %} {% if options == 1 %}value="1"{% endif %}
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}" aria-label="{% trans "Quantity" %}">
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
<button type="button" data-step="1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button> <button type="button" data-step="1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="{% trans "Increase quantity" %}">+</button>
</div> </fieldset>
{% endif %} {% endif %}
{% else %} {% else %}
<label> <label>

View File

@@ -251,7 +251,8 @@ class CartMixin:
'seconds_left': seconds_left, 'seconds_left': seconds_left,
'first_expiry': first_expiry, 'first_expiry': first_expiry,
'is_ordered': bool(order), 'is_ordered': bool(order),
'itemcount': sum(c.count for c in positions if not c.addon_to) 'itemcount': sum(c.count for c in positions if not c.addon_to),
'current_selected_payments': [p for p in self.current_selected_payments(total) if p.get('multi_use_supported')]
} }
def current_selected_payments(self, total, warn=False, total_includes_payment_fees=False): def current_selected_payments(self, total, warn=False, total_includes_payment_fees=False):

View File

@@ -42,6 +42,7 @@ from urllib.parse import quote
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.cache import caches from django.core.cache import caches
from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from django.http import FileResponse, Http404, JsonResponse from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
@@ -57,7 +58,7 @@ from django.views.generic import TemplateView, View
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pretix.base.models import ( from pretix.base.models import (
CartPosition, InvoiceAddress, QuestionAnswer, SubEvent, Voucher, CartPosition, GiftCard, InvoiceAddress, QuestionAnswer, SubEvent, Voucher,
) )
from pretix.base.services.cart import ( from pretix.base.services.cart import (
CartError, add_items_to_cart, apply_voucher, clear_cart, error_messages, CartError, add_items_to_cart, apply_voucher, clear_cart, error_messages,
@@ -438,8 +439,48 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
return _('We applied the voucher to as many products in your cart as we could.') return _('We applied the voucher to as many products in your cart as we could.')
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
from pretix.base.payment import GiftCardPayment
if 'voucher' in request.POST: if 'voucher' in request.POST:
return self.do(self.request.event.id, request.POST.get('voucher'), get_or_create_cart_id(self.request), code = request.POST.get('voucher').strip()
if not self.request.event.vouchers.filter(code__iexact=code):
try:
gc = self.request.event.organizer.accepted_gift_cards.get(secret=code)
gcp = GiftCardPayment(self.request.event)
if not gcp.is_enabled or not gcp.is_allowed(self.request, Decimal("1.00")):
raise ValidationError(error_messages['voucher_invalid'])
else:
cs = cart_session(request)
gcp._add_giftcard_to_cart(cs, gc)
messages.success(
request,
_("The gift card has been saved to your cart. Please continue your checkout.")
)
if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({
'ready': True,
'success': True,
'redirect': self.get_success_url(),
'message': str(
_("The gift card has been saved to your cart. Please continue your checkout.")
)
})
return redirect_to_url(self.get_success_url())
except GiftCard.DoesNotExist:
pass
except ValidationError as e:
messages.error(self.request, str(e.message))
if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({
'ready': True,
'success': False,
'redirect': self.get_success_url(),
'message': str(e.message)
})
return redirect_to_url(self.get_error_url())
return self.do(self.request.event.id, code, get_or_create_cart_id(self.request),
translation.get_language(), request.sales_channel.identifier, translation.get_language(), request.sales_channel.identifier,
time_machine_now(default=None)) time_machine_now(default=None))
else: else:
@@ -631,6 +672,8 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
return context return context
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
from pretix.base.payment import GiftCardPayment
err = None err = None
v = request.GET.get('voucher') v = request.GET.get('voucher')
@@ -653,10 +696,24 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
if v_avail < 1 and not err: if v_avail < 1 and not err:
err = error_messages['voucher_redeemed_cart'] % self.request.event.settings.reservation_time err = error_messages['voucher_redeemed_cart'] % self.request.event.settings.reservation_time
except Voucher.DoesNotExist: except Voucher.DoesNotExist:
if self.request.event.organizer.accepted_gift_cards.filter(secret__iexact=request.GET.get("voucher")).exists(): try:
err = error_messages['gift_card'] gc = self.request.event.organizer.accepted_gift_cards.get(secret=v.strip())
else: gcp = GiftCardPayment(self.request.event)
if not gcp.is_enabled or not gcp.is_allowed(self.request, Decimal("1.00")):
err = error_messages['voucher_invalid']
else:
cs = cart_session(request)
gcp._add_giftcard_to_cart(cs, gc)
messages.success(
request,
_("The gift card has been saved to your cart. Please now select the products "
"you want to purchase.")
)
return redirect_to_url(self.get_next_url())
except GiftCard.DoesNotExist:
err = error_messages['voucher_invalid'] err = error_messages['voucher_invalid']
except ValidationError as e:
err = str(e.message)
else: else:
context = {} context = {}
context['cart'] = self.get_cart() context['cart'] = self.get_cart()

View File

@@ -632,7 +632,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['subevent_list_cache_key'] = self._subevent_list_cachekey() context['subevent_list_cache_key'] = self._subevent_list_cachekey()
context['show_cart'] = ( context['show_cart'] = (
context['cart']['positions'] and ( (context['cart']['positions'] or context['cart'].get('current_selected_payments')) and (
self.request.event.has_subevents or self.request.event.presale_is_running self.request.event.has_subevents or self.request.event.presale_is_running
) )
) )

View File

@@ -70,7 +70,7 @@ input[type="checkbox"] {
// Note: Neither radios nor checkboxes can be readonly. // Note: Neither radios nor checkboxes can be readonly.
&[disabled], &[disabled],
&.disabled, &.disabled,
fieldset[disabled] &:not(fieldset[disabled] > legend &) { fieldset[disabled] & {
cursor: $cursor-disabled; cursor: $cursor-disabled;
} }
} }

View File

@@ -558,9 +558,6 @@ table td > .checkbox input[type="checkbox"] {
fieldset.accordion-panel > legend { fieldset.accordion-panel > legend {
display: contents; display: contents;
} }
fieldset.accordion-panel[disabled] > .panel-body {
display: none;
}
.maildesignpreview { .maildesignpreview {
label { label {
display: block; display: block;

View File

@@ -244,9 +244,9 @@ function setup_basics(el) {
} }
}); });
$("fieldset.accordion-panel > legend input[type=radio]").change(function() { $("fieldset.accordion-panel > legend input[type=radio]").change(function() {
$(this).closest("fieldset").siblings("fieldset").prop('disabled', true); $(this).closest("fieldset").siblings("fieldset").prop('disabled', true).children('.panel-body').slideUp();
$(this).closest("fieldset").prop('disabled', false); $(this).closest("fieldset").prop('disabled', false).children('.panel-body').slideDown();
}).each(function() { $(this).closest("fieldset").prop('disabled', true); }).filter(":checked").trigger('change'); }).filter(':not(:checked)').each(function() { $(this).closest("fieldset").prop('disabled', true).children('.panel-body').hide(); });
el.find(".js-only").removeClass("js-only"); el.find(".js-only").removeClass("js-only");
el.find(".js-hidden").hide(); el.find(".js-hidden").hide();

View File

@@ -136,6 +136,10 @@ article.item-with-variations .product-row:last-child:after {
display: none; display: none;
} }
.panel-body:not(:has(*)) {
padding: 0;
}
.panel-body > *:last-child, .panel-body > *:last-child,
.panel-body address:last-child { .panel-body address:last-child {
margin-bottom: 0; margin-bottom: 0;

View File

@@ -135,9 +135,13 @@ a.btn, button.btn {
margin: 0; margin: 0;
} }
.panel-default>.accordion-radio>.panel-heading, fieldset.accordion-panel>legend>.panel-heading { .panel-default>.accordion-radio>.panel-heading, fieldset.accordion-panel>legend>.panel-heading {
display: block;
color: #333; color: #333;
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 8px 15px; padding: 8px 15px;
margin: 0;
line-height: 1.428571429;
font-size: 16px;
input[type=radio] { input[type=radio] {
margin-top: 0; margin-top: 0;
@@ -150,8 +154,9 @@ a.btn, button.btn {
fieldset.accordion-panel > legend { fieldset.accordion-panel > legend {
display: contents; display: contents;
} }
fieldset.accordion-panel[disabled] > .panel-body { fieldset[disabled] legend input[type="radio"],
display: none; fieldset[disabled] legend input[type="checkbox"] {
cursor: default;
} }
.nav-tabs { .nav-tabs {

View File

@@ -335,6 +335,11 @@ body.loading .container {
} }
} }
.font-normal {
font-style: normal;
font-weight: normal;
}
.blank-after { .blank-after {
margin-bottom: 1em; margin-bottom: 1em;
} }

View File

@@ -77,7 +77,7 @@ def test_urlreplace_replace_parameter():
# rounding errors # rounding errors
("de", Decimal("1.234"), "EUR", "1,23" + NBSP + ""), ("de", Decimal("1.234"), "EUR", "1,23" + NBSP + ""),
("de", Decimal("1023.1"), "JPY", "JPY 1023,10"), ("de", Decimal("1023.1"), "JPY", "JPY 1.023,10"),
] ]
) )
def test_money_filter(locale, amount, currency, expected): def test_money_filter(locale, amount, currency, expected):
@@ -99,9 +99,9 @@ def test_money_filter(locale, amount, currency, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"locale,amount,currency,expected", "locale,amount,currency,expected",
[ [
("de", Decimal("1000.00"), "EUR", "1000,00"), ("de", Decimal("1000.00"), "EUR", "1.000,00"),
("en", Decimal("1000.00"), "EUR", "1000.00"), ("en", Decimal("1000.00"), "EUR", "1,000.00"),
("de", Decimal("1023.1"), "JPY", "1023,10"), ("de", Decimal("1023.1"), "JPY", "1.023,10"),
] ]
) )
def test_money_filter_hidecurrency(locale, amount, currency, expected): def test_money_filter_hidecurrency(locale, amount, currency, expected):

View File

@@ -2306,6 +2306,25 @@ class CartTest(CartTestMixin, TestCase):
assert cp1.voucher is None assert cp1.voucher is None
assert cp2.voucher is None assert cp2.voucher is None
def test_voucher_apply_is_a_giftcard(self):
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10)
)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
price=8, expires=now() + timedelta(minutes=10),
)
gc = self.orga.issued_gift_cards.create(secret="GIFTCARD", currency=self.event.currency)
gc.transactions.create(value=Decimal("12.24"), acceptor=self.orga)
html = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
'voucher': 'GIFTCARD',
}, follow=True)
assert "alert-success" in html.rendered_content
assert "€12.24" in html.rendered_content
def test_discount(self): def test_discount(self):
with scopes_disabled(): with scopes_disabled():
Discount.objects.create(event=self.event, condition_min_count=2, benefit_discount_matching_percent=20, Discount.objects.create(event=self.event, condition_min_count=2, benefit_discount_matching_percent=20,

View File

@@ -55,6 +55,7 @@ from pretix.base.models import (
) )
from pretix.base.models.items import SubEventItem, SubEventItemVariation from pretix.base.models.items import SubEventItem, SubEventItemVariation
from pretix.base.reldate import RelativeDate, RelativeDateWrapper from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.testutils.sessions import get_cart_session_key
class EventTestMixin: class EventTestMixin:
@@ -1003,6 +1004,25 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
assert 'name="variation_%d_%d' % (self.item.pk, var1.pk) not in html.rendered_content assert 'name="variation_%d_%d' % (self.item.pk, var1.pk) not in html.rendered_content
assert 'name="variation_%d_%d' % (self.item.pk, var2.pk) not in html.rendered_content assert 'name="variation_%d_%d' % (self.item.pk, var2.pk) not in html.rendered_content
def test_voucher_is_a_gift_card(self):
gc = self.orga.issued_gift_cards.create(secret="GIFTCARD", currency=self.event.currency)
gc.transactions.create(value=Decimal("12.00"), acceptor=self.orga)
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, 'GIFTCARD'), follow=True)
assert "alert-success" in html.rendered_content
assert "€12.00" in html.rendered_content
payments = self.client.session['carts'][get_cart_session_key(self.client, self.event)]["payments"]
assert payments[0]["info_data"]["gift_card_secret"] == "GIFTCARD"
def test_voucher_is_a_gift_card_but_invalid(self):
gc = self.orga.issued_gift_cards.create(secret="GIFTCARD", currency=self.event.currency, expires=now() - datetime.timedelta(days=1))
gc.transactions.create(value=Decimal("12.00"), acceptor=self.orga)
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, 'GIFTCARD'), follow=True)
assert "alert-danger" in html.rendered_content
assert "This gift card is no longer valid" in html.rendered_content
class WaitingListTest(EventTestMixin, SoupTest): class WaitingListTest(EventTestMixin, SoupTest):
@scopes_disabled() @scopes_disabled()