Compare commits

...
Author SHA1 Message Date
Richard Schreiber 8ee97a2c29 harmonize date-format for checkin CSV-export 2023-02-02 09:23:38 +01:00
Raphael Michel 7b301b6027 Self-service order change: Don't create invoice too early 2023-02-01 17:23:28 +01:00
Raphael Michel 68430f01a3 OrderPayment.fail(): Return whether fail was successful 2023-02-01 15:45:20 +01:00
Raphael Michel 363c62a6ca Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5093 of 5093 strings)

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

powered by weblate
2023-02-01 14:55:00 +01:00
Raphael Michel a7f32b8647 Translations: Update German
Currently translated at 100.0% (5093 of 5093 strings)

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

powered by weblate
2023-02-01 14:55:00 +01:00
Raphael Michel 8786397910 Add words to translation word lists 2023-02-01 14:52:03 +01:00
Raphael Michel d078a42250 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-02-01 14:31:33 +01:00
Raphael Michel f1a73cd440 Update django to 3.2.17 2023-02-01 13:22:54 +01:00
Raphael MichelandRichard Schreiber 8bba1a2ea6 Fix #1251 -- Event list/calendar: Show "event almost sold out" state (#3063)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-02-01 13:20:06 +01:00
Raphael Michel aeb5c52bfe Ignore deprecation warning from PyPDF2 2023-02-01 13:18:50 +01:00
Raphael Michel a684aca212 Revert "Fix test failing due to tz"
This reverts commit 1d46a96821.
2023-02-01 13:16:18 +01:00
Raphael Michel 59d46ddded Revert "First steps into pytz deprecation"
This reverts commit e4e7d50659.
2023-02-01 13:15:18 +01:00
Raphael Michel e4e7d50659 First steps into pytz deprecation 2023-02-01 13:12:24 +01:00
Raphael Michel 1d46a96821 Fix test failing due to tz 2023-02-01 12:27:22 +01:00
Raphael Michel 8b81ef6f43 Fix test classes 2023-02-01 12:27:15 +01:00
Raphael Michel cb734510ac Bump PyPDF2 to 3.0.* 2023-02-01 12:16:30 +01:00
Raphael Michel b29f5c69ed Update isort to 5.12.* 2023-02-01 12:16:01 +01:00
Raphael Michel 56ce37225c Update pytest-rerunfailures to 11.* 2023-02-01 12:15:28 +01:00
Raphael Michel afae6fdd45 Improve error messages when event or organizer can't be deleted (#3074) 2023-02-01 11:43:07 +01:00
Mauro Amico 4cad8eae93 Translations: Update Italian
Currently translated at 18.2% (928 of 5085 strings)

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

