mirror of
https://github.com/pretix/pretix.git
synced 2026-07-17 07:08:38 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ee97a2c29 | |||
| 7b301b6027 | |||
| 68430f01a3 | |||
| 363c62a6ca | |||
| a7f32b8647 | |||
| 8786397910 | |||
| d078a42250 | |||
| f1a73cd440 | |||
| 8bba1a2ea6 | |||
| aeb5c52bfe | |||
| a684aca212 | |||
| 59d46ddded | |||
| e4e7d50659 | |||
| 1d46a96821 | |||
| 8b81ef6f43 | |||
| cb734510ac | |||
| b29f5c69ed | |||
| 56ce37225c | |||
| afae6fdd45 | |||
| 4cad8eae93 | |||
| c5b7ff66b7 | |||
| 04e16bbb39 | |||
| eea48af60a | |||
| fc63f60960 | |||
| 3b94125471 | |||
| 00d901b04b | |||
| a7f9e100d2 | |||
| 59f409b1c6 | |||
| e03bebf5ab |
@@ -26,10 +26,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
name: Check gettext syntax
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -50,10 +50,10 @@ jobs:
|
||||
name: Spellcheck
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -45,10 +45,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -66,10 +66,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- name: Install Dependencies
|
||||
run: pip3 install licenseheaders
|
||||
- name: Run licenseheaders
|
||||
|
||||
@@ -24,22 +24,22 @@ jobs:
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.9", "3.10"]
|
||||
python-version: ["3.9", "3.10", "3.11"]
|
||||
database: [sqlite, postgres, mysql]
|
||||
exclude:
|
||||
- database: mysql
|
||||
python-version: "3.10"
|
||||
- database: mysql
|
||||
python-version: "3.9"
|
||||
- database: mysql
|
||||
python-version: "3.11"
|
||||
- database: sqlite
|
||||
python-version: "3.7"
|
||||
python-version: "3.9"
|
||||
- database: sqlite
|
||||
python-version: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: getong/mariadb-action@v1.1
|
||||
with:
|
||||
mariadb version: '10.4'
|
||||
mariadb version: '10.10'
|
||||
mysql database: 'pretix'
|
||||
mysql root password: ''
|
||||
if: matrix.database == 'mysql'
|
||||
@@ -83,4 +83,4 @@ jobs:
|
||||
file: src/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
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
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-bullseye
|
||||
FROM python:3.11-bullseye
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -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 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 `nodejs`_ installation
|
||||
|
||||
@@ -127,7 +127,7 @@ We now install pretix, its direct dependencies and 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::
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "4.16.0"
|
||||
__version__ = "4.17.0.dev0"
|
||||
|
||||
@@ -36,7 +36,7 @@ import logging
|
||||
import os
|
||||
import string
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import datetime, time, timedelta
|
||||
from operator import attrgetter
|
||||
from urllib.parse import urljoin
|
||||
@@ -340,64 +340,104 @@ class EventMixin:
|
||||
)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
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
|
||||
|
||||
if not hasattr(self, 'active_quotas'):
|
||||
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
|
||||
items_disabled = set(self.disabled_items.split(","))
|
||||
else:
|
||||
items_disabled = set()
|
||||
|
||||
if hasattr(self, 'disabled_vars'): # SubEventItemVariation
|
||||
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', {})
|
||||
quotas_for_item = defaultdict(list)
|
||||
quotas_for_variation = defaultdict(list)
|
||||
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:
|
||||
items_available.update(q.active_items.split(","))
|
||||
if q.active_variations:
|
||||
vars_available.update(q.active_variations.split(","))
|
||||
elif res[0] == Quota.AVAILABILITY_RESERVED:
|
||||
if q.active_items:
|
||||
items_reserved.update(q.active_items.split(","))
|
||||
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(","))
|
||||
if q.active_items:
|
||||
for item_id in q.active_items.split(","):
|
||||
if item_id not in items_disabled:
|
||||
quotas_for_item[item_id].append(q)
|
||||
if q.active_variations:
|
||||
for var_id in q.active_variations.split(","):
|
||||
if var_id not in vars_disabled:
|
||||
quotas_for_variation[var_id].append(q)
|
||||
|
||||
items_available -= items_disabled
|
||||
items_reserved -= items_disabled
|
||||
items_gone -= items_disabled
|
||||
vars_available -= vars_disabled
|
||||
vars_reserved -= vars_disabled
|
||||
vars_gone -= vars_disabled
|
||||
if not self.active_quotas or (not quotas_for_item and not quotas_for_variation):
|
||||
# No item is enabled for this event, treat the event as "unknown"
|
||||
return None, None, None
|
||||
|
||||
if not self.active_quotas or (
|
||||
not items_available and not items_reserved and not items_gone and not vars_gone and not vars_available and not vars_reserved
|
||||
):
|
||||
return None
|
||||
# We iterate over all items and variations and keep track of
|
||||
# - `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
|
||||
# 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:
|
||||
return Quota.AVAILABILITY_OK
|
||||
if items_reserved - items_gone or vars_reserved - vars_gone:
|
||||
return Quota.AVAILABILITY_RESERVED
|
||||
return Quota.AVAILABILITY_GONE
|
||||
if worst_state_for_ticket == 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)
|
||||
num_tickets_found += availability_of_this
|
||||
for q in quota_list:
|
||||
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):
|
||||
qs_annotated = self._seats(ignore_voucher=ignore_voucher)
|
||||
@@ -591,6 +631,7 @@ class Event(EventMixin, LoggedModel):
|
||||
self.settings.invoice_email_attachment = True
|
||||
self.settings.name_scheme = 'given_family'
|
||||
self.settings.payment_banktransfer_invoice_immediately = True
|
||||
self.settings.low_availability_percentage = 10
|
||||
|
||||
@property
|
||||
def social_image(self):
|
||||
|
||||
@@ -1635,7 +1635,7 @@ class OrderPayment(models.Model):
|
||||
logger.info('Failed payment {} but ignored due to likely race condition.'.format(
|
||||
self.full_id,
|
||||
))
|
||||
return
|
||||
return False
|
||||
|
||||
if isinstance(info, str):
|
||||
locked_instance.info = info
|
||||
@@ -1651,6 +1651,7 @@ class OrderPayment(models.Model):
|
||||
'info': info,
|
||||
'data': log_data,
|
||||
}, user=user, auth=auth)
|
||||
return True
|
||||
|
||||
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):
|
||||
|
||||
@@ -862,6 +862,10 @@ class Renderer:
|
||||
self._get_text_content(op, order, o) or "",
|
||||
).replace("\n", "<br/>\n").replace("-", "- ")
|
||||
|
||||
# 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
|
||||
# to resolve all ligatures and python-bidi to switch RTL texts.
|
||||
try:
|
||||
@@ -869,9 +873,6 @@ class Renderer:
|
||||
except:
|
||||
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)
|
||||
w, h = p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
|
||||
# p_size = p.wrap(float(o['width']) * mm, 1000 * mm)
|
||||
|
||||
@@ -43,7 +43,9 @@ from django.db import DatabaseError, transaction
|
||||
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value
|
||||
from django.dispatch import receiver
|
||||
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 pretix.base.channels import get_all_sales_channels
|
||||
@@ -81,74 +83,119 @@ class CartError(Exception):
|
||||
if msgargs:
|
||||
msg = _(msg) % msgargs
|
||||
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)
|
||||
|
||||
|
||||
error_messages = {
|
||||
'busy': _('We were not able to process your request completely as the '
|
||||
'server was too busy. Please try again.'),
|
||||
'empty': _('You did not select any products.'),
|
||||
'unknown_position': _('Unknown cart position.'),
|
||||
'busy': gettext_lazy(
|
||||
'We were not able to process your request completely as the '
|
||||
'server was too busy. Please try again.'
|
||||
),
|
||||
'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.'),
|
||||
'not_for_sale': _('You selected a product which is not available for sale.'),
|
||||
'unavailable': _('Some of the products you selected are no longer available. '
|
||||
'Please see below for details.'),
|
||||
'in_part': _('Some of the products you selected are no longer available in '
|
||||
'the quantity you selected. Please see below for details.'),
|
||||
'max_items': _("You cannot select more than %s items per order."),
|
||||
'max_items_per_product': _("You cannot select more than %(max)s items of the product %(product)s."),
|
||||
'min_items_per_product': _("You need to select at least %(min)s items of the product %(product)s."),
|
||||
'min_items_per_product_removed': _("We removed %(product)s from your cart as you can not buy less than "
|
||||
"%(min)s items of it."),
|
||||
'not_started': _('The booking period for this event has not yet started.'),
|
||||
'ended': _('The booking period for this event has ended.'),
|
||||
'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 '
|
||||
'have been removed from your cart.'),
|
||||
'some_subevent_ended': _('The booking period for one of the events in your cart has ended. The affected '
|
||||
'positions have been removed from your cart.'),
|
||||
'price_too_high': _('The entered price is to high.'),
|
||||
'voucher_invalid': _('This voucher code is not known in our database.'),
|
||||
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s '
|
||||
'matching products.'),
|
||||
'voucher_min_usages_removed': _('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.'),
|
||||
'voucher_redeemed': _('This voucher code has already been used the maximum number of times allowed.'),
|
||||
'voucher_redeemed_cart': _('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': _('This voucher code can only be redeemed %d more times.'),
|
||||
'voucher_double': _('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': _('This voucher is expired.'),
|
||||
'voucher_invalid_item': _('This voucher is not valid for this product.'),
|
||||
'voucher_invalid_seat': _('This voucher is not valid for this seat.'),
|
||||
'voucher_no_match': _('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': _(
|
||||
'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'),
|
||||
'unavailable': gettext_lazy(
|
||||
'Some of the products you selected are no longer available. '
|
||||
'Please see below for details.'
|
||||
),
|
||||
'in_part': gettext_lazy(
|
||||
'Some of the products you selected are no longer available in '
|
||||
'the quantity you selected. Please see below for details.'
|
||||
),
|
||||
'max_items': ngettext_lazy(
|
||||
"You cannot select more than %s item per order.",
|
||||
"You cannot select more than %s items per order."
|
||||
),
|
||||
'max_items_per_product': ngettext_lazy(
|
||||
"You cannot select more than %(max)s item of the product %(product)s.",
|
||||
"You cannot select more than %(max)s items of the product %(product)s.",
|
||||
"max"
|
||||
),
|
||||
'min_items_per_product': ngettext_lazy(
|
||||
"You need to select at least %(min)s item of the product %(product)s.",
|
||||
"You need to select at least %(min)s items of the product %(product)s.",
|
||||
"min"
|
||||
),
|
||||
'min_items_per_product_removed': ngettext_lazy(
|
||||
"We removed %(product)s from your cart as you can not buy less than %(min)s item of it.",
|
||||
"We removed %(product)s from your cart as you can not buy less than %(min)s items of it.",
|
||||
"min"
|
||||
),
|
||||
'not_started': gettext_lazy('The booking period for this event has not yet started.'),
|
||||
'ended': gettext_lazy('The booking period for this event has ended.'),
|
||||
'payment_ended': gettext_lazy('All payments for this event need to be confirmed already, so no new orders can be created.'),
|
||||
'some_subevent_not_started': gettext_lazy(
|
||||
'The booking period for this event 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.'),
|
||||
'price_too_high': gettext_lazy('The entered price is to high.'),
|
||||
'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.'),
|
||||
'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.'),
|
||||
'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.'),
|
||||
'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'),
|
||||
'addon_min_count': _('You need to select at least %(min)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.'),
|
||||
'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.'),
|
||||
'seat_required': _('You need to select a specific seat.'),
|
||||
'seat_invalid': _('Please select a valid seat.'),
|
||||
'seat_forbidden': _('You can not select a seat for this position.'),
|
||||
'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.'),
|
||||
'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."),
|
||||
'country_blocked': _('One of the selected products is not available in the selected country.'),
|
||||
'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.'),
|
||||
'addon_only': gettext_lazy('One of the products you selected can only be bought as an add-on to another product.'),
|
||||
'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
|
||||
not op.position.addon_to_id])
|
||||
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=[]):
|
||||
if isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
||||
@@ -494,7 +540,7 @@ class CartManager:
|
||||
|
||||
def apply_voucher(self, voucher_code: str):
|
||||
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:
|
||||
voucher = self.event.vouchers.get(code__iexact=voucher_code.strip())
|
||||
except Voucher.DoesNotExist:
|
||||
@@ -539,7 +585,7 @@ class CartManager:
|
||||
for voucher, cnt in list(voucher_use_diff.items()):
|
||||
if 0 < cnt < voucher.min_usages_remaining:
|
||||
raise CartError(
|
||||
_(error_messages['voucher_min_usages']) % {
|
||||
error_messages['voucher_min_usages'] % {
|
||||
'voucher': voucher.code,
|
||||
'number': voucher.min_usages_remaining,
|
||||
}
|
||||
@@ -843,22 +889,16 @@ class CartManager:
|
||||
for (i, v), c in selected.items():
|
||||
n_per_i[i] += c
|
||||
if sum(selected.values()) > iao.max_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise CartError(
|
||||
error_messages['addon_max_count'],
|
||||
{
|
||||
error_messages['addon_max_count'] % {
|
||||
'base': str(item.name),
|
||||
'max': iao.max_count,
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
)
|
||||
elif sum(selected.values()) < iao.min_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise CartError(
|
||||
error_messages['addon_min_count'],
|
||||
{
|
||||
error_messages['addon_min_count'] % {
|
||||
'base': str(item.name),
|
||||
'min': iao.min_count,
|
||||
'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:
|
||||
raise CartError(
|
||||
error_messages['addon_no_multi'],
|
||||
{
|
||||
error_messages['addon_no_multi'] % {
|
||||
'base': str(item.name),
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
@@ -931,7 +970,7 @@ class CartManager:
|
||||
|
||||
if item.max_per_order and count > item.max_per_order:
|
||||
raise CartError(
|
||||
_(error_messages['max_items_per_product']) % {
|
||||
error_messages['max_items_per_product'] % {
|
||||
'max': item.max_per_order,
|
||||
'product': item.name
|
||||
}
|
||||
@@ -945,13 +984,13 @@ class CartManager:
|
||||
for p in self.positions:
|
||||
if p.item_id == item.pk and p.pk not in removals:
|
||||
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,
|
||||
'product': item.name
|
||||
}
|
||||
if not err:
|
||||
raise CartError(
|
||||
_(error_messages['min_items_per_product']) % {
|
||||
error_messages['min_items_per_product'] % {
|
||||
'min': item.min_per_order,
|
||||
'product': item.name
|
||||
}
|
||||
@@ -980,13 +1019,13 @@ class CartManager:
|
||||
for p in self.positions:
|
||||
if p.voucher_id == voucher.pk and p.pk not in removals:
|
||||
self._operations.append(self.RemoveOperation(position=p))
|
||||
err = _(error_messages['voucher_min_usages_removed']) % {
|
||||
err = error_messages['voucher_min_usages_removed'] % {
|
||||
'voucher': voucher.code,
|
||||
'number': voucher.min_usages_remaining,
|
||||
}
|
||||
if not err:
|
||||
raise CartError(
|
||||
_(error_messages['voucher_min_usages']) % {
|
||||
error_messages['voucher_min_usages'] % {
|
||||
'voucher': voucher.code,
|
||||
'number': voucher.min_usages_remaining,
|
||||
}
|
||||
@@ -1034,11 +1073,11 @@ class CartManager:
|
||||
|
||||
if voucher_available_count < 1:
|
||||
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:
|
||||
err = err or error_messages['voucher_redeemed']
|
||||
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)
|
||||
|
||||
|
||||
@@ -54,15 +54,13 @@ from django.db.transaction import get_connection
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
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 pretix.api.models import OAuthApplication
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import (
|
||||
LazyLocaleException, get_language_without_region, language,
|
||||
)
|
||||
from pretix.base.i18n import get_language_without_region, language
|
||||
from pretix.base.models import (
|
||||
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership,
|
||||
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.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 = {
|
||||
'unavailable': _('Some of the products you selected were no longer available. '
|
||||
'Please see below for details.'),
|
||||
'in_part': _('Some of the products you selected were no longer available in '
|
||||
'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 '
|
||||
'meantime. Please see below for details.'),
|
||||
'internal': _("An internal error occurred, please try again."),
|
||||
'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 "
|
||||
"surplus items from your cart."),
|
||||
'busy': _('We were not able to process your request completely as the '
|
||||
'server was too busy. Please try again.'),
|
||||
'not_started': _('The booking period for this event has not yet started.'),
|
||||
'ended': _('The booking period has ended.'),
|
||||
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s '
|
||||
'matching products.'),
|
||||
'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'),
|
||||
'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum '
|
||||
'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 '
|
||||
'adjusted the price of the item in your cart.'),
|
||||
'voucher_expired': _('The voucher code used for one of the items in your cart is expired. We removed this item '
|
||||
'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 '
|
||||
'removed this item from your cart.'),
|
||||
'voucher_required': _('You need a valid voucher code to order one of the products.'),
|
||||
'some_subevent_not_started': _('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': _('The booking period for one of the events in your cart has ended. The affected '
|
||||
'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.'),
|
||||
'seat_unavailable': _('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'),
|
||||
'country_blocked': _('One of the selected products is not available in the selected country.'),
|
||||
'not_for_sale': _('You selected a product which is not available for sale.'),
|
||||
'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.'),
|
||||
'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'),
|
||||
'addon_min_count': _('You need to select at least %(min)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.'),
|
||||
'unavailable': gettext_lazy(
|
||||
'Some of the products you selected were no longer available. '
|
||||
'Please see below for details.'
|
||||
),
|
||||
'in_part': gettext_lazy(
|
||||
'Some of the products you selected were no longer available in '
|
||||
'the quantity you selected. Please see below for details.'
|
||||
),
|
||||
'price_changed': gettext_lazy(
|
||||
'The price of some of the items in your cart has changed in the '
|
||||
'meantime. Please see below for details.'
|
||||
),
|
||||
'internal': gettext_lazy("An internal error occurred, please try again."),
|
||||
'empty': gettext_lazy("Your cart is empty."),
|
||||
'max_items_per_product': ngettext_lazy(
|
||||
"You cannot select more than %(max)s item of the product %(product)s. We removed the surplus items from your cart.",
|
||||
"You cannot select more than %(max)s items of the product %(product)s. We removed the surplus items from your cart.",
|
||||
"max"
|
||||
),
|
||||
'busy': gettext_lazy(
|
||||
'We were not able to process your request completely as the '
|
||||
'server was too busy. Please try again.'
|
||||
),
|
||||
'not_started': gettext_lazy('The booking period for this event has not yet started.'),
|
||||
'ended': gettext_lazy('The booking period has ended.'),
|
||||
'voucher_min_usages': ngettext_lazy(
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
|
||||
'number'
|
||||
),
|
||||
'voucher_invalid': gettext_lazy('The voucher code used for one of the items in your cart is not known in our database.'),
|
||||
'voucher_redeemed': gettext_lazy(
|
||||
'The voucher code used for one of the items in your cart has already been used the maximum '
|
||||
'number of times allowed. We removed this item from your cart.'
|
||||
),
|
||||
'voucher_budget_used': gettext_lazy(
|
||||
'The voucher code used for one of the items in your cart has already been too often. We '
|
||||
'adjusted the price of the item in your cart.'
|
||||
),
|
||||
'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__)
|
||||
@@ -157,7 +202,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
|
||||
enough quota.
|
||||
"""
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
if event.presale_start and now_dt < event.presale_start:
|
||||
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,
|
||||
sales_channel='web', customer=None):
|
||||
err = None
|
||||
errargs = None
|
||||
_check_date(event, now_dt)
|
||||
|
||||
products_seen = Counter()
|
||||
@@ -607,9 +639,10 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
|
||||
products_seen[cp.item] += 1
|
||||
if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order:
|
||||
err = error_messages['max_items_per_product']
|
||||
errargs = {'max': cp.item.max_per_order,
|
||||
'product': cp.item.name}
|
||||
err = error_messages['max_items_per_product'] % {
|
||||
'max': cp.item.max_per_order,
|
||||
'product': cp.item.name
|
||||
}
|
||||
delete(cp)
|
||||
break
|
||||
|
||||
@@ -728,7 +761,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
|
||||
for voucher, cnt in v_usages.items():
|
||||
if 0 < cnt < voucher.min_usages_remaining:
|
||||
raise OrderError(error_messages['voucher_min_usages'], {
|
||||
raise OrderError(error_messages['voucher_min_usages'] % {
|
||||
'voucher': voucher.code,
|
||||
'number': voucher.min_usages_remaining,
|
||||
})
|
||||
@@ -791,7 +824,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
cp.save()
|
||||
|
||||
if err:
|
||||
raise OrderError(err, errargs)
|
||||
raise OrderError(err)
|
||||
|
||||
|
||||
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:
|
||||
error_messages = {
|
||||
'product_without_variation': _('You need to select a variation of the product.'),
|
||||
'quota': _('The quota {name} does not have enough capacity left to perform the operation.'),
|
||||
'quota_missing': _('There is no quota defined that allows this operation.'),
|
||||
'product_invalid': _('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.'),
|
||||
'paid_to_free_exceeded': _('This operation would make the order free and therefore immediately paid, however '
|
||||
'no quota is available.'),
|
||||
'addon_to_required': _('This is an add-on product, please select the base position it should be added to.'),
|
||||
'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.'),
|
||||
'seat_unavailable': _('The selected seat "{seat}" is not available.'),
|
||||
'seat_subevent_mismatch': _('You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.'),
|
||||
'seat_required': _('The selected product requires you to select a seat.'),
|
||||
'seat_forbidden': _('The selected product does not allow to select a seat.'),
|
||||
'tax_rule_country_blocked': _('The selected country is blocked by your tax rule.'),
|
||||
'gift_card_change': _('You cannot change the price of a position that has been used to issue a gift card.'),
|
||||
'product_without_variation': gettext_lazy('You need to select a variation of the product.'),
|
||||
'quota': gettext_lazy('The quota {name} does not have enough capacity left to perform the operation.'),
|
||||
'quota_missing': gettext_lazy('There is no quota defined that allows this operation.'),
|
||||
'product_invalid': gettext_lazy('The selected product is not active or has no price set.'),
|
||||
'complete_cancel': gettext_lazy('This operation would leave the order empty. Please cancel the order itself instead.'),
|
||||
'paid_to_free_exceeded': gettext_lazy(
|
||||
'This operation would make the order free and therefore immediately paid, however '
|
||||
'no quota is available.'
|
||||
),
|
||||
'addon_to_required': gettext_lazy('This is an add-on product, please select the base position it should be added to.'),
|
||||
'addon_invalid': gettext_lazy('The selected base position does not allow you to add this product as an add-on.'),
|
||||
'subevent_required': gettext_lazy('You need to choose a subevent for the new position.'),
|
||||
'seat_unavailable': gettext_lazy('The selected seat "{seat}" is not available.'),
|
||||
'seat_subevent_mismatch': gettext_lazy(
|
||||
'You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.'
|
||||
),
|
||||
'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'))
|
||||
SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent'))
|
||||
@@ -1766,22 +1803,16 @@ class OrderChangeManager:
|
||||
for (i, v), c in selected.items():
|
||||
n_per_i[i] += c
|
||||
if sum(selected.values()) > iao.max_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise OrderError(
|
||||
error_messages['addon_max_count'],
|
||||
{
|
||||
error_messages['addon_max_count'] % {
|
||||
'base': str(item.name),
|
||||
'max': iao.max_count,
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
)
|
||||
elif sum(selected.values()) < iao.min_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise OrderError(
|
||||
error_messages['addon_min_count'],
|
||||
{
|
||||
error_messages['addon_min_count'] % {
|
||||
'base': str(item.name),
|
||||
'min': iao.min_count,
|
||||
'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:
|
||||
raise OrderError(
|
||||
error_messages['addon_no_multi'],
|
||||
{
|
||||
error_messages['addon_no_multi'] % {
|
||||
'base': str(item.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:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise OrderError(str(error_messages['busy']))
|
||||
raise OrderError(error_messages['busy'])
|
||||
|
||||
|
||||
_unset = object()
|
||||
|
||||
@@ -1308,6 +1308,25 @@ DEFAULTS = {
|
||||
"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': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
|
||||
@@ -507,6 +507,7 @@ class EventSettingsForm(SettingsForm):
|
||||
'meta_noindex',
|
||||
'redirect_to_checkout_directly',
|
||||
'frontpage_subevent_ordering',
|
||||
'low_availability_percentage',
|
||||
'event_list_type',
|
||||
'event_list_available_only',
|
||||
'frontpage_text',
|
||||
|
||||
@@ -314,7 +314,8 @@
|
||||
{% if sform.event_list_available_only %}
|
||||
{% bootstrap_field sform.event_list_available_only layout="control" %}
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_field sform.low_availability_percentage layout="control" %}
|
||||
|
||||
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
||||
{% propagated request.event org_url "meta_noindex" %}
|
||||
{% bootstrap_field sform.meta_noindex layout="control" %}
|
||||
|
||||
@@ -42,6 +42,7 @@ from itertools import groupby
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import bleach
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -1025,9 +1026,27 @@ class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
|
||||
self.request.event.delete()
|
||||
messages.success(self.request, _('The event has been deleted.'))
|
||||
return redirect(self.get_success_url())
|
||||
except ProtectedError:
|
||||
messages.error(self.request, _('The event could not be deleted as some constraints (e.g. data created by '
|
||||
'plug-ins) do not allow it.'))
|
||||
except ProtectedError as e:
|
||||
err = gettext('The event could not be deleted as some constraints (e.g. data created by 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)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
|
||||
@@ -280,7 +280,9 @@ class EventWizard(SafeSessionWizardView):
|
||||
elif event.organizer.settings.event_team_provisioning:
|
||||
t = Team.objects.create(
|
||||
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_view_orders=True, can_change_orders=True, can_view_vouchers=True,
|
||||
can_change_vouchers=True
|
||||
|
||||
@@ -393,9 +393,23 @@ class OrganizerDelete(AdministratorPermissionRequiredMixin, FormView):
|
||||
self.request.organizer.delete()
|
||||
messages.success(self.request, _('The organizer has been deleted.'))
|
||||
return redirect(self.get_success_url())
|
||||
except ProtectedError:
|
||||
messages.error(self.request, _('The organizer could not be deleted as some constraints (e.g. data created by '
|
||||
'plug-ins) do not allow it.'))
|
||||
except ProtectedError as e:
|
||||
err = gettext('The organizer could not be deleted as some constraints (e.g. data created by 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)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
|
||||
@@ -556,7 +556,7 @@ def itemvarquota_select2(request, **kwargs):
|
||||
|
||||
if not request.event.has_subevents:
|
||||
# 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)
|
||||
more = False
|
||||
else:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: tree <jan.stransky@arnal.cz>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \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"
|
||||
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \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"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
@@ -48,6 +48,7 @@ BezahlCode
|
||||
Bezahlmethode
|
||||
Blackberry
|
||||
BLIK
|
||||
Blogpost
|
||||
BN
|
||||
Branding
|
||||
Browsereinstellungen
|
||||
@@ -165,6 +166,7 @@ Linktext
|
||||
loszulegen
|
||||
Ltd
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
Mercado
|
||||
Merchandise
|
||||
@@ -206,6 +208,7 @@ Plugin
|
||||
Plugins
|
||||
POS
|
||||
Postfix
|
||||
PostgreSQL
|
||||
prefix
|
||||
pretix
|
||||
pretixdesk
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \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"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
|
||||
@@ -48,6 +48,7 @@ BezahlCode
|
||||
Bezahlmethode
|
||||
Blackberry
|
||||
BLIK
|
||||
Blogpost
|
||||
BN
|
||||
Branding
|
||||
Browsereinstellungen
|
||||
@@ -165,6 +166,7 @@ Linktext
|
||||
loszulegen
|
||||
Ltd
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
Mercado
|
||||
Merchandise
|
||||
@@ -206,6 +208,7 @@ Plugin
|
||||
Plugins
|
||||
POS
|
||||
Postfix
|
||||
PostgreSQL
|
||||
prefix
|
||||
pretix
|
||||
pretixdesk
|
||||
|
||||
+833
-745
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\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"
|
||||
"Last-Translator: Maurice Kaag <maurice@kaag.me>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
||||
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n"
|
||||
"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 ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: fyksen <fredrik@fyksen.me>\n"
|
||||
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1\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"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"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 ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n"
|
||||
"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 ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n"
|
||||
"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 ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: David Vaz <davidmgvaz@gmail.com>\n"
|
||||
"Language-Team: Portuguese (Portugal) <https://translate.pretix.eu/projects/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Edd28 <chitu_edy@yahoo.com>\n"
|
||||
"Language-Team: Romanian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n"
|
||||
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Bostjan Marusic <bostjan@brokenbones.si>\n"
|
||||
"Language-Team: Slovenian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: MaLund13 <mart.lund13@gmail.com>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
|
||||
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Iryna N <in380@nyu.edu>\n"
|
||||
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
@@ -70,6 +70,7 @@ Lead
|
||||
Leaflet
|
||||
LLC
|
||||
MapQuest
|
||||
MariaDB
|
||||
Mercado
|
||||
MOTO
|
||||
Multibanco
|
||||
@@ -90,6 +91,7 @@ percentual
|
||||
personalization
|
||||
POS
|
||||
postfix
|
||||
PostgreSQL
|
||||
prepended
|
||||
presale
|
||||
pretix
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: ExtremeX-BB <qq754163444@gmail.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/"
|
||||
|
||||
@@ -100,7 +100,7 @@ class BankImportJobViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
if BankImportJob.objects.filter(Q(organizer=request.organizer)).filter(
|
||||
state=BankImportJob.STATE_RUNNING,
|
||||
created__lte=now() - timedelta(minutes=30) # safety timeout
|
||||
created__gte=now() - timedelta(minutes=30) # safety timeout
|
||||
).exists():
|
||||
return Response({'error': ['A job is currently running.']}, status=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@@ -521,9 +521,9 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
row += [
|
||||
str(op.item) + (" – " + str(op.variation.value) if op.variation else ""),
|
||||
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 '',
|
||||
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 '',
|
||||
_('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 '')
|
||||
if self.event.has_subevents:
|
||||
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:
|
||||
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:
|
||||
row.append('')
|
||||
|
||||
@@ -631,35 +631,30 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
selected[i, None] = val, price
|
||||
|
||||
if sum(a[0] for a in selected.values()) > category['max_count']:
|
||||
# TODO: Proper pluralization
|
||||
raise ValidationError(
|
||||
_(error_messages['addon_max_count']),
|
||||
'addon_max_count',
|
||||
{
|
||||
error_messages['addon_max_count'] % {
|
||||
'base': str(form['item'].name),
|
||||
'max': category['max_count'],
|
||||
'cat': str(category['category'].name),
|
||||
}
|
||||
},
|
||||
'addon_max_count'
|
||||
)
|
||||
elif sum(a[0] for a in selected.values()) < category['min_count']:
|
||||
# TODO: Proper pluralization
|
||||
raise ValidationError(
|
||||
_(error_messages['addon_min_count']),
|
||||
'addon_min_count',
|
||||
{
|
||||
error_messages['addon_min_count'] % {
|
||||
'base': str(form['item'].name),
|
||||
'min': category['min_count'],
|
||||
'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']:
|
||||
raise ValidationError(
|
||||
_(error_messages['addon_no_multi']),
|
||||
'addon_no_multi',
|
||||
{
|
||||
error_messages['addon_no_multi'] % {
|
||||
'base': str(form['item'].name),
|
||||
'cat': str(category['category'].name),
|
||||
}
|
||||
},
|
||||
'addon_no_multi',
|
||||
)
|
||||
try:
|
||||
validate_cart_addons.send(
|
||||
|
||||
@@ -21,11 +21,15 @@
|
||||
<div class="col-md-2 text-right flip">
|
||||
{% if subev.presale_is_running and event.settings.event_list_availability %}
|
||||
{% 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 %}
|
||||
<span class="label label-warning">{% trans "Waiting list" %}</span>
|
||||
{% 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 %}
|
||||
{% if subev.has_paid_item %}
|
||||
<span class="label label-danger">{% trans "Sold out" %}</span>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<li><a class="event {% if event.continued %}continued{% endif %} {% spaceless %}
|
||||
{% if event.event.presale_is_running and show_avail %}
|
||||
{% 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 %}
|
||||
waitinglist
|
||||
{% elif event.event.best_availability_state == 20 %}
|
||||
@@ -68,7 +68,11 @@
|
||||
<span class="event-status">
|
||||
{% if event.event.presale_is_running and show_avail %}
|
||||
{% 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 %}
|
||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Waiting list" %}
|
||||
{% elif event.event.best_availability_state == 20 %}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<a class="event {% if event.continued %}continued{% endif %} {% spaceless %}
|
||||
{% if event.event.presale_is_running and show_avail %}
|
||||
{% 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 %}
|
||||
waitinglist
|
||||
{% elif event.event.best_availability_state == 20 %}
|
||||
@@ -89,12 +89,16 @@
|
||||
<span class="event-status">
|
||||
{% if event.event.presale_is_running and show_avail %}
|
||||
{% if event.event.best_availability_state == 100 %}
|
||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Book now" %}
|
||||
{% elif event.event.settings.waiting_list_enabled and event.event.best_availability_state >= 0 %}
|
||||
{% 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 %}
|
||||
<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" %}
|
||||
{% elif event.event.best_availability_state < 20 %}
|
||||
{% elif event.event.best_availability_state < 20 %}
|
||||
{% if event.event.has_paid_item %}
|
||||
<span class="fa fa-ticket" aria-hidden="true"></span> {% trans "Sold out" %}
|
||||
{% else %}
|
||||
@@ -104,9 +108,9 @@
|
||||
{% endif %}
|
||||
{% elif event.event.presale_is_running %}
|
||||
<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" %}
|
||||
{% 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>
|
||||
{% blocktrans with start_date=event.event.presale_start|date:"SHORT_DATE_FORMAT" %}
|
||||
from {{ start_date }}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user