powered by weblate
2023-02-01 11:43:00 +01:00
Raphael Michel c5b7ff66b7 Fix incorrecly used error messages 2023-01-31 22:11:53 +01:00
Raphael Michel 04e16bbb39 Prevent generation of very long team names 2023-01-31 18:13:51 +01:00
Raphael Michel eea48af60a PDF: Fix Arabic string rendering 2023-01-31 17:11:47 +01:00
Raphael Michel fc63f60960 Fix #3064 -- Banktransfer: Incorrect timeout handling 2023-01-31 10:44:47 +01:00
Raphael Michel 3b94125471 Voucher product autocompletion: Allow to search for internal name 2023-01-30 18:20:28 +01:00
Raphael Michel 00d901b04b Test on Python 3.11, drop Python 3.7-3.8 (#2982) 2023-01-30 17:54:19 +01:00
Richard Schreiber a7f9e100d2 Clean up localization or error messages in cart (#3049) 2023-01-30 17:24:09 +01:00
Raphael Michel 59f409b1c6 Widget: Add missing "var" statement 2023-01-30 17:07:27 +01:00
Raphael Michel e03bebf5ab Bump to 4.17.0.dev0 2023-01-30 13:51:58 +01:00
118 changed files with 31723 additions and 27408 deletions
+2 -2
View File
@@ -26,10 +26,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
+4 -4
View File
@@ -24,10 +24,10 @@ jobs:
name: Check gettext syntax name: Check gettext syntax
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -50,10 +50,10 @@ jobs:
name: Spellcheck name: Spellcheck
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
+6 -6
View File
@@ -24,10 +24,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -45,10 +45,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -66,10 +66,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.9 - name: Set up Python 3.11
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.9 python-version: 3.11
- name: Install Dependencies - name: Install Dependencies
run: pip3 install licenseheaders run: pip3 install licenseheaders
- name: Run licenseheaders - name: Run licenseheaders
+6 -6
View File
@@ -24,22 +24,22 @@ jobs:
name: Tests name: Tests
strategy: strategy:
matrix: matrix:
python-version: ["3.7", "3.9", "3.10"] python-version: ["3.9", "3.10", "3.11"]
database: [sqlite, postgres, mysql] database: [sqlite, postgres, mysql]
exclude: exclude:
- database: mysql
python-version: "3.10"
- database: mysql - database: mysql
python-version: "3.9" python-version: "3.9"
- database: mysql
python-version: "3.11"
- database: sqlite - database: sqlite
python-version: "3.7" python-version: "3.9"
- database: sqlite - database: sqlite
python-version: "3.10" python-version: "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: getong/mariadb-action@v1.1 - uses: getong/mariadb-action@v1.1
with: with:
mariadb version: '10.4' mariadb version: '10.10'
mysql database: 'pretix' mysql database: 'pretix'
mysql root password: '' mysql root password: ''
if: matrix.database == 'mysql' if: matrix.database == 'mysql'
@@ -83,4 +83,4 @@ jobs:
file: src/coverage.xml file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true fail_ci_if_error: true
if: matrix.database == 'postgres' && matrix.python-version == '3.10' if: matrix.database == 'postgres' && matrix.python-version == '3.11'
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.9-bullseye FROM python:3.11-bullseye
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
+2 -2
View File
@@ -23,7 +23,7 @@ installation guides):
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for * A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections * A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `PostgreSQL`_ 9.6+ database server * A `PostgreSQL`_ 11+ database server
* A `redis`_ server * A `redis`_ server
* A `nodejs`_ installation * A `nodejs`_ installation
@@ -127,7 +127,7 @@ We now install pretix, its direct dependencies and gunicorn::
(venv)$ pip3 install pretix gunicorn (venv)$ pip3 install pretix gunicorn
Note that you need Python 3.7 or newer. You can find out your Python version using ``python -V``. Note that you need Python 3.9 or newer. You can find out your Python version using ``python -V``.
We also need to create a data directory:: We also need to create a data directory::
+1 -1
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 # 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/>.
# #
__version__ = "4.16.0" __version__ = "4.17.0.dev0"
+82 -41
View File
@@ -36,7 +36,7 @@ import logging
import os import os
import string import string
import uuid import uuid
from collections import OrderedDict from collections import Counter, OrderedDict, defaultdict
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from operator import attrgetter from operator import attrgetter
from urllib.parse import urljoin from urllib.parse import urljoin
@@ -340,64 +340,104 @@ class EventMixin:
) )
) )
@cached_property @property
def best_availability_state(self): def best_availability_state(self):
return self.best_availability[0]
@property
def best_availability_is_low(self):
"""
Returns ``True`` if the availability of tickets in this event is lower than the percentage
given in setting ``low_availability_percentage``.
"""
if not self.settings.low_availability_percentage:
return False
ba = self.best_availability
if ba[1] is None or not ba[2]:
return False
percentage = ba[1] / ba[2] * 100
return percentage < self.settings.low_availability_percentage
@cached_property
def best_availability(self):
"""
Returns a 3-tuple of
- The availability state of this event (one of the ``Quota.AVAILABILITY_*`` constants)
- The number of tickets currently available (or ``None``)
- The number of tickets "originally" available (or ``None``)
This can only be called on objects obtained through a queryset that has been passed through ``.annotated()``.
"""
from .items import Quota from .items import Quota
if not hasattr(self, 'active_quotas'): if not hasattr(self, 'active_quotas'):
raise TypeError("Call this only if you fetched the subevents via Event/SubEvent.annotated()") raise TypeError("Call this only if you fetched the subevents via Event/SubEvent.annotated()")
items_available = set()
vars_available = set()
items_reserved = set()
vars_reserved = set()
items_gone = set()
vars_gone = set()
items_disabled = set()
vars_disabled = set()
if hasattr(self, 'disabled_items'): # SubEventItem if hasattr(self, 'disabled_items'): # SubEventItem
items_disabled = set(self.disabled_items.split(",")) items_disabled = set(self.disabled_items.split(","))
else:
items_disabled = set()
if hasattr(self, 'disabled_vars'): # SubEventItemVariation if hasattr(self, 'disabled_vars'): # SubEventItemVariation
vars_disabled = set(self.disabled_vars.split(",")) vars_disabled = set(self.disabled_vars.split(","))
else:
vars_disabled = set()
# Compute the availability of all quotas and build a item→quotas mapping with all non-disabled items
r = getattr(self, '_quota_cache', {}) r = getattr(self, '_quota_cache', {})
quotas_for_item = defaultdict(list)
quotas_for_variation = defaultdict(list)
for q in self.active_quotas: for q in self.active_quotas:
res = r[q] if q in r else q.availability(allow_cache=True) if q not in r:
r[q] = q.availability(allow_cache=True)
if res[0] == Quota.AVAILABILITY_OK: if q.active_items:
if q.active_items: for item_id in q.active_items.split(","):
items_available.update(q.active_items.split(",")) if item_id not in items_disabled:
if q.active_variations: quotas_for_item[item_id].append(q)
vars_available.update(q.active_variations.split(",")) if q.active_variations:
elif res[0] == Quota.AVAILABILITY_RESERVED: for var_id in q.active_variations.split(","):
if q.active_items: if var_id not in vars_disabled:
items_reserved.update(q.active_items.split(",")) quotas_for_variation[var_id].append(q)
if q.active_variations:
vars_reserved.update(q.active_variations.split(","))
elif res[0] < Quota.AVAILABILITY_RESERVED:
if q.active_items:
items_gone.update(q.active_items.split(","))
if q.active_variations:
vars_gone.update(q.active_variations.split(","))
items_available -= items_disabled if not self.active_quotas or (not quotas_for_item and not quotas_for_variation):
items_reserved -= items_disabled # No item is enabled for this event, treat the event as "unknown"
items_gone -= items_disabled return None, None, None
vars_available -= vars_disabled
vars_reserved -= vars_disabled
vars_gone -= vars_disabled
if not self.active_quotas or ( # We iterate over all items and variations and keep track of
not items_available and not items_reserved and not items_gone and not vars_gone and not vars_available and not vars_reserved # - `best_state_found` - the best availability state we have seen so far. If one item is available, the event is available!
): # - `num_tickets_found` - the number of tickets currently available in total. We sum up all the items and variations, but keep
return None # track of them per-quota in `quota_used_for_found_tickets` to make sure we don't count the same tickets twice if two or more
# items share the same quota
# - `num_tickets_possible` - basically the same thing, just with the total size of quotas instead of their currently availability
# since we need that for the percentage calculation
best_state_found = Quota.AVAILABILITY_GONE
num_tickets_found = 0
num_tickets_possible = 0
quota_used_for_found_tickets = Counter()
quota_used_for_possible_tickets = Counter()
for quota_list in list(quotas_for_item.values()) + list(quotas_for_variation.values()):
worst_state_for_ticket = min(r[q][0] for q in quota_list)
quotas_that_are_not_unlimited = [q for q in quota_list if q.size is not None]
if not quotas_that_are_not_unlimited:
# We found an unlimited ticket, no more need to do anything else
return Quota.AVAILABILITY_OK, None, None
if items_available - items_reserved - items_gone or vars_available - vars_reserved - vars_gone: if worst_state_for_ticket == Quota.AVAILABILITY_OK:
return Quota.AVAILABILITY_OK availability_of_this = min(max(0, r[q][1] - quota_used_for_found_tickets[q]) for q in quotas_that_are_not_unlimited)
if items_reserved - items_gone or vars_reserved - vars_gone: num_tickets_found += availability_of_this
return Quota.AVAILABILITY_RESERVED for q in quota_list:
return Quota.AVAILABILITY_GONE quota_used_for_found_tickets[q] += availability_of_this
possible_of_this = min(max(0, q.size - quota_used_for_possible_tickets[q]) for q in quotas_that_are_not_unlimited)
num_tickets_possible += possible_of_this
for q in quota_list:
quota_used_for_possible_tickets[q] += possible_of_this
best_state_found = max(best_state_found, worst_state_for_ticket)
return best_state_found, num_tickets_found, num_tickets_possible
def free_seats(self, ignore_voucher=None, sales_channel='web', include_blocked=False): def free_seats(self, ignore_voucher=None, sales_channel='web', include_blocked=False):
qs_annotated = self._seats(ignore_voucher=ignore_voucher) qs_annotated = self._seats(ignore_voucher=ignore_voucher)
@@ -591,6 +631,7 @@ class Event(EventMixin, LoggedModel):
self.settings.invoice_email_attachment = True self.settings.invoice_email_attachment = True
self.settings.name_scheme = 'given_family' self.settings.name_scheme = 'given_family'
self.settings.payment_banktransfer_invoice_immediately = True self.settings.payment_banktransfer_invoice_immediately = True
self.settings.low_availability_percentage = 10
@property @property
def social_image(self): def social_image(self):
+2 -1
View File
@@ -1635,7 +1635,7 @@ class OrderPayment(models.Model):
logger.info('Failed payment {} but ignored due to likely race condition.'.format( logger.info('Failed payment {} but ignored due to likely race condition.'.format(
self.full_id, self.full_id,
)) ))
return return False
if isinstance(info, str): if isinstance(info, str):
locked_instance.info = info locked_instance.info = info
@@ -1651,6 +1651,7 @@ class OrderPayment(models.Model):
'info': info, 'info': info,
'data': log_data, 'data': log_data,
}, user=user, auth=auth) }, user=user, auth=auth)
return True
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='', def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='',
ignore_date=False, lock=True, payment_date=None, generate_invoice=True): ignore_date=False, lock=True, payment_date=None, generate_invoice=True):
+4 -3
View File
@@ -862,6 +862,10 @@ class Renderer:
self._get_text_content(op, order, o) or "", self._get_text_content(op, order, o) or "",
).replace("\n", "<br/>\n").replace("-", "-&hairsp;") ).replace("\n", "<br/>\n").replace("-", "-&hairsp;")
# reportlab does not support unicode combination characters
# It's important we do this before we use ArabicReshaper
text = unicodedata.normalize("NFKC", text)
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper # reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
# to resolve all ligatures and python-bidi to switch RTL texts. # to resolve all ligatures and python-bidi to switch RTL texts.
try: try:
@@ -869,9 +873,6 @@ class Renderer:
except: except:
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text))) logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
# reportlab does not support unicode combination characters
text = unicodedata.normalize("NFKC", text)
p = Paragraph(text, style=style) p = Paragraph(text, style=style)
w, h = p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm) w, h = p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
# p_size = p.wrap(float(o['width']) * mm, 1000 * mm) # p_size = p.wrap(float(o['width']) * mm, 1000 * mm)
+121 -82
View File
@@ -43,7 +43,9 @@ from django.db import DatabaseError, transaction
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import make_aware, now from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _, pgettext_lazy from django.utils.translation import (
gettext as _, gettext_lazy, ngettext_lazy, pgettext_lazy,
)
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pretix.base.channels import get_all_sales_channels from pretix.base.channels import get_all_sales_channels
@@ -81,74 +83,119 @@ class CartError(Exception):
if msgargs: if msgargs:
msg = _(msg) % msgargs msg = _(msg) % msgargs
else: else:
msg = _(msg) # force msg to string to make sure lazy-translation is done in current locale-context
# otherwise translation might happen in celery-context, which uses default-locale
# also translate with _/gettext to keep it backwards compatible
msg = _(str(msg))
super().__init__(msg) super().__init__(msg)
error_messages = { error_messages = {
'busy': _('We were not able to process your request completely as the ' 'busy': gettext_lazy(
'server was too busy. Please try again.'), 'We were not able to process your request completely as the '
'empty': _('You did not select any products.'), 'server was too busy. Please try again.'
'unknown_position': _('Unknown cart position.'), ),
'empty': gettext_lazy('You did not select any products.'),
'unknown_position': gettext_lazy('Unknown cart position.'),
'subevent_required': pgettext_lazy('subevent', 'No date was specified.'), 'subevent_required': pgettext_lazy('subevent', 'No date was specified.'),
'not_for_sale': _('You selected a product which is not available for sale.'), 'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'),
'unavailable': _('Some of the products you selected are no longer available. ' 'unavailable': gettext_lazy(
'Please see below for details.'), 'Some of the products you selected are no longer available. '
'in_part': _('Some of the products you selected are no longer available in ' 'Please see below for details.'
'the quantity you selected. Please see below for details.'), ),
'max_items': _("You cannot select more than %s items per order."), 'in_part': gettext_lazy(
'max_items_per_product': _("You cannot select more than %(max)s items of the product %(product)s."), 'Some of the products you selected are no longer available in '
'min_items_per_product': _("You need to select at least %(min)s items of the product %(product)s."), 'the quantity you selected. Please see below for details.'
'min_items_per_product_removed': _("We removed %(product)s from your cart as you can not buy less than " ),
"%(min)s items of it."), 'max_items': ngettext_lazy(
'not_started': _('The booking period for this event has not yet started.'), "You cannot select more than %s item per order.",
'ended': _('The booking period for this event has ended.'), "You cannot select more than %s items per order."
'payment_ended': _('All payments for this event need to be confirmed already, so no new orders can be created.'), ),
'some_subevent_not_started': _('The booking period for this event has not yet started. The affected positions ' 'max_items_per_product': ngettext_lazy(
'have been removed from your cart.'), "You cannot select more than %(max)s item of the product %(product)s.",
'some_subevent_ended': _('The booking period for one of the events in your cart has ended. The affected ' "You cannot select more than %(max)s items of the product %(product)s.",
'positions have been removed from your cart.'), "max"
'price_too_high': _('The entered price is to high.'), ),
'voucher_invalid': _('This voucher code is not known in our database.'), 'min_items_per_product': ngettext_lazy(
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s ' "You need to select at least %(min)s item of the product %(product)s.",
'matching products.'), "You need to select at least %(min)s items of the product %(product)s.",
'voucher_min_usages_removed': _('The voucher code "%(voucher)s" can only be used if you select at least ' "min"
'%(number)s matching products. We have therefore removed some positions from ' ),
'your cart that can no longer be purchased like this.'), 'min_items_per_product_removed': ngettext_lazy(
'voucher_redeemed': _('This voucher code has already been used the maximum number of times allowed.'), "We removed %(product)s from your cart as you can not buy less than %(min)s item of it.",
'voucher_redeemed_cart': _('This voucher code is currently locked since it is already contained in a cart. This ' "We removed %(product)s from your cart as you can not buy less than %(min)s items of it.",
'might mean that someone else is redeeming this voucher right now, or that you tried ' "min"
'to redeem it before but did not complete the checkout process. You can try to use it ' ),
'again in %d minutes.'), 'not_started': gettext_lazy('The booking period for this event has not yet started.'),
'voucher_redeemed_partial': _('This voucher code can only be redeemed %d more times.'), 'ended': gettext_lazy('The booking period for this event has ended.'),
'voucher_double': _('You already used this voucher code. Remove the associated line from your ' 'payment_ended': gettext_lazy('All payments for this event need to be confirmed already, so no new orders can be created.'),
'cart if you want to use it for a different product.'), 'some_subevent_not_started': gettext_lazy(
'voucher_expired': _('This voucher is expired.'), 'The booking period for this event has not yet started. The affected positions '
'voucher_invalid_item': _('This voucher is not valid for this product.'), 'have been removed from your cart.'),
'voucher_invalid_seat': _('This voucher is not valid for this seat.'), 'some_subevent_ended': gettext_lazy(
'voucher_no_match': _('We did not find any position in your cart that we could use this voucher for. If you want ' 'The booking period for one of the events in your cart has ended. The affected '
'to add something new to your cart using that voucher, you can do so with the voucher ' 'positions have been removed from your cart.'),
'redemption option on the bottom of the page.'), 'price_too_high': gettext_lazy('The entered price is to high.'),
'voucher_item_not_available': _( 'voucher_invalid': gettext_lazy('This voucher code is not known in our database.'),
'voucher_min_usages': gettext_lazy(
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s '
'matching products.'
),
'voucher_min_usages_removed': ngettext_lazy(
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. '
'We have therefore removed some positions from your cart that can no longer be purchased like this.',
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. '
'We have therefore removed some positions from your cart that can no longer be purchased like this.',
'number'
),
'voucher_redeemed': gettext_lazy('This voucher code has already been used the maximum number of times allowed.'),
'voucher_redeemed_cart': gettext_lazy(
'This voucher code is currently locked since it is already contained in a cart. This '
'might mean that someone else is redeeming this voucher right now, or that you tried '
'to redeem it before but did not complete the checkout process. You can try to use it '
'again in %d minutes.'
),
'voucher_redeemed_partial': gettext_lazy('This voucher code can only be redeemed %d more times.'),
'voucher_whole_cart_not_combined': gettext_lazy('Applying a voucher to the whole cart should not be combined with other operations.'),
'voucher_double': gettext_lazy(
'You already used this voucher code. Remove the associated line from your '
'cart if you want to use it for a different product.'
),
'voucher_expired': gettext_lazy('This voucher is expired.'),
'voucher_invalid_item': gettext_lazy('This voucher is not valid for this product.'),
'voucher_invalid_seat': gettext_lazy('This voucher is not valid for this seat.'),
'voucher_no_match': gettext_lazy(
'We did not find any position in your cart that we could use this voucher for. If you want '
'to add something new to your cart using that voucher, you can do so with the voucher '
'redemption option on the bottom of the page.'
),
'voucher_item_not_available': gettext_lazy(
'Your voucher is valid for a product that is currently not for sale.'), 'Your voucher is valid for a product that is currently not for sale.'),
'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'), 'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'),
'voucher_required': _('You need a valid voucher code to order this product.'), 'voucher_required': gettext_lazy('You need a valid voucher code to order this product.'),
'inactive_subevent': pgettext_lazy('subevent', 'The selected event date is not active.'), 'inactive_subevent': pgettext_lazy('subevent', 'The selected event date is not active.'),
'addon_invalid_base': _('You can not select an add-on for the selected product.'), 'addon_invalid_base': gettext_lazy('You can not select an add-on for the selected product.'),
'addon_duplicate_item': _('You can not select two variations of the same add-on product.'), 'addon_duplicate_item': gettext_lazy('You can not select two variations of the same add-on product.'),
'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'), 'addon_max_count': ngettext_lazy(
'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the ' 'You can select at most %(max)s add-on from the category %(cat)s for the product %(base)s.',
'product %(base)s.'), 'You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.',
'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), 'max'
'addon_only': _('One of the products you selected can only be bought as an add-on to another product.'), ),
'bundled_only': _('One of the products you selected can only be bought part of a bundle.'), 'addon_min_count': ngettext_lazy(
'seat_required': _('You need to select a specific seat.'), 'You need to select at least %(min)s add-on from the category %(cat)s for the product %(base)s.',
'seat_invalid': _('Please select a valid seat.'), 'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'seat_forbidden': _('You can not select a seat for this position.'), 'min'
'seat_unavailable': _('The seat you selected has already been taken. Please select a different seat.'), ),
'seat_multiple': _('You can not select the same seat multiple times.'), 'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
'gift_card': _("You entered a gift card instead of a voucher. Gift cards can be entered later on when you're asked for your payment details."), 'addon_only': gettext_lazy('One of the products you selected can only be bought as an add-on to another product.'),
'country_blocked': _('One of the selected products is not available in the selected country.'), 'bundled_only': gettext_lazy('One of the products you selected can only be bought part of a bundle.'),
'seat_required': gettext_lazy('You need to select a specific seat.'),
'seat_invalid': gettext_lazy('Please select a valid seat.'),
'seat_forbidden': gettext_lazy('You can not select a seat for this position.'),
'seat_unavailable': gettext_lazy('The seat you selected has already been taken. Please select a different seat.'),
'seat_multiple': gettext_lazy('You can not select the same seat multiple times.'),
'gift_card': gettext_lazy("You entered a gift card instead of a voucher. Gift cards can be entered later on when you're asked for your payment details."),
'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'),
} }
@@ -320,8 +367,7 @@ class CartManager:
cartsize -= len([1 for op in self._operations if isinstance(op, self.RemoveOperation) if cartsize -= len([1 for op in self._operations if isinstance(op, self.RemoveOperation) if
not op.position.addon_to_id]) not op.position.addon_to_id])
if cartsize > int(self.event.settings.max_items_per_order): if cartsize > int(self.event.settings.max_items_per_order):
# TODO: i18n plurals raise CartError(error_messages['max_items'] % self.event.settings.max_items_per_order)
raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,))
def _check_item_constraints(self, op, current_ops=[]): def _check_item_constraints(self, op, current_ops=[]):
if isinstance(op, (self.AddOperation, self.ExtendOperation)): if isinstance(op, (self.AddOperation, self.ExtendOperation)):
@@ -494,7 +540,7 @@ class CartManager:
def apply_voucher(self, voucher_code: str): def apply_voucher(self, voucher_code: str):
if self._operations: if self._operations:
raise CartError('Applying a voucher to the whole cart should not be combined with other operations.') raise CartError(error_messages['voucher_whole_cart_not_combined'])
try: try:
voucher = self.event.vouchers.get(code__iexact=voucher_code.strip()) voucher = self.event.vouchers.get(code__iexact=voucher_code.strip())
except Voucher.DoesNotExist: except Voucher.DoesNotExist:
@@ -539,7 +585,7 @@ class CartManager:
for voucher, cnt in list(voucher_use_diff.items()): for voucher, cnt in list(voucher_use_diff.items()):
if 0 < cnt < voucher.min_usages_remaining: if 0 < cnt < voucher.min_usages_remaining:
raise CartError( raise CartError(
_(error_messages['voucher_min_usages']) % { error_messages['voucher_min_usages'] % {
'voucher': voucher.code, 'voucher': voucher.code,
'number': voucher.min_usages_remaining, 'number': voucher.min_usages_remaining,
} }
@@ -843,22 +889,16 @@ class CartManager:
for (i, v), c in selected.items(): for (i, v), c in selected.items():
n_per_i[i] += c n_per_i[i] += c
if sum(selected.values()) > iao.max_count: if sum(selected.values()) > iao.max_count:
# TODO: Proper i18n
# TODO: Proper pluralization
raise CartError( raise CartError(
error_messages['addon_max_count'], error_messages['addon_max_count'] % {
{
'base': str(item.name), 'base': str(item.name),
'max': iao.max_count, 'max': iao.max_count,
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
} }
) )
elif sum(selected.values()) < iao.min_count: elif sum(selected.values()) < iao.min_count:
# TODO: Proper i18n
# TODO: Proper pluralization
raise CartError( raise CartError(
error_messages['addon_min_count'], error_messages['addon_min_count'] % {
{
'base': str(item.name), 'base': str(item.name),
'min': iao.min_count, 'min': iao.min_count,
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
@@ -866,8 +906,7 @@ class CartManager:
) )
elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed: elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed:
raise CartError( raise CartError(
error_messages['addon_no_multi'], error_messages['addon_no_multi'] % {
{
'base': str(item.name), 'base': str(item.name),
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
} }
@@ -931,7 +970,7 @@ class CartManager:
if item.max_per_order and count > item.max_per_order: if item.max_per_order and count > item.max_per_order:
raise CartError( raise CartError(
_(error_messages['max_items_per_product']) % { error_messages['max_items_per_product'] % {
'max': item.max_per_order, 'max': item.max_per_order,
'product': item.name 'product': item.name
} }
@@ -945,13 +984,13 @@ class CartManager:
for p in self.positions: for p in self.positions:
if p.item_id == item.pk and p.pk not in removals: if p.item_id == item.pk and p.pk not in removals:
self._operations.append(self.RemoveOperation(position=p)) self._operations.append(self.RemoveOperation(position=p))
err = _(error_messages['min_items_per_product_removed']) % { err = error_messages['min_items_per_product_removed'] % {
'min': item.min_per_order, 'min': item.min_per_order,
'product': item.name 'product': item.name
} }
if not err: if not err:
raise CartError( raise CartError(
_(error_messages['min_items_per_product']) % { error_messages['min_items_per_product'] % {
'min': item.min_per_order, 'min': item.min_per_order,
'product': item.name 'product': item.name
} }
@@ -980,13 +1019,13 @@ class CartManager:
for p in self.positions: for p in self.positions:
if p.voucher_id == voucher.pk and p.pk not in removals: if p.voucher_id == voucher.pk and p.pk not in removals:
self._operations.append(self.RemoveOperation(position=p)) self._operations.append(self.RemoveOperation(position=p))
err = _(error_messages['voucher_min_usages_removed']) % { err = error_messages['voucher_min_usages_removed'] % {
'voucher': voucher.code, 'voucher': voucher.code,
'number': voucher.min_usages_remaining, 'number': voucher.min_usages_remaining,
} }
if not err: if not err:
raise CartError( raise CartError(
_(error_messages['voucher_min_usages']) % { error_messages['voucher_min_usages'] % {
'voucher': voucher.code, 'voucher': voucher.code,
'number': voucher.min_usages_remaining, 'number': voucher.min_usages_remaining,
} }
@@ -1034,11 +1073,11 @@ class CartManager:
if voucher_available_count < 1: if voucher_available_count < 1:
if op.voucher in self._voucher_depend_on_cart: if op.voucher in self._voucher_depend_on_cart:
err = err or _(error_messages['voucher_redeemed_cart']) % self.event.settings.reservation_time err = err or (error_messages['voucher_redeemed_cart'] % self.event.settings.reservation_time)
else: else:
err = err or error_messages['voucher_redeemed'] err = err or error_messages['voucher_redeemed']
elif voucher_available_count < requested_count: elif voucher_available_count < requested_count:
err = err or _(error_messages['voucher_redeemed_partial']) % voucher_available_count err = err or (error_messages['voucher_redeemed_partial'] % voucher_available_count)
available_count = min(quota_available_count, voucher_available_count) available_count = min(quota_available_count, voucher_available_count)
+120 -90
View File
@@ -54,15 +54,13 @@ from django.db.transaction import get_connection
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now from django.utils.timezone import make_aware, now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _, gettext_lazy, ngettext_lazy
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pretix.api.models import OAuthApplication from pretix.api.models import OAuthApplication
from pretix.base.channels import get_all_sales_channels from pretix.base.channels import get_all_sales_channels
from pretix.base.email import get_email_context from pretix.base.email import get_email_context
from pretix.base.i18n import ( from pretix.base.i18n import get_language_without_region, language
LazyLocaleException, get_language_without_region, language,
)
from pretix.base.models import ( from pretix.base.models import (
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership, CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership,
Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User, Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
@@ -101,47 +99,94 @@ from pretix.helpers import OF_SELF
from pretix.helpers.models import modelcopy from pretix.helpers.models import modelcopy
from pretix.helpers.periodic import minimum_interval from pretix.helpers.periodic import minimum_interval
class OrderError(Exception):
def __init__(self, *args):
msg = args[0]
msgargs = args[1] if len(args) > 1 else None
self.args = args
if msgargs:
msg = _(msg) % msgargs
else:
# force msg to string to make sure lazy-translation is done in current locale-context
# otherwise translation might happen in celery-context, which uses default-locale
# also translate with _/gettext to keep it backwards compatible
msg = _(str(msg))
super().__init__(msg)
error_messages = { error_messages = {
'unavailable': _('Some of the products you selected were no longer available. ' 'unavailable': gettext_lazy(
'Please see below for details.'), 'Some of the products you selected were no longer available. '
'in_part': _('Some of the products you selected were no longer available in ' 'Please see below for details.'
'the quantity you selected. Please see below for details.'), ),
'price_changed': _('The price of some of the items in your cart has changed in the ' 'in_part': gettext_lazy(
'meantime. Please see below for details.'), 'Some of the products you selected were no longer available in '
'internal': _("An internal error occurred, please try again."), 'the quantity you selected. Please see below for details.'
'empty': _("Your cart is empty."), ),
'max_items_per_product': _("You cannot select more than %(max)s items of the product %(product)s. We removed the " 'price_changed': gettext_lazy(
"surplus items from your cart."), 'The price of some of the items in your cart has changed in the '
'busy': _('We were not able to process your request completely as the ' 'meantime. Please see below for details.'
'server was too busy. Please try again.'), ),
'not_started': _('The booking period for this event has not yet started.'), 'internal': gettext_lazy("An internal error occurred, please try again."),
'ended': _('The booking period has ended.'), 'empty': gettext_lazy("Your cart is empty."),
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s ' 'max_items_per_product': ngettext_lazy(
'matching products.'), "You cannot select more than %(max)s item of the product %(product)s. We removed the surplus items from your cart.",
'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'), "You cannot select more than %(max)s items of the product %(product)s. We removed the surplus items from your cart.",
'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum ' "max"
'number of times allowed. We removed this item from your cart.'), ),
'voucher_budget_used': _('The voucher code used for one of the items in your cart has already been too often. We ' 'busy': gettext_lazy(
'adjusted the price of the item in your cart.'), 'We were not able to process your request completely as the '
'voucher_expired': _('The voucher code used for one of the items in your cart is expired. We removed this item ' 'server was too busy. Please try again.'
'from your cart.'), ),
'voucher_invalid_item': _('The voucher code used for one of the items in your cart is not valid for this item. We ' 'not_started': gettext_lazy('The booking period for this event has not yet started.'),
'removed this item from your cart.'), 'ended': gettext_lazy('The booking period has ended.'),
'voucher_required': _('You need a valid voucher code to order one of the products.'), 'voucher_min_usages': ngettext_lazy(
'some_subevent_not_started': _('The booking period for one of the events in your cart has not yet started. The ' 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
'affected positions have been removed from your cart.'), 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
'some_subevent_ended': _('The booking period for one of the events in your cart has ended. The affected ' 'number'
'positions have been removed from your cart.'), ),
'seat_invalid': _('One of the seats in your order was invalid, we removed the position from your cart.'), 'voucher_invalid': gettext_lazy('The voucher code used for one of the items in your cart is not known in our database.'),
'seat_unavailable': _('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'), 'voucher_redeemed': gettext_lazy(
'country_blocked': _('One of the selected products is not available in the selected country.'), 'The voucher code used for one of the items in your cart has already been used the maximum '
'not_for_sale': _('You selected a product which is not available for sale.'), 'number of times allowed. We removed this item from your cart.'
'addon_invalid_base': _('You can not select an add-on for the selected product.'), ),
'addon_duplicate_item': _('You can not select two variations of the same add-on product.'), 'voucher_budget_used': gettext_lazy(
'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'), 'The voucher code used for one of the items in your cart has already been too often. We '
'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the ' 'adjusted the price of the item in your cart.'
'product %(base)s.'), ),
'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), 'voucher_expired': gettext_lazy(
'The voucher code used for one of the items in your cart is expired. We removed this item from your cart.'
),
'voucher_invalid_item': gettext_lazy(
'The voucher code used for one of the items in your cart is not valid for this item. We removed this item from your cart.'
),
'voucher_required': gettext_lazy('You need a valid voucher code to order one of the products.'),
'some_subevent_not_started': gettext_lazy(
'The booking period for one of the events in your cart has not yet started. The '
'affected positions have been removed from your cart.'
),
'some_subevent_ended': gettext_lazy(
'The booking period for one of the events in your cart has ended. The affected '
'positions have been removed from your cart.'
),
'seat_invalid': gettext_lazy('One of the seats in your order was invalid, we removed the position from your cart.'),
'seat_unavailable': gettext_lazy('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'),
'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'),
'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'),
'addon_invalid_base': gettext_lazy('You can not select an add-on for the selected product.'),
'addon_duplicate_item': gettext_lazy('You can not select two variations of the same add-on product.'),
'addon_max_count': ngettext_lazy(
'You can select at most %(max)s add-on from the category %(cat)s for the product %(base)s.',
'You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.',
'max'
),
'addon_min_count': ngettext_lazy(
'You need to select at least %(min)s add-on from the category %(cat)s for the product %(base)s.',
'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'min'
),
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
} }
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -157,7 +202,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
enough quota. enough quota.
""" """
if order.status != Order.STATUS_CANCELED: if order.status != Order.STATUS_CANCELED:
raise OrderError('The order was not canceled.') raise OrderError(_('The order was not canceled.'))
with order.event.lock() as now_dt: with order.event.lock() as now_dt:
is_available = order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True, is_available = order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True,
@@ -538,18 +583,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
return order.pk return order.pk
class OrderError(LazyLocaleException):
def __init__(self, *args):
msg = args[0]
msgargs = args[1] if len(args) > 1 else None
self.args = args
if msgargs:
msg = _(msg) % msgargs
else:
msg = _(msg)
super().__init__(msg)
def _check_date(event: Event, now_dt: datetime): def _check_date(event: Event, now_dt: datetime):
if event.presale_start and now_dt < event.presale_start: if event.presale_start and now_dt < event.presale_start:
raise OrderError(error_messages['not_started']) raise OrderError(error_messages['not_started'])
@@ -570,7 +603,6 @@ def _check_date(event: Event, now_dt: datetime):
def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None, def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None,
sales_channel='web', customer=None): sales_channel='web', customer=None):
err = None err = None
errargs = None
_check_date(event, now_dt) _check_date(event, now_dt)
products_seen = Counter() products_seen = Counter()
@@ -607,9 +639,10 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
products_seen[cp.item] += 1 products_seen[cp.item] += 1
if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order: if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order:
err = error_messages['max_items_per_product'] err = error_messages['max_items_per_product'] % {
errargs = {'max': cp.item.max_per_order, 'max': cp.item.max_per_order,
'product': cp.item.name} 'product': cp.item.name
}
delete(cp) delete(cp)
break break
@@ -728,7 +761,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
for voucher, cnt in v_usages.items(): for voucher, cnt in v_usages.items():
if 0 < cnt < voucher.min_usages_remaining: if 0 < cnt < voucher.min_usages_remaining:
raise OrderError(error_messages['voucher_min_usages'], { raise OrderError(error_messages['voucher_min_usages'] % {
'voucher': voucher.code, 'voucher': voucher.code,
'number': voucher.min_usages_remaining, 'number': voucher.min_usages_remaining,
}) })
@@ -791,7 +824,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
cp.save() cp.save()
if err: if err:
raise OrderError(err, errargs) raise OrderError(err)
def _get_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress, def _get_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress,
@@ -1344,22 +1377,26 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
class OrderChangeManager: class OrderChangeManager:
error_messages = { error_messages = {
'product_without_variation': _('You need to select a variation of the product.'), 'product_without_variation': gettext_lazy('You need to select a variation of the product.'),
'quota': _('The quota {name} does not have enough capacity left to perform the operation.'), 'quota': gettext_lazy('The quota {name} does not have enough capacity left to perform the operation.'),
'quota_missing': _('There is no quota defined that allows this operation.'), 'quota_missing': gettext_lazy('There is no quota defined that allows this operation.'),
'product_invalid': _('The selected product is not active or has no price set.'), 'product_invalid': gettext_lazy('The selected product is not active or has no price set.'),
'complete_cancel': _('This operation would leave the order empty. Please cancel the order itself instead.'), 'complete_cancel': gettext_lazy('This operation would leave the order empty. Please cancel the order itself instead.'),
'paid_to_free_exceeded': _('This operation would make the order free and therefore immediately paid, however ' 'paid_to_free_exceeded': gettext_lazy(
'no quota is available.'), 'This operation would make the order free and therefore immediately paid, however '
'addon_to_required': _('This is an add-on product, please select the base position it should be added to.'), 'no quota is available.'
'addon_invalid': _('The selected base position does not allow you to add this product as an add-on.'), ),
'subevent_required': _('You need to choose a subevent for the new position.'), 'addon_to_required': gettext_lazy('This is an add-on product, please select the base position it should be added to.'),
'seat_unavailable': _('The selected seat "{seat}" is not available.'), 'addon_invalid': gettext_lazy('The selected base position does not allow you to add this product as an add-on.'),
'seat_subevent_mismatch': _('You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.'), 'subevent_required': gettext_lazy('You need to choose a subevent for the new position.'),
'seat_required': _('The selected product requires you to select a seat.'), 'seat_unavailable': gettext_lazy('The selected seat "{seat}" is not available.'),
'seat_forbidden': _('The selected product does not allow to select a seat.'), 'seat_subevent_mismatch': gettext_lazy(
'tax_rule_country_blocked': _('The selected country is blocked by your tax rule.'), 'You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.'
'gift_card_change': _('You cannot change the price of a position that has been used to issue a gift card.'), ),
'seat_required': gettext_lazy('The selected product requires you to select a seat.'),
'seat_forbidden': gettext_lazy('The selected product does not allow to select a seat.'),
'tax_rule_country_blocked': gettext_lazy('The selected country is blocked by your tax rule.'),
'gift_card_change': gettext_lazy('You cannot change the price of a position that has been used to issue a gift card.'),
} }
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation')) ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation'))
SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent')) SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent'))
@@ -1766,22 +1803,16 @@ class OrderChangeManager:
for (i, v), c in selected.items(): for (i, v), c in selected.items():
n_per_i[i] += c n_per_i[i] += c
if sum(selected.values()) > iao.max_count: if sum(selected.values()) > iao.max_count:
# TODO: Proper i18n
# TODO: Proper pluralization
raise OrderError( raise OrderError(
error_messages['addon_max_count'], error_messages['addon_max_count'] % {
{
'base': str(item.name), 'base': str(item.name),
'max': iao.max_count, 'max': iao.max_count,
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
} }
) )
elif sum(selected.values()) < iao.min_count: elif sum(selected.values()) < iao.min_count:
# TODO: Proper i18n
# TODO: Proper pluralization
raise OrderError( raise OrderError(
error_messages['addon_min_count'], error_messages['addon_min_count'] % {
{
'base': str(item.name), 'base': str(item.name),
'min': iao.min_count, 'min': iao.min_count,
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
@@ -1789,8 +1820,7 @@ class OrderChangeManager:
) )
elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed: elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed:
raise OrderError( raise OrderError(
error_messages['addon_no_multi'], error_messages['addon_no_multi'] % {
{
'base': str(item.name), 'base': str(item.name),
'cat': str(iao.addon_category.name), 'cat': str(iao.addon_category.name),
} }
@@ -2471,7 +2501,7 @@ def perform_order(self, event: Event, payments: List[dict], positions: List[str]
except LockTimeoutException: except LockTimeoutException:
self.retry() self.retry()
except (MaxRetriesExceededError, LockTimeoutException): except (MaxRetriesExceededError, LockTimeoutException):
raise OrderError(str(error_messages['busy'])) raise OrderError(error_messages['busy'])
_unset = object() _unset = object()
+19
View File
@@ -1308,6 +1308,25 @@ DEFAULTS = {
"the email. Does not affect orders performed through other sales channels."), "the email. Does not affect orders performed through other sales channels."),
) )
}, },
'low_availability_percentage': {
'default': None,
'type': int,
'serializer_class': serializers.IntegerField,
'form_class': forms.IntegerField,
'serializer_kwargs': dict(
min_value=0,
max_value=100,
),
'form_kwargs': dict(
label=_('Low availability threshold'),
help_text=_('If the availability of tickets falls below this percentage, the event (or a date, if it is an '
'event series) will be highlighted to have low availability in the event list or calendar. If '
'you keep this option empty, low availability will not be shown publicly.'),
min_value=0,
max_value=100,
required=False
)
},
'event_list_availability': { 'event_list_availability': {
'default': 'True', 'default': 'True',
'type': bool, 'type': bool,
+1
View File
@@ -507,6 +507,7 @@ class EventSettingsForm(SettingsForm):
'meta_noindex', 'meta_noindex',
'redirect_to_checkout_directly', 'redirect_to_checkout_directly',
'frontpage_subevent_ordering', 'frontpage_subevent_ordering',
'low_availability_percentage',
'event_list_type', 'event_list_type',
'event_list_available_only', 'event_list_available_only',
'frontpage_text', 'frontpage_text',
@@ -314,7 +314,8 @@
{% if sform.event_list_available_only %} {% if sform.event_list_available_only %}
{% bootstrap_field sform.event_list_available_only layout="control" %} {% bootstrap_field sform.event_list_available_only layout="control" %}
{% endif %} {% endif %}
{% bootstrap_field sform.low_availability_percentage layout="control" %}
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %} {% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
{% propagated request.event org_url "meta_noindex" %} {% propagated request.event org_url "meta_noindex" %}
{% bootstrap_field sform.meta_noindex layout="control" %} {% bootstrap_field sform.meta_noindex layout="control" %}
+22 -3
View File
@@ -42,6 +42,7 @@ from itertools import groupby
from urllib.parse import urlsplit from urllib.parse import urlsplit
import bleach import bleach
from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@@ -1025,9 +1026,27 @@ class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
self.request.event.delete() self.request.event.delete()
messages.success(self.request, _('The event has been deleted.')) messages.success(self.request, _('The event has been deleted.'))
return redirect(self.get_success_url()) return redirect(self.get_success_url())
except ProtectedError: except ProtectedError as e:
messages.error(self.request, _('The event could not be deleted as some constraints (e.g. data created by ' err = gettext('The event could not be deleted as some constraints (e.g. data created by plug-ins) do not allow it.')
'plug-ins) do not allow it.'))
app_labels = set()
for e in e.protected_objects:
app_labels.add(type(e)._meta.app_label)
plugin_names = []
for app_label in app_labels:
app = apps.get_app_config(app_label)
if hasattr(app, 'PretixPluginMeta'):
plugin_names.append(str(app.PretixPluginMeta.name))
else:
plugin_names.append(str(app.verbose_name))
if plugin_names:
err += ' ' + gettext(
'Specifically, the following plugins still contain data depends on this event: {plugin_names}'
).format(plugin_names=', '.join(plugin_names))
messages.error(self.request, err)
return self.get(self.request, *self.args, **self.kwargs) return self.get(self.request, *self.args, **self.kwargs)
def get_success_url(self) -> str: def get_success_url(self) -> str:
+3 -1
View File
@@ -280,7 +280,9 @@ class EventWizard(SafeSessionWizardView):
elif event.organizer.settings.event_team_provisioning: elif event.organizer.settings.event_team_provisioning:
t = Team.objects.create( t = Team.objects.create(
organizer=event.organizer, organizer=event.organizer,
name=_('Team {event}').format(event=str(event.name)[:188] + "…" if len(str(event.name)) > 190 else str(event.name)), name=_('Team {event}').format(
event=str(event.name)[:100] + "…" if len(str(event.name)) > 100 else str(event.name)
),
can_change_event_settings=True, can_change_items=True, can_change_event_settings=True, can_change_items=True,
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
can_change_vouchers=True can_change_vouchers=True
+17 -3
View File
@@ -393,9 +393,23 @@ class OrganizerDelete(AdministratorPermissionRequiredMixin, FormView):
self.request.organizer.delete() self.request.organizer.delete()
messages.success(self.request, _('The organizer has been deleted.')) messages.success(self.request, _('The organizer has been deleted.'))
return redirect(self.get_success_url()) return redirect(self.get_success_url())
except ProtectedError: except ProtectedError as e:
messages.error(self.request, _('The organizer could not be deleted as some constraints (e.g. data created by ' err = gettext('The organizer could not be deleted as some constraints (e.g. data created by plug-ins) do not allow it.')
'plug-ins) do not allow it.'))
# Unlike deleting events (which is done by regular backend users), this feature can only be used by sysadmins,
# so we expose more technical / less polished information.
affected_models = set()
for m in e.protected_objects:
affected_models.add(type(m)._meta.label)
if affected_models:
err += ' ' + gettext(
'The following database models still contain data that cannot be deleted automatically: {affected_models}'
).format(
affected_models=', '.join(list(affected_models))
)
messages.error(self.request, err)
return self.get(self.request, *self.args, **self.kwargs) return self.get(self.request, *self.args, **self.kwargs)
def get_success_url(self) -> str: def get_success_url(self) -> str:
+1 -1
View File
@@ -556,7 +556,7 @@ def itemvarquota_select2(request, **kwargs):
if not request.event.has_subevents: if not request.event.has_subevents:
# We are very unlikely to need pagination # We are very unlikely to need pagination
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query)) itemqs = request.event.items.prefetch_related('variations').filter(Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query))
quotaqs = request.event.quotas.filter(name__icontains=query) quotaqs = request.event.quotas.filter(name__icontains=query)
more = False more = False
else: else:
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-09-15 11:22+0000\n" "PO-Revision-Date: 2021-09-15 11:22+0000\n"
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n" "Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2020-12-19 07:00+0000\n" "PO-Revision-Date: 2020-12-19 07:00+0000\n"
"Last-Translator: albert <albert.serra.monner@gmail.com>\n" "Last-Translator: albert <albert.serra.monner@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2023-01-13 08:00+0000\n" "PO-Revision-Date: 2023-01-13 08:00+0000\n"
"Last-Translator: tree <jan.stransky@arnal.cz>\n" "Last-Translator: tree <jan.stransky@arnal.cz>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-12-01 17:00+0000\n" "PO-Revision-Date: 2022-12-01 17:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n" "Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-07-26 08:58+0000\n" "PO-Revision-Date: 2022-07-26 08:58+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
+3
View File
@@ -48,6 +48,7 @@ BezahlCode
Bezahlmethode Bezahlmethode
Blackberry Blackberry
BLIK BLIK
Blogpost
BN BN
Branding Branding
Browsereinstellungen Browsereinstellungen
@@ -165,6 +166,7 @@ Linktext
loszulegen loszulegen
Ltd Ltd
max max
MariaDB
MapQuest MapQuest
Mercado Mercado
Merchandise Merchandise
@@ -206,6 +208,7 @@ Plugin
Plugins Plugins
POS POS
Postfix Postfix
PostgreSQL
prefix prefix
pretix pretix
pretixdesk pretixdesk
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-07-26 08:58+0000\n" "PO-Revision-Date: 2022-07-26 08:58+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/" "Language-Team: German (informal) <https://translate.pretix.eu/projects/"
@@ -48,6 +48,7 @@ BezahlCode
Bezahlmethode Bezahlmethode
Blackberry Blackberry
BLIK BLIK
Blogpost
BN BN
Branding Branding
Browsereinstellungen Browsereinstellungen
@@ -165,6 +166,7 @@ Linktext
loszulegen loszulegen
Ltd Ltd
max max
MariaDB
MapQuest MapQuest
Mercado Mercado
Merchandise Merchandise
@@ -206,6 +208,7 @@ Plugin
Plugins Plugins
POS POS
Postfix Postfix
PostgreSQL
prefix prefix
pretix pretix
pretixdesk pretixdesk
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2019-10-03 19:00+0000\n" "PO-Revision-Date: 2019-10-03 19:00+0000\n"
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n" "Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-11-25 21:00+0000\n" "PO-Revision-Date: 2021-11-25 21:00+0000\n"
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n" "Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-11-10 05:00+0000\n" "PO-Revision-Date: 2021-11-10 05:00+0000\n"
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n" "Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: French\n" "Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2023-01-17 01:00+0000\n" "PO-Revision-Date: 2023-01-17 01:00+0000\n"
"Last-Translator: Maurice Kaag <maurice@kaag.me>\n" "Last-Translator: Maurice Kaag <maurice@kaag.me>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-02-22 22:00+0000\n" "PO-Revision-Date: 2022-02-22 22:00+0000\n"
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n" "Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-09-24 13:54+0000\n" "PO-Revision-Date: 2021-09-24 13:54+0000\n"
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n" "Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2020-01-24 08:00+0000\n" "PO-Revision-Date: 2020-01-24 08:00+0000\n"
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n" "Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-05-08 19:00+0000\n" "PO-Revision-Date: 2022-05-08 19:00+0000\n"
"Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n" "Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-03-15 00:00+0000\n" "PO-Revision-Date: 2022-03-15 00:00+0000\n"
"Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n" "Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-04-06 03:00+0000\n" "PO-Revision-Date: 2022-04-06 03:00+0000\n"
"Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n" "Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n"
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-06-20 02:00+0000\n" "PO-Revision-Date: 2022-06-20 02:00+0000\n"
"Last-Translator: fyksen <fredrik@fyksen.me>\n" "Last-Translator: fyksen <fredrik@fyksen.me>\n"
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/" "Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-10-29 02:00+0000\n" "PO-Revision-Date: 2021-10-29 02:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n" "Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-08-05 04:00+0000\n" "PO-Revision-Date: 2021-08-05 04:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n" "Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/" "Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2019-09-24 19:00+0000\n" "PO-Revision-Date: 2019-09-24 19:00+0000\n"
"Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n" "Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2019-03-19 09:00+0000\n" "PO-Revision-Date: 2019-03-19 09:00+0000\n"
"Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n" "Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/" "Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-11-16 16:12+0000\n" "PO-Revision-Date: 2022-11-16 16:12+0000\n"
"Last-Translator: David Vaz <davidmgvaz@gmail.com>\n" "Last-Translator: David Vaz <davidmgvaz@gmail.com>\n"
"Language-Team: Portuguese (Portugal) <https://translate.pretix.eu/projects/" "Language-Team: Portuguese (Portugal) <https://translate.pretix.eu/projects/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-04-29 04:00+0000\n" "PO-Revision-Date: 2022-04-29 04:00+0000\n"
"Last-Translator: Edd28 <chitu_edy@yahoo.com>\n" "Last-Translator: Edd28 <chitu_edy@yahoo.com>\n"
"Language-Team: Romanian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Romanian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-08-09 13:10+0000\n" "PO-Revision-Date: 2021-08-09 13:10+0000\n"
"Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n" "Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2019-08-27 08:00+0000\n" "PO-Revision-Date: 2019-08-27 08:00+0000\n"
"Last-Translator: Bostjan Marusic <bostjan@brokenbones.si>\n" "Last-Translator: Bostjan Marusic <bostjan@brokenbones.si>\n"
"Language-Team: Slovenian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Slovenian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-03-03 07:00+0000\n" "PO-Revision-Date: 2022-03-03 07:00+0000\n"
"Last-Translator: MaLund13 <mart.lund13@gmail.com>\n" "Last-Translator: MaLund13 <mart.lund13@gmail.com>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n" "PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n" "Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2022-05-10 02:00+0000\n" "PO-Revision-Date: 2022-05-10 02:00+0000\n"
"Last-Translator: Iryna N <in380@nyu.edu>\n" "Last-Translator: Iryna N <in380@nyu.edu>\n"
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
+2
View File
@@ -70,6 +70,7 @@ Lead
Leaflet Leaflet
LLC LLC
MapQuest MapQuest
MariaDB
Mercado Mercado
MOTO MOTO
Multibanco Multibanco
@@ -90,6 +91,7 @@ percentual
personalization personalization
POS POS
postfix postfix
PostgreSQL
prepended prepended
presale presale
pretix pretix
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" 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: 2023-01-30 11:33+0000\n" "POT-Creation-Date: 2023-02-01 13:31+0000\n"
"PO-Revision-Date: 2021-12-03 08:37+0000\n" "PO-Revision-Date: 2021-12-03 08:37+0000\n"
"Last-Translator: ExtremeX-BB <qq754163444@gmail.com>\n" "Last-Translator: ExtremeX-BB <qq754163444@gmail.com>\n"
"Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/" "Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/"
+1 -1
View File
@@ -100,7 +100,7 @@ class BankImportJobViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
if BankImportJob.objects.filter(Q(organizer=request.organizer)).filter( if BankImportJob.objects.filter(Q(organizer=request.organizer)).filter(
state=BankImportJob.STATE_RUNNING, state=BankImportJob.STATE_RUNNING,
created__lte=now() - timedelta(minutes=30) # safety timeout created__gte=now() - timedelta(minutes=30) # safety timeout
).exists(): ).exists():
return Response({'error': ['A job is currently running.']}, status=status.HTTP_409_CONFLICT) return Response({'error': ['A job is currently running.']}, status=status.HTTP_409_CONFLICT)
+4 -4
View File
@@ -521,9 +521,9 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row += [ row += [
str(op.item) + (" – " + str(op.variation.value) if op.variation else ""), str(op.item) + (" – " + str(op.variation.value) if op.variation else ""),
op.price, op.price,
date_format(last_checked_in.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT') last_checked_in.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M')
if last_checked_in else '', if last_checked_in else '',
date_format(last_checked_out.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT') last_checked_out.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M')
if last_checked_out else '', if last_checked_out else '',
_('Yes') if op.auto_checked_in else _('No'), _('Yes') if op.auto_checked_in else _('No'),
] ]
@@ -535,10 +535,10 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row.append(str(op.order.phone) if op.order.phone else '') row.append(str(op.order.phone) if op.order.phone else '')
if self.event.has_subevents: if self.event.has_subevents:
row.append(str(op.subevent.name)) row.append(str(op.subevent.name))
row.append(date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')) row.append(op.subevent.date_from.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M'))
if op.subevent.date_to: if op.subevent.date_to:
row.append( row.append(
date_format(op.subevent.date_to.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT') op.subevent.date_to.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M')
) )
else: else:
row.append('') row.append('')
+9 -14
View File
@@ -631,35 +631,30 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
selected[i, None] = val, price selected[i, None] = val, price
if sum(a[0] for a in selected.values()) > category['max_count']: if sum(a[0] for a in selected.values()) > category['max_count']:
# TODO: Proper pluralization
raise ValidationError( raise ValidationError(
_(error_messages['addon_max_count']), error_messages['addon_max_count'] % {
'addon_max_count',
{
'base': str(form['item'].name), 'base': str(form['item'].name),
'max': category['max_count'], 'max': category['max_count'],
'cat': str(category['category'].name), 'cat': str(category['category'].name),
} },
'addon_max_count'
) )
elif sum(a[0] for a in selected.values()) < category['min_count']: elif sum(a[0] for a in selected.values()) < category['min_count']:
# TODO: Proper pluralization
raise ValidationError( raise ValidationError(
_(error_messages['addon_min_count']), error_messages['addon_min_count'] % {
'addon_min_count',
{
'base': str(form['item'].name), 'base': str(form['item'].name),
'min': category['min_count'], 'min': category['min_count'],
'cat': str(category['category'].name), 'cat': str(category['category'].name),
} },
'addon_min_count',
) )
elif any(sum(v[0] for k, v in selected.items() if k[0] == i) > 1 for i in category['items']) and not category['multi_allowed']: elif any(sum(v[0] for k, v in selected.items() if k[0] == i) > 1 for i in category['items']) and not category['multi_allowed']:
raise ValidationError( raise ValidationError(
_(error_messages['addon_no_multi']), error_messages['addon_no_multi'] % {
'addon_no_multi',
{
'base': str(form['item'].name), 'base': str(form['item'].name),
'cat': str(category['category'].name), 'cat': str(category['category'].name),
} },
'addon_no_multi',
) )
try: try:
validate_cart_addons.send( validate_cart_addons.send(
@@ -21,11 +21,15 @@
<div class="col-md-2 text-right flip"> <div class="col-md-2 text-right flip">
{% if subev.presale_is_running and event.settings.event_list_availability %} {% if subev.presale_is_running and event.settings.event_list_availability %}
{% if subev.best_availability_state == 100 %} {% if subev.best_availability_state == 100 %}
<span class="label label-success">{% trans "Book now" %}</span> {% if subev.best_availability_is_low %}
<span class="label label-success-warning">{% trans "Few tickets left" %}</span>
{% else %}
<span class="label label-success">{% trans "Book now" %}</span>
{% endif %}
{% elif event.settings.waiting_list_enabled and subev.best_availability_state >= 0 %} {% elif event.settings.waiting_list_enabled and subev.best_availability_state >= 0 %}
<span class="label label-warning">{% trans "Waiting list" %}</span> <span class="label label-warning">{% trans "Waiting list" %}</span>
{% elif subev.best_availability_state == 20 %} {% elif subev.best_availability_state == 20 %}
<span class="label label-warning">{% trans "Reserved" %}</span> <span class="label label-danger">{% trans "Reserved" %}</span>
{% elif subev.best_availability_state < 20 %} {% elif subev.best_availability_state < 20 %}
{% if subev.has_paid_item %} {% if subev.has_paid_item %}
<span class="label label-danger">{% trans "Sold out" %}</span> <span class="label label-danger">{% trans "Sold out" %}</span>
@@ -27,7 +27,7 @@
<li><a class="event {% if event.continued %}continued{% endif %} {% spaceless %} <li><a class="event {% if event.continued %}continued{% endif %} {% spaceless %}
{% if event.event.presale_is_running and show_avail %} {% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %} {% if event.event.best_availability_state == 100 %}
available available {% if event.event.best_availability_is_low %} low {% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %} {% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
waitinglist waitinglist
{% elif event.event.best_availability_state == 20 %} {% elif event.event.best_availability_state == 20 %}
@@ -68,7 +68,11 @@
<span class="event-status"> <span class="event-status">
{% if event.event.presale_is_running and show_avail %} {% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %} {% if event.event.best_availability_state == 100 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %} {% if event.event.best_availability_is_low %}
<span class="fa fa-exclamation-circle" aria-hidden="true"></span> {% trans "Few tickets left" %}
{% else %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %} {% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
{% elif event.event.best_availability_state == 20 %} {% elif event.event.best_availability_state == 20 %}
@@ -40,7 +40,7 @@
<a class="event {% if event.continued %}continued{% endif %} {% spaceless %} <a class="event {% if event.continued %}continued{% endif %} {% spaceless %}
{% if event.event.presale_is_running and show_avail %} {% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %} {% if event.event.best_availability_state == 100 %}
available available {% if event.event.best_availability_is_low %} low {% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %} {% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
waitinglist waitinglist
{% elif event.event.best_availability_state == 20 %} {% elif event.event.best_availability_state == 20 %}
@@ -89,12 +89,16 @@
<span class="event-status"> <span class="event-status">
{% if event.event.presale_is_running and show_avail %} {% if event.event.presale_is_running and show_avail %}
{% if event.event.best_availability_state == 100 %} {% if event.event.best_availability_state == 100 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %} {% if event.event.best_availability_is_low %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %} <span class="fa fa-exclamation-circle" aria-hidden="true"></span> {% trans "Few tickets left" %}
{% else %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% endif %}
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
{% elif event.event.best_availability_state == 20 %} {% elif event.event.best_availability_state == 20 %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Reserved" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Reserved" %}
{% elif event.event.best_availability_state < 20 %} {% elif event.event.best_availability_state < 20 %}
{% if event.event.has_paid_item %} {% if event.event.has_paid_item %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sold out" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sold out" %}
{% else %} {% else %}
@@ -104,9 +108,9 @@
{% endif %} {% endif %}
{% elif event.event.presale_is_running %} {% elif event.event.presale_is_running %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
{% elif event.event.presale_has_ended %} {% elif event.event.presale_has_ended %}
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sale over" %} <span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sale over" %}
{% elif event.event.settings.presale_start_show_date and event.event.presale_start %} {% elif event.event.settings.presale_start_show_date and event.event.presale_start %}
<span class="fa fa-ticket" aria-hidden="true"></span> <span class="fa fa-ticket" aria-hidden="true"></span>
{% blocktrans with start_date=event.event.presale_start|date:"SHORT_DATE_FORMAT" %} {% blocktrans with start_date=event.event.presale_start|date:"SHORT_DATE_FORMAT" %}
from {{ start_date }} from {{ start_date }}

Some files were not shown because too many files have changed in this diff Show More