Compare commits

...

46 Commits

Author SHA1 Message Date
Richard Schreiber
1b876d0a8e PDF: fix export with the same image multiple times 2023-07-21 10:42:42 +02:00
Ronan LE MEILLAT
393a218df5 Translations: Update French
Currently translated at 100.0% (211 of 211 strings)

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

powered by weblate
2023-07-20 20:50:57 +02:00
Ronan LE MEILLAT
f247eb0568 Translations: Update French
Currently translated at 99.9% (5361 of 5362 strings)

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

powered by weblate
2023-07-20 20:50:57 +02:00
Pascal Zimmermann
b35a388685 Add PostgreSQL & Redis TLS/mTLS support (#3435) 2023-07-20 20:50:41 +02:00
Raphael Michel
6dbbfe3b04 Fix test failures caused by b2c49461b 2023-07-20 15:47:10 +02:00
Raphael Michel
b2c49461bc API: Fix validation issue in sendmail rules 2023-07-20 14:29:48 +02:00
Raphael Michel
23dcdf1fd1 Export tasks: Request new database connection after completing output 2023-07-20 11:41:54 +02:00
dependabot[bot]
1f80e9ef82 Bump @babel/preset-env from 7.22.4 to 7.22.9 in /src/pretix/static/npm_dir (#3474)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-17 12:51:47 +02:00
Richard Schreiber
0969abb460 Badges: reduce memory usage when placing multiple per page (Z#23125583) (#3472)
Co-authored-by: Raphael Michel <michel@rami.io>
2023-07-17 12:50:48 +02:00
Freek Engelbarts
7b5789b110 Translations: Update Dutch (informal) (nl_Informal)
Currently translated at 74.2% (3983 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Freek Engelbarts
f3b5996b82 Translations: Update Dutch
Currently translated at 84.6% (4537 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
umarbgs
5dcab59174 Translations: Add Indonesian 2023-07-17 12:16:24 +02:00
Martin Gross
a2e38bb415 Translations: Update Spanish
Currently translated at 57.6% (3093 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Felipe
0510814aae Translations: Update Spanish
Currently translated at 57.6% (3093 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Felipe
dee2818f5d Translations: Update Spanish
Currently translated at 56.2% (3014 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Iria Costas
0d7809c36b Translations: Update Spanish
Currently translated at 56.2% (3014 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Iria Costas
4c494b5265 Translations: Update Spanish
Currently translated at 55.5% (2977 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Felipe
9e85e8c60a Translations: Update Spanish
Currently translated at 55.5% (2977 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
hara metaxa
ab8c71fab8 Translations: Update Greek
Currently translated at 52.6% (2821 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
alemao8
1fa8ea3a12 Translations: Update Greek
Currently translated at 52.5% (2820 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Ronan LE MEILLAT
f584d3d5af Translations: Update French
Currently translated at 99.9% (5361 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Maciej Szymczak
46ae911ade Translations: Update Polish
Currently translated at 14.9% (801 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Thomas Vranken
85db5698a6 Translations: Update Dutch (informal) (nl_Informal)
Currently translated at 74.1% (3978 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Thomas Vranken
09a17b57ce Translations: Update Dutch (Belgium)
Currently translated at 0.1% (1 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Thomas Vranken
826962d6e2 Translations: Update Dutch
Currently translated at 84.4% (4530 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Ronan LE MEILLAT
f77e79bb38 Translations: Update French
Currently translated at 99.9% (5361 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Maurice Kaag
d21e832204 Translations: Update French
Currently translated at 99.9% (5360 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Ronan LE MEILLAT
119d4f0e04 Translations: Update French
Currently translated at 100.0% (211 of 211 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Mossroy
feab6acfbd Translations: Update French
Currently translated at 99.7% (5351 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Ronan LE MEILLAT
d85a6074ec Translations: Update French
Currently translated at 99.7% (5351 of 5362 strings)

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

powered by weblate
2023-07-17 12:16:24 +02:00
Raphael Michel
6c813ea299 Waiting list: Make it harder to accidentally delete full list 2023-07-17 11:54:37 +02:00
Martin Gross
8a903f21ae Stripe/Middleware: Move CSP to signal (#3465) 2023-07-17 11:15:12 +02:00
Kian Cross
a7f7c64cce Add signals for customer account creation and sign in (#3470) 2023-07-17 11:09:05 +02:00
dependabot[bot]
82969daf37 Bump semver from 5.7.1 to 5.7.2 in /src/pretix/static/npm_dir (#3467)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-17 11:08:55 +02:00
Raphael Michel
8e9d0fb723 API: Order position search, add invoice company 2023-07-17 09:37:20 +02:00
Raphael Michel
ef3d44e581 Stripe: Fix crash in rendering of bancontact payments 2023-07-14 16:49:33 +02:00
Raphael Michel
f9055fce9f Disable slow safety mode of reportlab in prod 2023-07-14 16:12:19 +02:00
Raphael Michel
cff0e86fd9 Email settings: Block with invalid SPF setup (#3471) 2023-07-12 12:36:41 +02:00
Raphael Michel
f0913fc720 Fix #3452 -- Encode UUIDs to string before passing through celery (#3463) 2023-07-11 15:36:29 +02:00
dependabot[bot]
23a9f60171 Bump @babel/core from 7.22.1 to 7.22.5 in /src/pretix/static/npm_dir (#3445)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 15:36:18 +02:00
Raphael Michel
faf41c805c Waiting list: Fix display on unlimited quota 2023-07-11 13:38:17 +02:00
Martin Gross
41cded095c PProv: Implement detection of wallets such as Google Pay and Apple Pay (#3444)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-07-11 11:51:43 +02:00
Raphael Michel
90fb034897 Check-in simulator: Fix usage of simulated time in rules 2023-07-11 09:17:02 +02:00
Raphael Michel
f4203b7408 Vouchers: Don't allow to generate more than 100k random codes at once 2023-07-10 15:11:49 +02:00
Richard Schreiber
8a9f14db03 Fix cart sneak-peek on async error 2023-07-07 09:02:53 +02:00
Richard Schreiber
a2adf2825a PDF: fix page-size when mediabox of background-pdf uses offsets 2023-07-04 13:10:27 +02:00
52 changed files with 30562 additions and 1916 deletions

View File

@@ -152,6 +152,10 @@ Example::
password=abcd
host=localhost
port=3306
sslmode=require
sslrootcert=/etc/pretix/postgresql-ca.crt
sslcert=/etc/pretix/postgresql-client-crt.crt
sslkey=/etc/pretix/postgresql-client-key.key
``backend``
One of ``sqlite3`` and ``postgresql``.
@@ -163,6 +167,11 @@ Example::
``user``, ``password``, ``host``, ``port``
Connection details for the database connection. Empty by default.
``sslmode``, ``sslrootcert``
Connection TLS details for the PostgreSQL database connection. Possible values of ``sslmode`` are ``disable``, ``allow``, ``prefer``, ``require``, ``verify-ca``, and ``verify-full``. ``sslrootcert`` should be the accessible path of the ca certificate. Both values are empty by default.
``sslcert``, ``sslkey``
Connection mTLS details for the PostgreSQL database connection. It's also necessary to specify ``sslmode`` and ``sslrootcert`` parameters, please check the correct values from the TLS part. ``sslcert`` should be the accessible path of the client certificate. ``sslkey`` should be the accessible path of the client key. All values are empty by default.
.. _`config-replica`:
Database replica settings
@@ -324,6 +333,10 @@ to speed up various operations::
["sentinel_host_3", 26379]
]
password=password
ssl_cert_reqs=required
ssl_ca_certs=/etc/pretix/redis-ca.pem
ssl_keyfile=/etc/pretix/redis-client-crt.pem
ssl_certfile=/etc/pretix/redis-client-key.key
``location``
The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
@@ -347,6 +360,22 @@ to speed up various operations::
If your redis setup doesn't require a password or you already specified it in the location you can omit this option.
If this is set it will be passed to redis as the connection option PASSWORD.
``ssl_cert_reqs``
If this is set it will be passed to redis as the connection option ``SSL_CERT_REQS``.
Possible values are ``none``, ``optional``, and ``required``.
``ssl_ca_certs``
If your redis setup doesn't require TLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_CA_CERTS``. Possible value is the ca path.
``ssl_keyfile``
If your redis setup doesn't require mTLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_KEYFILE``. Possible value is the keyfile path.
``ssl_certfile``
If your redis setup doesn't require mTLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_CERTFILE``. Possible value is the certfile path.
If redis is not configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis.
@@ -396,6 +425,8 @@ The two ``transport_options`` entries can be omitted in most cases.
If they are present they need to be a valid JSON dictionary.
For possible entries in that dictionary see the `Celery documentation`_.
It is possible the use Redis with TLS/mTLS for the broker or the backend. To do so, it is necessary to specify the TLS identifier ``rediss``, the ssl mode ``ssl_cert_reqs`` and optionally specify the CA (TLS) ``ssl_ca_certs``, cert ``ssl_certfile`` and key ``ssl_keyfile`` (mTLS) path as encoded string. the following uri describes the format and possible parameters ``rediss://0.0.0.0:6379/1?ssl_cert_reqs=required&ssl_ca_certs=%2Fetc%2Fpretix%2Fredis-ca.pem&ssl_certfile=%2Fetc%2Fpretix%2Fredis-client-crt.pem&ssl_keyfile=%2Fetc%2Fpretix%2Fredis-client-key.key``
To use redis with sentinels set the broker or backend to ``sentinel://sentinel_host_1:26379;sentinel_host_2:26379/0``
and the respective transport_options to ``{"master_name":"mymaster"}``.
If your redis instances behind the sentinel have a password use ``sentinel://:my_password@sentinel_host_1:26379;sentinel_host_2:26379/0``.

View File

@@ -61,7 +61,7 @@ Backend
item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display, customer_created, customer_signed_in
Vouchers
""""""""

View File

@@ -70,6 +70,8 @@ The provider class
.. autoattribute:: settings_form_fields
.. autoattribute:: walletqueries
.. automethod:: settings_form_clean
.. automethod:: settings_content_render

View File

@@ -945,6 +945,7 @@ with scopes_disabled():
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__invoice_address__company__icontains=value)
| Q(order__email__icontains=value)
| Q(pk__in=matching_media)
)

View File

@@ -140,7 +140,7 @@ class BaseExporter:
"""
return {}
def render(self, form_data: dict) -> Tuple[str, str, bytes]:
def render(self, form_data: dict) -> Tuple[str, str, Optional[bytes]]:
"""
Render the exported file and return a tuple consisting of a filename, a file type
and file content.

View File

@@ -26,7 +26,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from django.conf import settings
from django.http import Http404, HttpRequest, HttpResponse
from django.middleware.common import CommonMiddleware
from django.urls import get_script_prefix
from django.urls import get_script_prefix, resolve
from django.utils import timezone, translation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
@@ -230,6 +230,8 @@ class SecurityMiddleware(MiddlewareMixin):
)
def process_response(self, request, resp):
url = resolve(request.path_info)
if settings.DEBUG and resp.status_code >= 400:
# Don't use CSP on debug error page as it breaks of Django's fancy error
# pages
@@ -249,20 +251,26 @@ class SecurityMiddleware(MiddlewareMixin):
h = {
'default-src': ["{static}"],
'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
'script-src': ['{static}'],
'object-src': ["'none'"],
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
'frame-src': ['{static}'],
'style-src': ["{static}", "{media}"],
'connect-src': ["{dynamic}", "{media}", "https://checkout.stripe.com"],
'img-src': ["{static}", "{media}", "data:", "https://*.stripe.com"] + img_src,
'connect-src': ["{dynamic}", "{media}"],
'img-src': ["{static}", "{media}", "data:"] + img_src,
'font-src': ["{static}"],
'media-src': ["{static}", "data:"],
# form-action is not only used to match on form actions, but also on URLs
# form-actions redirect to. In the context of e.g. payment providers or
# single-sign-on this can be nearly anything so we cannot really restrict
# single-sign-on this can be nearly anything, so we cannot really restrict
# this. However, we'll restrict it to HTTPS.
'form-action': ["{dynamic}", "https:"] + (['http:'] if settings.SITE_URL.startswith('http://') else []),
}
# Only include pay.google.com for wallet detection purposes on the Payment selection page
if (
url.url_name == "event.order.pay.change" or
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment")
):
h['script-src'].append('https://pay.google.com')
if settings.LOG_CSP:
h['report-uri'] = ["/csp_report/"]
if 'Content-Security-Policy' in resp:

View File

@@ -78,6 +78,16 @@ from pretix.presale.views.cart import cart_session, get_or_create_cart_id
logger = logging.getLogger(__name__)
class WalletQueries:
APPLEPAY = 'applepay'
GOOGLEPAY = 'googlepay'
WALLETS = (
(APPLEPAY, pgettext_lazy('payment', 'Apple Pay')),
(GOOGLEPAY, pgettext_lazy('payment', 'Google Pay')),
)
class PaymentProviderForm(Form):
def clean(self):
cleaned_data = super().clean()
@@ -436,6 +446,19 @@ class BasePaymentProvider:
d['_restrict_to_sales_channels']._as_type = list
return d
@property
def walletqueries(self):
"""
.. warning:: This property is considered **experimental**. It might change or get removed at any time without
prior notice.
A list of wallet payment methods that should be dynamically joined to the public name of the payment method,
if they are available to the user.
The detection is made on a best effort basis with no guarantees of correctness and actual availability.
Wallets that pretix can check for are exposed through ``pretix.base.payment.WalletQueries``.
"""
return []
def settings_form_clean(self, cleaned_data):
"""
Overriding this method allows you to inject custom validation into the settings form.

View File

@@ -48,6 +48,7 @@ from functools import partial
from io import BytesIO
import jsonschema
import reportlab.rl_config
from bidi.algorithm import get_display
from django.conf import settings
from django.contrib.staticfiles import finders
@@ -60,7 +61,8 @@ from django.utils.html import conditional_escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext
from i18nfield.strings import LazyI18nString
from pypdf import PdfReader
from pypdf import PdfReader, PdfWriter, Transformation
from pypdf.generic import RectangleObject
from reportlab.graphics import renderPDF
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.graphics.shapes import Drawing
@@ -85,6 +87,9 @@ from pretix.presale.style import get_fonts
logger = logging.getLogger(__name__)
if not settings.DEBUG:
reportlab.rl_config.shapeChecking = 0
DEFAULT_VARIABLES = OrderedDict((
("secret", {
@@ -861,7 +866,7 @@ class Renderer:
if image_file:
try:
ir = ThumbnailingImageReader(image_file)
ir = ThumbnailingImageReader(image_file.path)
ir.resize(float(o['width']) * mm, float(o['height']) * mm, 300)
canvas.drawImage(
image=ir,
@@ -992,7 +997,10 @@ class Renderer:
elif o['type'] == "poweredby":
self._draw_poweredby(canvas, op, o)
if self.bg_pdf:
page_size = (self.bg_pdf.pages[0].mediabox[2], self.bg_pdf.pages[0].mediabox[3])
page_size = (
self.bg_pdf.pages[0].mediabox[2] - self.bg_pdf.pages[0].mediabox[0],
self.bg_pdf.pages[0].mediabox[3] - self.bg_pdf.pages[0].mediabox[1]
)
if self.bg_pdf.pages[0].get('/Rotate') in (90, 270):
# swap dimensions due to pdf being rotated
page_size = page_size[::-1]
@@ -1020,14 +1028,12 @@ class Renderer:
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
return BytesIO(f.read())
else:
from pypdf import PdfReader, PdfWriter, Transformation
from pypdf.generic import RectangleObject
buffer.seek(0)
new_pdf = PdfReader(buffer)
output = PdfWriter()
for i, page in enumerate(new_pdf.pages):
bg_page = copy.copy(self.bg_pdf.pages[i])
bg_page = copy.deepcopy(self.bg_pdf.pages[i])
bg_rotation = bg_page.get('/Rotate')
if bg_rotation:
# /Rotate is clockwise, transformation.rotate is counter-clockwise
@@ -1064,6 +1070,56 @@ class Renderer:
return outbuffer
def merge_background(fg_pdf, bg_pdf, out_file, compress):
if settings.PDFTK:
with tempfile.TemporaryDirectory() as d:
fg_filename = os.path.join(d, 'fg.pdf')
bg_filename = os.path.join(d, 'bg.pdf')
fg_pdf.write(fg_filename)
bg_pdf.write(bg_filename)
pdftk_cmd = [
settings.PDFTK,
fg_filename,
'multibackground',
bg_filename,
'output',
'-',
]
if compress:
pdftk_cmd.append('compress')
subprocess.run(pdftk_cmd, check=True, stdout=out_file)
else:
output = PdfWriter()
for i, page in enumerate(fg_pdf.pages):
bg_page = copy.deepcopy(bg_pdf.pages[i])
bg_rotation = bg_page.get('/Rotate')
if bg_rotation:
# /Rotate is clockwise, transformation.rotate is counter-clockwise
t = Transformation().rotate(bg_rotation)
w = float(page.mediabox.getWidth())
h = float(page.mediabox.getHeight())
if bg_rotation in (90, 270):
# offset due to rotation base
if bg_rotation == 90:
t = t.translate(h, 0)
else:
t = t.translate(0, w)
# rotate mediabox as well
page.mediabox = RectangleObject((
page.mediabox.left.as_numeric(),
page.mediabox.bottom.as_numeric(),
page.mediabox.top.as_numeric(),
page.mediabox.right.as_numeric(),
))
page.trimbox = page.mediabox
elif bg_rotation == 180:
t = t.translate(w, h)
page.add_transformation(t)
bg_page.merge_page(page)
output.add_page(bg_page)
output.write(out_file)
@deconstructible
class PdfLayoutValidator:
def __call__(self, value):

View File

@@ -86,8 +86,8 @@ def _build_time(t=None, value=None, ev=None, now_dt=None):
return ev.date_admission or ev.date_from
def _logic_annotate_for_graphic_explain(rules, ev, rule_data):
logic_environment = _get_logic_environment(ev)
def _logic_annotate_for_graphic_explain(rules, ev, rule_data, now_dt):
logic_environment = _get_logic_environment(ev, now_dt)
event = ev if isinstance(ev, Event) else ev.event
def _evaluate_inners(r):
@@ -152,7 +152,7 @@ def _logic_explain(rules, ev, rule_data, now_dt=None):
get in before 17:00". In the middle of the night it would switch to "You can only get in after 09:00".
"""
now_dt = now_dt or now()
logic_environment = _get_logic_environment(ev)
logic_environment = _get_logic_environment(ev, now_dt)
_var_values = {'False': False, 'True': True}
_var_explanations = {}
@@ -229,7 +229,7 @@ def _logic_explain(rules, ev, rule_data, now_dt=None):
for vname, data in _var_explanations.items():
var, operator, rhs = data['var'], data['operator'], data['rhs']
if var == 'now':
compare_to = _build_time(*rhs[0]['buildTime'], ev=ev).astimezone(ev.timezone)
compare_to = _build_time(*rhs[0]['buildTime'], ev=ev, now_dt=now_dt).astimezone(ev.timezone)
tolerance = timedelta(minutes=float(rhs[1])) if len(rhs) > 1 and rhs[1] else timedelta(seconds=0)
if operator == 'isBefore':
compare_to += tolerance
@@ -337,7 +337,7 @@ def _logic_explain(rules, ev, rule_data, now_dt=None):
return ', '.join(var_texts[v] for v in paths_with_min_weight[0] if not _var_values[v])
def _get_logic_environment(ev):
def _get_logic_environment(ev, now_dt):
# Every change to our supported JSON logic must be done
# * in pretix.base.services.checkin
# * in pretix.base.models.checkin
@@ -354,7 +354,7 @@ def _get_logic_environment(ev):
logic.add_operation('objectList', lambda *objs: list(objs))
logic.add_operation('lookup', lambda model, pk, str: int(pk))
logic.add_operation('inList', lambda a, b: a in b)
logic.add_operation('buildTime', partial(_build_time, ev=ev))
logic.add_operation('buildTime', partial(_build_time, ev=ev, now_dt=now_dt))
logic.add_operation('isBefore', is_before)
logic.add_operation('isAfter', lambda t1, t2, tol=None: is_before(t2, t1, tol))
return logic
@@ -861,7 +861,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
if type == Checkin.TYPE_ENTRY and clist.rules:
rule_data = LazyRuleVars(op, clist, dt)
logic = _get_logic_environment(op.subevent or clist.event)
logic = _get_logic_environment(op.subevent or clist.event, now_dt=dt)
if not logic.apply(clist.rules, rule_data):
if force:
force_used = True

View File

@@ -26,7 +26,7 @@ from typing import Any, Dict, Union
from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import connection, transaction
from django.db import close_old_connections, connection, transaction
from django.dispatch import receiver
from django.utils.timezone import now, override
from django.utils.translation import gettext
@@ -86,9 +86,12 @@ def export(self, event: Event, fileid: str, provider: str, form_data: Dict[str,
gettext('Your export did not contain any data.')
)
file.filename, file.type, data = d
close_old_connections() # This task can run very long, we might need a new DB connection
f = ContentFile(data)
file.file.save(cachedfile_name(file, file.filename), f)
return file.pk
return str(file.pk)
@app.task(base=ProfiledOrganizerUserTask, throws=(ExportError,), bind=True)
@@ -154,9 +157,12 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
gettext('Your export did not contain any data.')
)
file.filename, file.type, data = d
close_old_connections() # This task can run very long, we might need a new DB connection
f = ContentFile(data)
file.file.save(cachedfile_name(file, file.filename), f)
return file.pk
return str(file.pk)
def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter, config_url, retry_func, has_permission):
@@ -214,6 +220,11 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
raise ExportError(
gettext('Your exported data exceeded the size limit for scheduled exports.')
)
conn = transaction.get_connection()
if not conn.in_atomic_block: # atomic execution only happens during tests or with celery always_eager on
close_old_connections() # This task can run very long, we might need a new DB connection
f = ContentFile(data)
file.file.save(cachedfile_name(file, file.filename), f)
except ExportEmptyError as e:

View File

@@ -787,3 +787,23 @@ return a dictionary mapping names of attributes in the settings store to DRF ser
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
customer_created = GlobalSignal()
"""
Arguments: ``customer``
This signal is sent out every time a customer account is created. The ``customer``
object is given as the first argument.
The ``sender`` keyword argument will contain the organizer.
"""
customer_signed_in = GlobalSignal()
"""
Arguments: ``customer``
This signal is sent out every time a customer signs in. The ``customer`` object
is given as the first argument.
The ``sender`` keyword argument will contain the organizer.
"""

View File

@@ -20,7 +20,7 @@
</div>
<div class="panel-body form-horizontal">
{% if spf_warning %}
<div class="alert alert-warning">
<div class="alert alert-danger">
<p>
{{ spf_warning }}
</p>
@@ -70,10 +70,18 @@
</div>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
{% if spf_warning %}
<div class="form-group submit-group">
<a href="" class="btn btn-default btn-save">
{% trans "Cancel" %}
</a>
</div>
{% else %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
{% endif %}
</form>
{% endblock %}

View File

@@ -17,7 +17,7 @@
placeholder="{% trans "Prefix (optional)" %}">
<div class="input-group">
<input type="number" class="form-control input-xs"
id="voucher-bulk-codes-num"
id="voucher-bulk-codes-num" max="100000"
placeholder="{% trans "Number" context "number_of_things" %}">
<div class="input-group-btn">
<button class="btn btn-default" type="button" id="voucher-bulk-codes-generate"

View File

@@ -212,11 +212,21 @@
<span class="label label-warning">{% trans "Voucher assigned" %}</span>
{% endif %}
{% elif e.availability.0 == 100 %}
<span class="label label-warning">
{% blocktrans with num=e.availability.1 %}
Waiting, product {{ num }}x available
{% endblocktrans %}
</span>
{% if e.availability.1|default_if_none:"none" == "none" %}
<span class="label label-danger" data-toggle="tooltip"
title="{% trans "For safety reasons, the waiting list does not run if the quota is set to unlimited." %}">
<span class="fa fa-ban" aria-hidden="true"></span>
{% blocktrans trimmed %}
Quota unlimited
{% endblocktrans %}
</span>
{% else %}
<span class="label label-warning">
{% blocktrans with num=e.availability.1 %}
Waiting, product {{ num }}x available
{% endblocktrans %}
</span>
{% endif %}
{% else %}
<span class="label label-danger">{% trans "Waiting, product unavailable" %}</span>
{% endif %}
@@ -226,7 +236,7 @@
<a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=e.voucher.pk %}">
{{ e.voucher }}
</a>
{% elif not e.voucher and e.availability.0 == 100 %}
{% elif not e.voucher and e.availability.0 == 100 and e.availability.1|default_if_none:"none" != "none" %}
<button name="assign" value="{{ e.pk }}" class="btn btn-default btn-xs">
{% trans "Send a voucher" %}
</button>

View File

@@ -530,7 +530,8 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
and (self.result["status"] in ("ok", "incomplete") or self.result["reason"] == "rules"):
op = OrderPosition.objects.get(pk=self.result["position"]["id"])
rule_data = LazyRuleVars(op, self.list, form.cleaned_data["datetime"])
rule_graph = _logic_annotate_for_graphic_explain(self.list.rules, op.subevent or self.list.event, rule_data)
rule_graph = _logic_annotate_for_graphic_explain(self.list.rules, op.subevent or self.list.event, rule_data,
form.cleaned_data["datetime"])
self.result["rule_graph"] = rule_graph
if self.result.get("questions"):

View File

@@ -192,8 +192,8 @@ class MailSettingsSetupView(TemplateView):
spf_record = get_spf_record(hostname)
if not spf_record:
spf_warning = _(
'We could not find an SPF record set for the domain you are trying to use. You can still '
'proceed, but it will increase the chance of emails going to spam or being rejected. We '
'We could not find an SPF record set for the domain you are trying to use. This means that '
'there is a very high change most of the emails will be rejected or markes as spam. We '
'strongly recommend setting an SPF record on the domain. You can do so through the DNS '
'settings at the provider you registered your domain with.'
)
@@ -205,7 +205,8 @@ class MailSettingsSetupView(TemplateView):
'this system in the SPF record.'
)
if settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED:
verification = settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED and not spf_warning
if verification:
if 'verification' in self.request.POST:
messages.error(request, _('The verification code was incorrect, please try again.'))
else:
@@ -230,7 +231,7 @@ class MailSettingsSetupView(TemplateView):
context={
'basetpl': self.basetpl,
'object': self.object,
'verification': settings.MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED,
'verification': verification,
'spf_warning': spf_warning,
'spf_record': spf_record,
'spf_key': settings.MAIL_CUSTOM_SENDER_SPF_STRING,

View File

@@ -569,6 +569,8 @@ class VoucherRNG(EventPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
try:
num = int(request.GET.get('num', '5'))
if num > 100_000:
return HttpResponseBadRequest()
except ValueError: # NOQA
return HttpResponseBadRequest()

View File

@@ -91,7 +91,7 @@ class WaitingListQuerySetMixin:
return self.request.POST
return self.request.GET
def get_queryset(self):
def get_queryset(self, force_filtered=False):
qs = WaitingListEntry.objects.filter(
event=self.request.event
).select_related('item', 'variation', 'voucher').prefetch_related(
@@ -135,6 +135,8 @@ class WaitingListQuerySetMixin:
qs = qs.filter(
id__in=self.request_data.getlist('entry')
)
elif force_filtered and '__ALL' not in self.request_data:
qs = qs.none()
return qs
@@ -158,7 +160,7 @@ class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMix
'forbidden': self.get_queryset().filter(voucher__isnull=False),
})
elif request.POST.get('action') == 'delete_confirm':
for obj in self.get_queryset():
for obj in self.get_queryset(force_filtered=True):
if not obj.voucher_id:
obj.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user)
obj.delete()

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-03-07 03:00+0000\n"
"Last-Translator: alemao8 <alevizosfotis@gmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix/el/"
">\n"
"PO-Revision-Date: 2023-07-11 11:38+0000\n"
"Last-Translator: hara metaxa <metaxahara@gmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix/el/>"
"\n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.15.2\n"
"X-Generator: Weblate 4.17\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -31444,17 +31444,13 @@ msgid "Cart expired"
msgstr "Το καλάθι έληξε"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:37
#, fuzzy
#| msgid "Show information"
msgid "Show full cart"
msgstr "Εμφάνιση πληροφοριών"
msgstr "Εμφάνιση καλαθιού"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:49
#: pretix/presale/templates/pretixpresale/event/index.html:78
#, fuzzy
#| msgid "This file is from a different event."
msgid "Add tickets for a different date"
msgstr "Αυτό το αρχείο προέρχεται από διαφορετική εκδήλωση."
msgstr "Προσθέστε εισιτήρια για διαφορετική ημερομηνία"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:7
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:9
@@ -31849,10 +31845,8 @@ msgstr "από %(minprice)s"
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:97
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:89
#, fuzzy
#| msgid "Show variants"
msgid "Hide variants"
msgstr "Εμφάνιση παραλλαγών"
msgstr "Απόκρυψη παραλλαγών"
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:99
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:91
@@ -32197,12 +32191,12 @@ msgstr ""
"Τα στοιχεία του καλαθιού σας είναι στη διάθεσή σας για %(minutes)s λεπτά."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:486
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr "Τα αντικείμενα στο καλάθι σας δεν είναι πλέον αποκλειστικά για εσάς."
msgstr ""
"Τα αντικείμενα στο καλάθι σας δεν είναι πλέον διαθέσιμα. Μπορείτε να "
"επαναλάβετε την διαδικασία εφόσον είναι διαθέσιμα."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:490
msgid "Overview of your ordered products."

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-27 07:11+0000\n"
"Last-Translator: Jonathan Berger <drskullster@gmail.com>\n"
"PO-Revision-Date: 2023-07-19 17:00+0000\n"
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
"Language: fr\n"
@@ -528,28 +528,20 @@ msgid "Test-Mode of shop has been deactivated"
msgstr "Le mode de test de la boutique a été désactivé"
#: pretix/api/webhooks.py:339
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry added"
msgstr "Saisie de la liste d'attente"
msgstr "Ajout d'une inscription sur la liste d'attente"
#: pretix/api/webhooks.py:343
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry changed"
msgstr "Saisie de la liste d'attente"
msgstr "Changement d'une inscription sur la liste d'attente"
#: pretix/api/webhooks.py:347
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry deleted"
msgstr "Saisie de la liste d'attente"
msgstr "Suppression d'une inscription de la liste d'attente"
#: pretix/api/webhooks.py:351
#, fuzzy
#| msgid "Waiting list entries"
msgid "Waiting list entry received voucher"
msgstr "Entrées de liste d'attente"
msgstr "Bon dinscription sur la liste dattente reçu"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
@@ -3406,7 +3398,7 @@ msgstr "Interdit par une règle personnalisée"
#: pretix/base/models/checkin.py:347
msgid "Ticket code revoked/changed"
msgstr "Code du billet révoqué/changé"
msgstr "Code du billet révoqué/modifié"
#: pretix/base/models/checkin.py:348
msgid "Information required"
@@ -3426,7 +3418,7 @@ msgstr "Le code du billet est ambigu dans la liste"
#: pretix/base/models/checkin.py:352
msgid "Server error"
msgstr "Erreur du serveur"
msgstr "Erreur côté serveur"
#: pretix/base/models/checkin.py:353
msgid "Ticket blocked"
@@ -3932,11 +3924,11 @@ msgstr ""
#: pretix/base/models/exports.py:64
msgid "Additional recipients (Cc)"
msgstr "Destinataires supplémentaires (Cc)"
msgstr "Destinataires supplémentaires (copie)"
#: pretix/base/models/exports.py:69
msgid "Additional recipients (Bcc)"
msgstr "Destinataires supplémentaires (Bcc)"
msgstr "Destinataires supplémentaires (copie cachée)"
#: pretix/base/models/exports.py:74 pretix/control/forms/event.py:1045
#: pretix/control/forms/event.py:1107 pretix/control/forms/event.py:1119
@@ -4224,9 +4216,9 @@ msgid ""
"product as an add-on product, but only for fixed bundles!"
msgstr ""
"Si cette option est définie, le produit ne sera vendu que dans le cadre de "
"produits groupés. <strong>Cochez</strong> cette option si vous souhaitez "
"utiliser ce produit pour des offres groupées, pas en tant que produit "
"complémentaire."
"produits groupés. <strong>Ne cochez pas</strong> cette option si vous "
"souhaitez utiliser ce produit pour des offres groupées, pas en tant que "
"produit complémentaire."
#: pretix/base/models/items.py:527
msgid ""
@@ -5530,12 +5522,8 @@ msgid "Seat {number}"
msgstr "Siège {number}"
#: pretix/base/models/tax.py:157
#, fuzzy
#| msgid "Your layout file is not a valid layout. Error message: {}"
msgid "Your set of rules is not valid. Error message: {}"
msgstr ""
"Votre fichier de mise en page nest pas une mise en page valide. Message "
"derreur : {}"
msgstr "Votre ensemble de règles n'est pas valide. Message d'erreur : {}"
#: pretix/base/models/tax.py:168
msgid "Official name"
@@ -6673,9 +6661,8 @@ msgid "Attendee country"
msgstr "Pays du participant"
#: pretix/base/pdf.py:211
#, fuzzy
msgid "Pseudonymization ID (lead scanning)"
msgstr "ID pseudonyme"
msgstr "ID de pseudonymisation (balayage principal)"
#: pretix/base/pdf.py:217 pretix/base/pdf.py:222
msgid "Sample event name"
@@ -8048,11 +8035,8 @@ msgstr ""
"réessayer."
#: pretix/base/services/shredder.py:177
#, fuzzy
#| msgctxt "paypal"
#| msgid "Capture completed."
msgid "Data shredding completed"
msgstr "Capture terminée."
msgstr "Déchiquetage de données terminé"
#: pretix/base/services/stats.py:210
msgid "Uncategorized"
@@ -8762,7 +8746,7 @@ msgstr ""
#: pretix/base/settings.py:934
msgid "Accept late payments"
msgstr "Accepter les paiements en retard"
msgstr "Accepter les retards de paiement"
#: pretix/base/settings.py:935
msgid ""
@@ -11459,6 +11443,23 @@ msgid ""
"\n"
"Your pretix team\n"
msgstr ""
"Bonjour\n"
"\n"
"Nous confirmons par la présente que le travail de déchiquetage de données "
"suivant a été effectué:\n"
"\n"
"Organisateur: %(organizer)s\n"
"\n"
"Evénement: %(event)s\n"
"\n"
"Sélection des données: %(shredders)s\n"
"\n"
"Heure de début: %(start_time)s (les nouvelles données ajoutées après cette "
"heure nont peut-être pas été supprimées)\n"
"\n"
"Sinceres salutations\n"
"\n"
"Votre équipe pretix\n"
#: pretix/base/templates/pretixbase/forms/widgets/portrait_image.html:10
msgid "Upload photo"
@@ -12069,16 +12070,16 @@ msgstr "Saisie de texte libre"
#: pretix/control/forms/event.py:666
msgid "Do not ask"
msgstr "Ne pas demandez"
msgstr "Ne pas demander"
#: pretix/control/forms/event.py:667
msgid "Ask, but do not require input"
msgstr "Demandez, mais navez pas besoin dentrée"
msgstr "Demander, mais ne pas exiger de saisie"
#: pretix/control/forms/event.py:668
#: pretix/control/templates/pretixcontrol/event/settings.html:74
msgid "Ask and require input"
msgstr "Demandez et requérez la saise"
msgstr "Demander et exiger la saisie"
#: pretix/control/forms/event.py:740
msgid ""
@@ -13015,7 +13016,7 @@ msgstr "Appareils révoqués"
#: pretix/control/forms/global_settings.py:59
msgid "Additional footer text"
msgstr "Texte de pied de page supplémentaire"
msgstr "Texte supplémentaire en bas de page"
#: pretix/control/forms/global_settings.py:60
msgid "Will be included as additional text in the footer, site-wide."
@@ -14829,11 +14830,11 @@ msgstr "Un événement a été supprimé."
#: pretix/control/logdisplay.py:375
msgid "A removal process for personal data has been started."
msgstr ""
msgstr "Un processus de suppression des données personnelles a été lancé."
#: pretix/control/logdisplay.py:376
msgid "A removal process for personal data has been completed."
msgstr ""
msgstr "Un processus de suppression des données personnelles a été achevé."
#: pretix/control/logdisplay.py:377
msgid "The order details have been changed."
@@ -15889,7 +15890,7 @@ msgstr "Tous les utilisateurs"
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html:5
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html:7
msgid "Admin sessions"
msgstr "Sessions d'administration"
msgstr "Sessions Admin"
#: pretix/control/navigation.py:427
#: pretix/control/templates/pretixcontrol/global_settings_base.html:5
@@ -18380,7 +18381,7 @@ msgstr ""
"Vous pouvez ici définir un ensemble de propriétés de métadonnées (cest-à-"
"dire des variables) que vous pouvez définir ultérieurement pour vos articles "
"et réutiliser dans des endroits tels que les mises en page de tickets. Cest "
"un gain de temps utile si vous créez beaucoup, beaucoup déléments."
"un gain de temps précieux si vous créez beaucoup d'articles."
#: pretix/control/templates/pretixcontrol/event/settings.html:389
#: pretix/control/templates/pretixcontrol/event/settings.html:417
@@ -19012,7 +19013,7 @@ msgstr "Type de produit"
#: pretix/control/templates/pretixcontrol/item/create.html:25
#: pretix/control/templates/pretixcontrol/item/index.html:33
msgid "Admission product"
msgstr "Produit dadmission"
msgstr "Produit d'admission"
#: pretix/control/templates/pretixcontrol/item/create.html:27
#: pretix/control/templates/pretixcontrol/item/index.html:35
@@ -19275,7 +19276,7 @@ msgstr "Nouvelle variante"
#: pretix/control/templates/pretixcontrol/item/include_variations.html:215
msgid "Add a new variation"
msgstr "Ajouter une nouvelle variante"
msgstr "Ajouter une nouvelle variation"
#: pretix/control/templates/pretixcontrol/item/index.html:153
msgid "Availability"
@@ -19565,7 +19566,7 @@ msgstr "Billet dentrée personnalisé"
#: pretix/control/templates/pretixcontrol/items/index.html:87
msgid "Admission ticket without personalization"
msgstr "Billet dentrée sans personnalisation"
msgstr "Billet d'entrée sans personnalisation"
#: pretix/control/templates/pretixcontrol/items/index.html:95
msgid "Product with variations"
@@ -21598,9 +21599,8 @@ msgstr ""
"événements."
#: pretix/control/templates/pretixcontrol/organizers/customer.html:79
#, fuzzy
msgid "Lifetime spending"
msgstr "Paiement en attente"
msgstr "Dépenses à vie"
#: pretix/control/templates/pretixcontrol/organizers/customer.html:101
#: pretix/control/templates/pretixcontrol/organizers/customer_anonymize.html:39
@@ -23036,6 +23036,9 @@ msgid ""
"Depending on the amount of data in your event, the following step may take a "
"while to complete. We will inform you via email once it has been completed."
msgstr ""
"Selon la quantité de données de votre événement, létape suivante peut "
"prendre un certain temps. Nous vous informerons par e-mail une fois quil "
"aura été complété."
#: pretix/control/templates/pretixcontrol/shredder/index.html:11
msgid ""
@@ -23628,7 +23631,7 @@ msgstr "On"
#: pretix/control/templates/pretixcontrol/user/notifications.html:71
#: pretix/control/templates/pretixcontrol/user/settings.html:24
msgid "Off"
msgstr "Off"
msgstr "Désactivé"
#: pretix/control/templates/pretixcontrol/user/notifications.html:75
msgid "You have no permission to receive this notification"
@@ -26569,8 +26572,8 @@ msgid ""
"We will assign you a personal reference code to use after you completed the "
"order."
msgstr ""
"Nous vous assignerons un code de référence personnel à utiliser après que "
"vous ayez complété la commande."
"Nous vous assignerons un code de référence personnel à utiliser après avoir "
"complété la commande."
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_confirm.html:35
#, python-format
@@ -27096,9 +27099,8 @@ msgstr ""
"liste denregistrement."
#: pretix/plugins/checkinlists/exporters.py:479
#, fuzzy
msgid "Checked out"
msgstr "Paiement"
msgstr "Passé en caisse"
#: pretix/plugins/checkinlists/exporters.py:479
#: pretix/plugins/checkinlists/exporters.py:670
@@ -27385,11 +27387,11 @@ msgid ""
"payment methods world-wide."
msgstr ""
"Acceptez les paiements avec votre compte PayPal. En plus des paiements "
"PayPal réguliers, vous pouvez désormais également proposer des paiements "
"dans une variété de méthodes de paiement locales telles que giropay, SOFORT, "
"iDEAL et bien dautres à vos clients - ils nont même pas besoin dun compte "
"PayPal. PayPal est lune des méthodes de paiement les plus populaires au "
"monde."
"PayPal standards, vous pouvez désormais également proposer des paiements "
"dans une variété de méthodes de paiement locales à vos clients telles que "
"giropay, SOFORT, iDEAL et bien dautres - ils nont même pas besoin dun "
"compte PayPal. PayPal est lune des méthodes de paiement les plus populaires "
"au monde."
#: pretix/plugins/paypal2/payment.py:95
msgid "PayPal Merchant ID"
@@ -28329,10 +28331,9 @@ msgid "Create a new rule"
msgstr "Créer une nouvelle règle"
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:33
#, fuzzy
msgctxt "subevent"
msgid "Sent / Total dates"
msgstr "date de début d'événement"
msgstr "Envoyés / Dates total"
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:52
msgid "Next execution:"

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-17 09:09+0000\n"
"PO-Revision-Date: 2023-07-19 17:00+0000\n"
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n"
@@ -281,7 +281,7 @@ msgstr "Entrée non autorisée"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
msgid "Ticket code revoked/changed"
msgstr "Code du billet révoqué/changé"
msgstr "Code du billet révoqué/modifié"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
msgid "Ticket blocked"

File diff suppressed because it is too large Load Diff

View File

@@ -7,10 +7,10 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-07 15:04+0000\n"
"Last-Translator: Thomas Vranken <thvranken@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
">\n"
"PO-Revision-Date: 2023-07-16 22:00+0000\n"
"Last-Translator: Freek Engelbarts <freekengelbarts@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -40,7 +40,7 @@ msgstr "Chinees (versimpeld)"
#: pretix/_base_settings.py:83
msgid "Chinese (traditional)"
msgstr ""
msgstr "Chinees (traditioneel)"
#: pretix/_base_settings.py:84
msgid "Czech"
@@ -522,22 +522,16 @@ msgid "Test-Mode of shop has been deactivated"
msgstr "Testmode van winkel gedeactiveerd"
#: pretix/api/webhooks.py:339
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry added"
msgstr "Wachtlijstitem"
msgstr "Wachtlijstitem toegevoegd"
#: pretix/api/webhooks.py:343
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry changed"
msgstr "Wachtlijstitem"
msgstr "Wachtlijstitem aangepast"
#: pretix/api/webhooks.py:347
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry deleted"
msgstr "Wachtlijstitem"
msgstr "Wachtlijstitem verwijderd"
#: pretix/api/webhooks.py:351
#, fuzzy
@@ -1679,7 +1673,7 @@ msgstr "Toon alleen met geldig lidmaatschap"
#: pretix/base/exporters/json.py:51 pretix/base/exporters/orderlist.py:85
msgid "Order data"
msgstr "Besteldatums"
msgstr "Bestelgegevens"
#: pretix/base/exporters/json.py:53
msgid ""
@@ -3199,11 +3193,9 @@ msgid "Modern Invoice Renderer (pretix 2.7)"
msgstr "Moderne factuurrenderer (pretix 2.7)"
#: pretix/base/invoice.py:947
#, fuzzy
#| msgid "Please enter a valid state."
msgctxt "invoice"
msgid "(Please quote at all times.)"
msgstr "Kies een geldige staat."
msgstr "(Gelieve steeds te vermelden.)"
#: pretix/base/media.py:58
msgid "Barcode / QR-Code"
@@ -6559,7 +6551,7 @@ msgstr "Productcategorie"
#: pretix/base/pdf.py:151 pretix/base/pdf.py:156
msgid "123.45 EUR"
msgstr "123,45 EUR"
msgstr "123,45"
#: pretix/base/pdf.py:155
msgid "Price including add-ons"
@@ -16069,7 +16061,7 @@ msgstr "Meldingen"
#: pretix/control/navigation.py:390
msgid "2FA"
msgstr "2FA"
msgstr "tweefactorauthenticatie"
#: pretix/control/navigation.py:395
msgid "Authorized apps"
@@ -20400,7 +20392,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html:250
msgid ""
msgstr ""
msgstr "-"
#: pretix/control/templates/pretixcontrol/order/change.html:274
#, fuzzy
@@ -31047,10 +31039,8 @@ msgstr "Toon volgende week, %(week)s"
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:57
#: pretix/presale/templates/pretixpresale/organizers/index.html:85
#: pretix/presale/views/widget.py:376
#, fuzzy
#| msgid "PDF ticket layout"
msgid "Few tickets left"
msgstr "PDF-ticketlay-out"
msgstr "Laatste kaarten"
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html:28
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:75
@@ -31058,11 +31048,9 @@ msgstr "PDF-ticketlay-out"
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:60
#: pretix/presale/templates/pretixpresale/organizers/index.html:88
#: pretix/presale/views/widget.py:381
#, fuzzy
#| msgid "Pay now"
msgctxt "available_event_in_list"
msgid "Buy now"
msgstr "Betaal nu"
msgstr "Koop nu"
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html:30
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html:45

View File

@@ -8,14 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"PO-Revision-Date: 2023-07-02 06:00+0000\n"
"Last-Translator: Thomas Vranken <thvranken@gmail.com>\n"
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_BE/>\n"
"Language: nl_BE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -3078,7 +3080,7 @@ msgstr ""
#: pretix/base/invoice.py:947
msgctxt "invoice"
msgid "(Please quote at all times.)"
msgstr ""
msgstr "(Gelieve steeds te vermelden.)"
#: pretix/base/media.py:58
msgid "Barcode / QR-Code"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-02-23 23:00+0000\n"
"Last-Translator: Toon Toetenel <toon@toetenel.com>\n"
"PO-Revision-Date: 2023-07-16 22:00+0000\n"
"Last-Translator: Freek Engelbarts <freekengelbarts@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_Informal/>\n"
"Language: nl_Informal\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.15.2\n"
"X-Generator: Weblate 4.17\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -41,11 +41,11 @@ msgstr "Chinees (versimpeld)"
#: pretix/_base_settings.py:83
msgid "Chinese (traditional)"
msgstr ""
msgstr "Chinees (traditioneel)"
#: pretix/_base_settings.py:84
msgid "Czech"
msgstr ""
msgstr "Tsjechisch"
#: pretix/_base_settings.py:85
msgid "Danish"
@@ -69,7 +69,7 @@ msgstr "Fins"
#: pretix/_base_settings.py:90
msgid "Galician"
msgstr ""
msgstr "Galicisch"
#: pretix/_base_settings.py:91
msgid "Greek"
@@ -97,7 +97,7 @@ msgstr "Portugees (Brazilië)"
#: pretix/_base_settings.py:97
msgid "Romanian"
msgstr ""
msgstr "Roemeens"
#: pretix/_base_settings.py:98
msgid "Russian"
@@ -113,7 +113,7 @@ msgstr "Turks"
#: pretix/_base_settings.py:101
msgid "Ukrainian"
msgstr ""
msgstr "Oekraïens"
#: pretix/api/auth/devicesecurity.py:31
msgid ""
@@ -762,17 +762,17 @@ msgstr "Antwoorden op vragen"
#: pretix/base/exporters/orderlist.py:1189
#: pretix/plugins/reports/exporters.py:451
#: pretix/plugins/reports/exporters.py:624
#, fuzzy
#| msgid "Order data"
msgctxt "export_category"
msgid "Order data"
msgstr "Besteldatums"
msgstr "Bestelgegevens"
#: pretix/base/exporters/answers.py:54
msgid ""
"Download a ZIP file including all files that have been uploaded by your "
"customers while creating an order."
msgstr ""
"Download een ZIP-bestand dat alle bestanden bevat die bij bestellingen als "
"antwoord op een vraag geüpload werden."
#: pretix/base/exporters/answers.py:64 pretix/base/models/items.py:1565
#: pretix/control/navigation.py:182
@@ -1709,7 +1709,7 @@ msgstr "Verberg zonder geldig lidmaatschap"
#: pretix/base/exporters/json.py:51 pretix/base/exporters/orderlist.py:85
msgid "Order data"
msgstr "Besteldatums"
msgstr "Bestelgegevens"
#: pretix/base/exporters/json.py:53
msgid ""
@@ -2305,10 +2305,8 @@ msgid "Transaction time"
msgstr "Transactiecode"
#: pretix/base/exporters/orderlist.py:833
#, fuzzy
#| msgid "Order data"
msgid "Old data"
msgstr "Besteldatums"
msgstr "Oude gegevens"
#: pretix/base/exporters/orderlist.py:836 pretix/base/models/items.py:1361
#: pretix/control/templates/pretixcontrol/order/transactions.html:22
@@ -3249,11 +3247,9 @@ msgid "Modern Invoice Renderer (pretix 2.7)"
msgstr "Moderne factuurrenderer (pretix 2.7)"
#: pretix/base/invoice.py:947
#, fuzzy
#| msgid "Please enter a valid state."
msgctxt "invoice"
msgid "(Please quote at all times.)"
msgstr "Kies een geldige staat."
msgstr "(Gelieve steeds te vermelden.)"
#: pretix/base/media.py:58
msgid "Barcode / QR-Code"
@@ -15231,10 +15227,8 @@ msgid "The order's internal comment has been updated."
msgstr "Het interne commentaar van de bestelling is bijgewerkt."
#: pretix/control/logdisplay.py:405
#, fuzzy
#| msgid "The order of items has been updated."
msgid "The order's follow-up date has been updated."
msgstr "De volgorde van items is bijgewerkt."
msgstr "De opvolgdatum van de bestelling werd bijgewerkt."
#: pretix/control/logdisplay.py:406
msgid "The order's flag to require attention at check-in has been toggled."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-05-27 22:00+0000\n"
"PO-Revision-Date: 2023-07-04 06:00+0000\n"
"Last-Translator: Maciej Szymczak <maciej+github@szymczak.at>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix/pl/"
">\n"
@@ -42,7 +42,7 @@ msgstr "Chiński (uproszczony)"
#: pretix/_base_settings.py:83
msgid "Chinese (traditional)"
msgstr ""
msgstr "Chiński (tradycyjny)"
#: pretix/_base_settings.py:84
msgid "Czech"
@@ -244,8 +244,6 @@ msgid "Item meta data property '{name}' does not exist."
msgstr "Klucz metadanych '{name}' nie istnieje."
#: pretix/api/serializers/item.py:182 pretix/control/forms/item.py:1082
#, fuzzy
#| msgid "The add-on's category must belong to the same event as the item."
msgid "The bundled item must not be the same item as the bundling one."
msgstr "Dodatek w pakiecie nie może być tym samym co bilet."
@@ -439,7 +437,6 @@ msgid "External refund of payment"
msgstr "Zewnętrzny zwrot płatności"
#: pretix/api/webhooks.py:258
#, fuzzy
msgid "Refund of payment requested by customer"
msgstr "Żądanie zwrotu płatności zgłoszone przez klienta"
@@ -527,28 +524,20 @@ msgid "Test-Mode of shop has been deactivated"
msgstr "Tryb testowy sklepu został wyłączony"
#: pretix/api/webhooks.py:339
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry added"
msgstr "Numer na liście"
msgstr "Dodano wpis na listę oczekujących"
#: pretix/api/webhooks.py:343
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry changed"
msgstr "Numer na liście"
msgstr "Zmieniono wpis na liście oczekujących"
#: pretix/api/webhooks.py:347
#, fuzzy
#| msgid "Waiting list entry"
msgid "Waiting list entry deleted"
msgstr "Numer na liście"
msgstr "Usunięto wpis z listy oczekujących"
#: pretix/api/webhooks.py:351
#, fuzzy
#| msgid "Waiting list entries"
msgid "Waiting list entry received voucher"
msgstr "Numery na liście"
msgstr "Osoba z listy oczekujących dostała voucher"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
@@ -564,11 +553,11 @@ msgstr "To pole jest wymagane."
#: pretix/base/addressvalidation.py:213
msgid "Enter a postal code in the format XXX."
msgstr ""
msgstr "Wprowadź kod pocztowy w formacie XXX."
#: pretix/base/addressvalidation.py:222 pretix/base/addressvalidation.py:224
msgid "Enter a postal code in the format XXXX."
msgstr ""
msgstr "Wprowadź kod pocztowy w formacie XXXX."
#: pretix/base/auth.py:143
#, python-brace-format
@@ -637,8 +626,7 @@ msgid "Incompatible SSO provider: \"{error}\"."
msgstr "Niekompatybilny dostawca SSO: \"{error}\"."
#: pretix/base/customersso/oidc.py:109
#, fuzzy, python-brace-format
#| msgid "Presale not started"
#, python-brace-format
msgid "You are not requesting \"{scope}\"."
msgstr "Nie prosisz o \"{scope}\"."
@@ -758,8 +746,6 @@ msgstr "Przesyłanie plików z odpowiedziami na pytania"
#: pretix/base/exporters/orderlist.py:1189
#: pretix/plugins/reports/exporters.py:451
#: pretix/plugins/reports/exporters.py:624
#, fuzzy
#| msgid "Order data"
msgctxt "export_category"
msgid "Order data"
msgstr "Dane zamówienia"
@@ -785,8 +771,6 @@ msgid "Customer accounts"
msgstr "Konta klientów"
#: pretix/base/exporters/customers.py:51
#, fuzzy
#| msgid "Cart positions"
msgctxt "export_category"
msgid "Customer accounts"
msgstr "Rachunki klientów"
@@ -976,8 +960,6 @@ msgid "No"
msgstr "Nie"
#: pretix/base/exporters/dekodi.py:42 pretix/base/exporters/invoices.py:66
#, fuzzy
#| msgid "Invoices"
msgctxt "export_category"
msgid "Invoices"
msgstr "Faktury"
@@ -1688,8 +1670,6 @@ msgstr "Wymagaj ważnego członkostwa"
#: pretix/base/exporters/items.py:94 pretix/base/models/items.py:580
#: pretix/base/models/items.py:1041
#, fuzzy
#| msgid "Team members"
msgid "Hide without a valid membership"
msgstr "Ukryj dla osób bez ważnego członkostwa"
@@ -1759,9 +1739,6 @@ msgid "Only paid orders"
msgstr "Tylko opłacone zamówienia"
#: pretix/base/exporters/orderlist.py:115
#, fuzzy
#| msgctxt "checkin"
#| msgid "Include pending orders"
msgid "Include payment amounts"
msgstr "Uwzględnij kwoty płatności"
@@ -1939,7 +1916,6 @@ msgstr "Kanał sprzedaży"
#: pretix/base/exporters/orderlist.py:279
#: pretix/base/exporters/orderlist.py:583 pretix/base/models/orders.py:233
#: pretix/control/forms/filter.py:238
#, fuzzy
msgid "Follow-up date"
msgstr "Data kontynuacji"
@@ -2309,18 +2285,13 @@ msgstr "Identyfikator zasady podatkowej"
#: pretix/base/exporters/orderlist.py:853
#: pretix/plugins/reports/accountingreport.py:250
#, fuzzy
#| msgctxt "invoice"
#| msgid "Gross value"
msgid "Gross total"
msgstr "Wartość brutto"
#: pretix/base/exporters/orderlist.py:854
#: pretix/plugins/reports/accountingreport.py:249
#, fuzzy
#| msgid "Total"
msgid "Tax total"
msgstr "Razem"
msgstr "Razem (podatki)"
#: pretix/base/exporters/orderlist.py:864
msgid ""
@@ -2603,6 +2574,8 @@ msgstr "Karty podarunkowe"
#: pretix/base/exporters/orderlist.py:1237
msgid "Download a spreadsheet of all gift cards including their current value."
msgstr ""
"Pobierz arkusz kalkulacyjny zawierający wszystkie karty podarunkowe wraz z "
"ich aktualną wartością."
#: pretix/base/exporters/orderlist.py:1244
msgid "Show value at"
@@ -2610,7 +2583,7 @@ msgstr "Pokaż wartość w"
#: pretix/base/exporters/orderlist.py:1247
msgid "Defaults to the time of report."
msgstr ""
msgstr "Domyślny czas raportu."
#: pretix/base/exporters/orderlist.py:1252
#: pretix/base/exporters/orderlist.py:1262 pretix/control/forms/filter.py:507
@@ -2632,7 +2605,6 @@ msgid "All"
msgstr "Zaznacz wszystko"
#: pretix/base/exporters/orderlist.py:1254 pretix/control/forms/filter.py:1315
#, fuzzy
msgid "Live"
msgstr "Na żywo"
@@ -2642,12 +2614,10 @@ msgid "Empty"
msgstr "Pusty"
#: pretix/base/exporters/orderlist.py:1264 pretix/control/forms/filter.py:1324
#, fuzzy
msgid "Valid and with value"
msgstr "Ważny z wartością"
#: pretix/base/exporters/orderlist.py:1265 pretix/control/forms/filter.py:1325
#, fuzzy
msgid "Expired and with value"
msgstr "Wygaśnięty z wartością"
@@ -2685,33 +2655,25 @@ msgid "Current value"
msgstr "Wartość netto"
#: pretix/base/exporters/orderlist.py:1310
#, fuzzy
#| msgid "Creation date"
msgid "Created in order"
msgstr "Data stworzenia"
#: pretix/base/exporters/orderlist.py:1311
#, fuzzy
#| msgid "Invoice number"
msgid "Last invoice number of order"
msgstr "Numer faktury ostatniego zamówienia"
#: pretix/base/exporters/orderlist.py:1312
#, fuzzy
#| msgid "Expiration date"
msgid "Last invoice date of order"
msgstr "Data wygaśnięcia"
msgstr "Data ostatniej faktury zamówienia"
#: pretix/base/exporters/waitinglist.py:42
#, fuzzy
#| msgid "Waiting list"
msgctxt "export_category"
msgid "Waiting list"
msgstr "Lista oczekiwania"
#: pretix/base/exporters/waitinglist.py:43
msgid "Download a spread sheet with all your waiting list data."
msgstr ""
msgstr "Pobierz arkusz kalkulacyjny ze wszystkimi danymi z listy oczekujących."
#: pretix/base/exporters/waitinglist.py:49
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:87
@@ -2733,7 +2695,7 @@ msgstr "Voucher został przypisany"
#: pretix/base/exporters/waitinglist.py:64
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:95
msgid "Waiting for redemption"
msgstr ""
msgstr "Oczekiwanie na wykupienie vouchera"
#: pretix/base/exporters/waitinglist.py:72
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:208
@@ -2745,7 +2707,6 @@ msgstr "Voucher został wykorzystany"
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:101
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:210
#: pretix/control/views/waitinglist.py:322
#, fuzzy
msgid "Voucher expired"
msgstr "Voucher wygaśnięty"
@@ -2784,6 +2745,8 @@ msgid ""
"Due to technical reasons you cannot set inputs, that need to be masked (e.g. "
"passwords), to %(value)s."
msgstr ""
"Ze względów technicznych nie można ustawić pól, które mają być maskowane ("
"np. hasła) do %(value)s."
#: pretix/base/forms/auth.py:57 pretix/base/forms/auth.py:168
msgid "Keep me logged in"
@@ -2833,11 +2796,9 @@ msgid "Please enter a shorter name."
msgstr "Wpisz proszę krótszą nazwę."
#: pretix/base/forms/questions.py:283
#, fuzzy
#| msgid "Internal reference"
msgctxt "phonenumber"
msgid "International area code"
msgstr "Wewnętrzna adnotacja"
msgstr "Wewnętrzny kod rejonu"
#: pretix/base/forms/questions.py:307
msgctxt "phonenumber"
@@ -2851,10 +2812,9 @@ msgid ""
msgstr "Wgrałeś poziomie zdjęcie. Wymagane jest zdjęcie w pionie."
#: pretix/base/forms/questions.py:471
#, fuzzy
msgid "Please upload an image where the width is 3/4 of the height."
msgstr ""
"Wgraj obrazek, którego proporcje to: szerokość nie mniej niż 3/4 wysokości"
"Wgraj obrazek, którego proporcje to: szerokość nie mniej niż 3/4 wysokości."
#: pretix/base/forms/questions.py:474
msgid ""
@@ -2876,7 +2836,7 @@ msgstr ""
msgid ""
"If you keep this empty, the ticket will be valid starting at the time of "
"purchase."
msgstr ""
msgstr "Jeśli to pole pozostanie puste, bilet będzie ważny od momentu zakupu."
#: pretix/base/forms/questions.py:664 pretix/base/forms/questions.py:992
msgid "Street and Number"
@@ -2892,10 +2852,14 @@ msgid ""
"Optional, but depending on the country you reside in we might need to charge "
"you additional taxes if you do not enter it."
msgstr ""
"Opcjonalne, ale w zależności od kraju, w którym mieszkasz, możemy być "
"zmuszeni do naliczenia dodatkowych podatków, jeśli go nie podasz."
#: pretix/base/forms/questions.py:1033 pretix/base/forms/questions.py:1039
msgid "If you are registered in Switzerland, you can enter your UID instead."
msgstr ""
"Jeśli jesteś zarejestrowany w Szwajcarii, możesz zamiast tego wprowadzić "
"swój identyfikator UID."
#: pretix/base/forms/questions.py:1037
msgid ""

View File

@@ -34,24 +34,27 @@
import json
import logging
import os
import subprocess
import tempfile
from collections import OrderedDict
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from io import BytesIO
from typing import Tuple
from typing import BinaryIO, List, Optional, Tuple
import dateutil.parser
from django import forms
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db import DataError, models
from django.db.models import Case, Exists, OuterRef, Q, Subquery, When
from django.db.models.functions import Cast, Coalesce
from django.utils.timezone import make_aware
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from pypdf import PdfReader, PdfWriter, Transformation
from pypdf import PageObject, PdfReader, PdfWriter, Transformation
from pypdf.generic import RectangleObject
from reportlab.lib import pagesizes
from reportlab.lib.units import mm
@@ -59,8 +62,10 @@ from reportlab.pdfgen import canvas
from pretix.base.exporter import BaseExporter
from pretix.base.i18n import language
from pretix.base.models import Order, OrderPosition, Question, QuestionAnswer
from pretix.base.pdf import Renderer
from pretix.base.models import (
Event, Order, OrderPosition, Question, QuestionAnswer,
)
from pretix.base.pdf import Renderer, merge_background
from pretix.base.services.export import ExportError
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.helpers.templatetags.jsonfield import JSONExtract
@@ -179,9 +184,137 @@ OPTIONS = OrderedDict([
])
def render_pdf(event, positions, opt):
Renderer._register_fonts()
def _chunks(lst, n):
"""
Yield successive n-sized chunks from lst.
"""
for i in range(0, len(lst), n):
yield lst[i:i + n]
def _render_nup_page(nup_pdf: PdfWriter, input_pages: PageObject, opt: dict) -> PageObject:
"""
Render the `Page` objects in `input_pages` onto one page of `nup_pdf` using the options given in `opt` and
return the newly created page.
"""
badges_per_page = opt['cols'] * opt['rows']
nup_page = nup_pdf.add_blank_page(
width=Decimal('%.5f' % (opt['pagesize'][0])),
height=Decimal('%.5f' % (opt['pagesize'][1])),
)
for i, page in enumerate(input_pages):
di = i % badges_per_page
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
page.add_transformation(Transformation().translate(tx, ty))
page.mediabox = RectangleObject((
Decimal('%.5f' % (page.mediabox.left.as_numeric() + tx)),
Decimal('%.5f' % (page.mediabox.bottom.as_numeric() + ty)),
Decimal('%.5f' % (page.mediabox.right.as_numeric() + tx)),
Decimal('%.5f' % (page.mediabox.top.as_numeric() + ty))
))
page.trimbox = page.mediabox
nup_page.merge_page(page)
return nup_page
def _merge_pages(file_paths: List[str], output_file: BinaryIO):
"""
Merge all pages from the PDF files named `file_paths` into the `output_file`.
"""
if settings.PDFTK:
subprocess.run([
settings.PDFTK,
*file_paths,
'cat',
'output',
'-',
'compress'
], check=True, stdout=output_file)
else:
merger = PdfWriter()
merger.add_metadata({
'/Title': 'Badges',
'/Creator': 'pretix',
})
# append all temp-PDFs
for pdf in file_paths:
merger.append(pdf)
# write merged PDFs to buffer
merger.write(output_file)
def _render_nup(input_files: List[str], num_pages: int, output_file: BytesIO, opt: dict):
"""
Render the pages from the PDF files listed in `input_files` (file names) with a total number of `num_pages` pages
into one file written to `output_file` using the -nup options given in `opt`.
"""
badges_per_page = opt['cols'] * opt['rows']
max_nup_pages = 20 # chunk size to prevent working with huge files
nup_pdf_files = []
temp_dir = None
if num_pages > badges_per_page * max_nup_pages:
# to reduce memory consumption with lots of badges
# we try to use temporary PDF-files with up to
# max_nup_pages pages
# If temp-files fail, we try to merge in-memory anyways
try:
temp_dir = tempfile.TemporaryDirectory()
except IOError:
pass
try:
badges_pdf = PdfReader(input_files.pop())
offset = 0
for i, chunk_indices in enumerate(_chunks(range(num_pages), badges_per_page * max_nup_pages)):
chunk = []
for j in chunk_indices:
# We need to dynamically switch to the next input file as we don't know how many pages each input
# file has beforehand
if j - offset >= len(badges_pdf.pages):
offset += len(badges_pdf.pages)
badges_pdf = PdfReader(input_files.pop())
chunk.append(badges_pdf.pages[j - offset])
# Reset some internal state from pypdf. This will make it a little slower, but will prevent us from
# running out of memory if we process a really large file.
badges_pdf.flattened_pages = None
nup_pdf = PdfWriter()
nup_pdf.add_metadata({
'/Title': 'Badges',
'/Creator': 'pretix',
})
for page_chunk in _chunks(chunk, badges_per_page):
_render_nup_page(nup_pdf, page_chunk, opt)
if temp_dir:
file_path = os.path.join(temp_dir.name, 'badges-%d.pdf' % i)
nup_pdf.write(file_path)
nup_pdf_files.append(file_path)
else:
# everything fitted into one nup_pdf -- we can save some work
nup_pdf.write(output_file)
return
del badges_pdf # free up memory
file_paths = [os.path.join(temp_dir.name, fp) for fp in nup_pdf_files]
_merge_pages(file_paths, output_file)
finally:
if temp_dir:
try:
temp_dir.cleanup()
except IOError:
pass
def _render_badges(event: Event, positions: List[OrderPosition], opt: dict) -> Tuple[PdfWriter, PdfWriter, int]:
"""
Render the badges for the given order positions into two different files, one with the foregrounds and one with
the backgrounds.
"""
renderermap = {
bi.item_id: _renderer(event, bi.layout)
for bi in BadgeItem.objects.select_related('layout').filter(item__event=event)
@@ -195,12 +328,13 @@ def render_pdf(event, positions, opt):
if not len(op_renderers):
raise ExportError(_("None of the selected products is configured to print badges."))
# render each badge on its own page first
merger = PdfWriter()
merger.add_metadata({
fg_pdf = PdfWriter()
fg_pdf.add_metadata({
'/Title': 'Badges',
'/Creator': 'pretix',
})
bg_pdf = PdfWriter()
num_pages = 0
for op, renderer in op_renderers:
buffer = BytesIO()
page = canvas.Canvas(buffer, pagesize=pagesizes.A4)
@@ -210,46 +344,52 @@ def render_pdf(event, positions, opt):
if opt['pagesize']:
page.setPageSize(opt['pagesize'])
page.save()
buffer = renderer.render_background(buffer, _('Badge'))
merger.append(ContentFile(buffer.read()))
# to reduce disk-IO render backgrounds in own PDF and merge later
fg_pdf.append(buffer)
new_num_pages = len(fg_pdf.pages)
for i in range(new_num_pages - num_pages):
bg_pdf.add_page(renderer.bg_pdf.pages[i])
num_pages = new_num_pages
outbuffer = BytesIO()
merger.write(outbuffer)
outbuffer.seek(0)
return fg_pdf, bg_pdf, num_pages
def render_pdf(event, positions, opt, output_file):
Renderer._register_fonts()
badges_per_page = opt['cols'] * opt['rows']
if badges_per_page == 1:
# no need to place multiple badges on one page
return outbuffer
fg_pdf, bg_pdf, _ = _render_badges(event, positions, opt)
merge_background(
fg_pdf,
bg_pdf,
output_file,
compress=True,
)
else:
# place n-up badges/pages per page
with tempfile.TemporaryDirectory() as tmp_dir:
page_pdfs = []
total_num_pages = 0
for position_chunk in _chunks(positions, 200):
# We first render the foreground and background of every individual badge and merge them, but we do
# so in chunks, since the n-up code is slower if it has to deal with huge PDFs. It doesn't matter
# that not every position has the same number of pages, as the n-up code can deal with that
fg_pdf, bg_pdf, num_pages = _render_badges(event, position_chunk, opt)
out_pdf_name = os.path.join(tmp_dir, f'chunk-{len(page_pdfs)}.pdf')
with open(out_pdf_name, 'wb') as out_pdf:
merge_background(
fg_pdf,
bg_pdf,
out_pdf,
compress=False,
)
page_pdfs.append(out_pdf_name)
total_num_pages += num_pages
del fg_pdf, bg_pdf # free up memory
# place n-up badges/pages per page
badges_pdf = PdfReader(outbuffer)
nup_pdf = PdfWriter()
nup_page = None
for i, page in enumerate(badges_pdf.pages):
di = i % badges_per_page
if di == 0:
nup_page = nup_pdf.add_blank_page(
width=Decimal('%.5f' % (opt['pagesize'][0])),
height=Decimal('%.5f' % (opt['pagesize'][1])),
)
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
page.add_transformation(Transformation().translate(tx, ty))
page.mediabox = RectangleObject((
Decimal('%.5f' % (page.mediabox.left.as_numeric() + tx)),
Decimal('%.5f' % (page.mediabox.bottom.as_numeric() + ty)),
Decimal('%.5f' % (page.mediabox.right.as_numeric() + tx)),
Decimal('%.5f' % (page.mediabox.top.as_numeric() + ty))
))
page.trimbox = page.mediabox
nup_page.merge_page(page)
outbuffer = BytesIO()
nup_pdf.write(outbuffer)
outbuffer.seek(0)
return outbuffer
# Actually render a n-up file
return _render_nup(page_pdfs, total_num_pages, output_file, opt)
class BadgeExporter(BaseExporter):
@@ -335,7 +475,7 @@ class BadgeExporter(BaseExporter):
)
return d
def render(self, form_data: dict) -> Tuple[str, str, str]:
def render(self, form_data: dict, output_file=None) -> Tuple[str, str, Optional[bytes]]:
qs = OrderPosition.objects.filter(
order__event=self.event, item_id__in=form_data['items']
).prefetch_related(
@@ -431,11 +571,17 @@ class BadgeExporter(BaseExporter):
)
try:
outbuffer = render_pdf(self.event, qs, OPTIONS[form_data.get('rendering', 'one')])
if output_file:
render_pdf(self.event, qs, OPTIONS[form_data.get('rendering', 'one')], output_file=output_file)
return 'badges.pdf', 'application/pdf', None
else:
with tempfile.NamedTemporaryFile(delete=True) as tmpfile:
render_pdf(self.event, qs, OPTIONS[form_data.get('rendering', 'one')], output_file=tmpfile)
tmpfile.seek(0)
return 'badges.pdf', 'application/pdf', tmpfile.read()
except DataError:
logging.exception('DataError during export')
raise ExportError(
_('Your data could not be converted as requested. This could be caused by invalid values in your '
'databases, such as answers to number questions which are not a number.')
)
return 'badges.pdf', 'application/pdf', outbuffer.read()

View File

@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import logging
import tempfile
from typing import List
from django.core.files.base import ContentFile
@@ -41,7 +42,10 @@ logger = logging.getLogger(__name__)
def badges_create_pdf(event: Event, fileid: int, positions: List[int]) -> int:
file = CachedFile.objects.get(id=fileid)
pdfcontent = render_pdf(event, OrderPosition.objects.filter(id__in=positions), opt=OPTIONS['one'])
file.file.save(cachedfile_name(file, file.filename), ContentFile(pdfcontent.read()))
file.save()
with tempfile.TemporaryFile() as tmp_file:
render_pdf(event, OrderPosition.objects.filter(id__in=positions), opt=OPTIONS['one'],
output_file=tmp_file)
tmp_file.seek(0)
file.file.save(cachedfile_name(file, file.filename), ContentFile(tmp_file.read()))
file.save()
return file.pk

View File

@@ -49,7 +49,7 @@ class RuleSerializer(I18nAwareModelSerializer):
if not full_data.get('send_date'):
raise ValidationError('send_date is required for date_is_absolute=True')
else:
if not all([full_data.get(k) for k in ['send_offset_days', 'send_offset_time']]):
if not all([full_data.get(k) is not None for k in ['send_offset_days', 'send_offset_time']]):
raise ValidationError('send_offset_days and send_offset_time are required for date_is_absolute=False')
if full_data.get('all_products') is False:

View File

@@ -59,7 +59,9 @@ from pretix import __version__
from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField
from pretix.base.models import Event, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.payment import (
BasePaymentProvider, PaymentException, WalletQueries,
)
from pretix.base.plugins import get_all_plugins
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
@@ -219,6 +221,20 @@ class StripeSettingsHolder(BasePaymentProvider):
]
extra_fields = [
('walletdetection',
forms.BooleanField(
label=mark_safe(
_('Check for Apple Pay/Google Pay') +
' ' +
'<span class="label label-info">{}</span>'.format(_('experimental'))
),
help_text=_("pretix will attempt to check if the customer's webbrowser supports wallet-based payment "
"methods like Apple Pay or Google Pay and display them prominently with the credit card"
"payment method. This detection does not take into consideration if Google Pay/Apple Pay "
"has been disabled in the Stripe Dashboard."),
initial=True,
required=False,
)),
('postfix',
forms.CharField(
label=_('Statement descriptor postfix'),
@@ -747,6 +763,15 @@ class StripeCC(StripeMethod):
public_name = _('Credit card')
method = 'cc'
@property
def walletqueries(self):
# ToDo: Check against Stripe API, if ApplePay and GooglePay are even activated/available
# This is probably only really feasable once the Payment Methods Configuration API is out of beta
# https://stripe.com/docs/connect/payment-method-configurations
if self.settings.get("walletdetection", True, as_type=bool):
return [WalletQueries.APPLEPAY, WalletQueries.GOOGLEPAY]
return []
def payment_form_render(self, request, total) -> str:
account = get_stripe_account_key(self)
if not RegisteredApplePayDomain.objects.filter(account=account, domain=request.host).exists():

View File

@@ -24,18 +24,22 @@ from collections import OrderedDict
from django import forms
from django.dispatch import receiver
from django.http import HttpRequest
from django.template.loader import get_template
from django.urls import resolve, reverse
from django.utils.translation import gettext_lazy as _
from paypalhttp import HttpResponse
from pretix.base.forms import SecretKeySettingsField
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
from pretix.base.settings import settings_hierarkey
from pretix.base.signals import (
logentry_display, register_global_settings, register_payment_providers,
)
from pretix.control.signals import nav_organizer
from pretix.plugins.stripe.forms import StripeKeyValidator
from pretix.presale.signals import html_head
from pretix.plugins.stripe.payment import StripeMethod
from pretix.presale.signals import html_head, process_response
@receiver(register_payment_providers, dispatch_uid="payment_stripe")
@@ -178,3 +182,34 @@ def nav_o(sender, request, organizer, **kwargs):
'active': 'settings.connect' in url.url_name,
}]
return []
@receiver(signal=process_response, dispatch_uid="stripe_middleware_resp")
def signal_process_response(sender, request: HttpRequest, response: HttpResponse, **kwargs):
provider = StripeMethod(sender)
url = resolve(request.path_info)
if provider.settings.get('_enabled', as_type=bool) and (
url.url_name == "event.order.pay.change" or
url.url_name == "event.order.pay" or
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment") or
(url.namespace == "plugins:stripe" and url.url_name in ["sca", "sca.return"])
):
if 'Content-Security-Policy' in response:
h = _parse_csp(response['Content-Security-Policy'])
else:
h = {}
# https://stripe.com/docs/security/guide#content-security-policy
csps = {
'connect-src': ['https://api.stripe.com'],
'frame-src': ['https://js.stripe.com', 'https://hooks.stripe.com'],
'script-src': ['https://js.stripe.com'],
}
_merge_csp(h, csps)
if h:
response['Content-Security-Policy'] = _render_csp(h)
return response

View File

@@ -26,8 +26,10 @@
{% if payment_info.source.type == "bancontact" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ payment_info.source.bancontact.bank_name }} ({{ payment_info.source.bancontact.bic }})</dd>
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
{% if owner in payment_info.source %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
{% endif %}
{% endif %}
{% if payment_info.source.type == "ideal" %}
<dt>{% trans "Bank" %}</dt>

View File

@@ -3,6 +3,10 @@
{% load money %}
{% load bootstrap3 %}
{% load rich_text %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block inner %}
{% if current_payments %}
<p>{% trans "You already selected the following payment methods:" %}</p>
@@ -71,7 +75,8 @@
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
id="input_payment_{{ p.provider.identifier }}"
aria-describedby="payment_{{ p.provider.identifier }}"
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"/>
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
data-wallets="{{ p.provider.walletqueries|join:"|" }}" />
<label for="input_payment_{{ p.provider.identifier }}"><strong>{{ p.provider.public_name }}</strong></label>
</p>
</div>

View File

@@ -0,0 +1,6 @@
{% load static %}
{% load compress %}
{% compress js %}
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
{% endcompress %}

View File

@@ -3,6 +3,10 @@
{% load eventurl %}
{% load money %}
{% block title %}{% trans "Change payment method" %}{% endblock %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=order.code %}
@@ -29,7 +33,8 @@
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
data-parent="#payment_accordion"
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}" />
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
data-wallets="{{ p.provider.walletqueries|join:"|" }}"/>
<strong>{{ p.provider.public_name }}</strong>
</label>
</h4>

View File

@@ -52,6 +52,7 @@ from pretix.base.customersso.oidc import (
from pretix.base.models import Customer, InvoiceAddress, Order, OrderPosition
from pretix.base.services.mail import mail
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import customer_created, customer_signed_in
from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.http import redirect_to_url
from pretix.multidomain.models import KnownDomain
@@ -151,7 +152,9 @@ class LoginView(RedirectBackMixin, FormView):
def form_valid(self, form):
"""Security check complete. Log the user in."""
customer_login(self.request, form.get_customer())
customer = form.get_customer()
customer_login(self.request, customer)
customer_signed_in.send(customer.organizer, customer=customer)
return HttpResponseRedirect(self.get_success_url())
@@ -237,7 +240,8 @@ class RegistrationView(RedirectBackMixin, FormView):
def form_valid(self, form):
with transaction.atomic():
form.create()
customer = form.create()
customer_created.send(customer.organizer, customer=customer)
messages.success(
self.request,
_('Your account has been created. Please follow the link in the email we sent you to activate your '
@@ -756,6 +760,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
)
try:
customer.save(force_insert=True)
customer_created.send(customer.organizer, customer=customer)
except IntegrityError:
# This might either be a race condition or the email address is taken
# by a different customer account
@@ -819,6 +824,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
})
else:
customer_login(self.request, customer)
customer_signed_in.send(customer.organizer, customer=customer)
return redirect_to_url(self.get_success_url(redirect_to))
def _fail(self, message, popup_origin):

View File

@@ -116,6 +116,29 @@ elif 'mysql' in db_backend:
db_options = {}
postgresql_sslmode = config.get('database', 'sslmode', fallback='disable')
USE_DATABASE_TLS = postgresql_sslmode != 'disable'
USE_DATABASE_MTLS = USE_DATABASE_TLS and config.has_option('database', 'sslcert')
if USE_DATABASE_TLS or USE_DATABASE_MTLS:
tls_config = {}
if not USE_DATABASE_MTLS:
if 'postgresql' in db_backend:
tls_config = {
'sslmode': config.get('database', 'sslmode'),
'sslrootcert': config.get('database', 'sslrootcert'),
}
else:
if 'postgresql' in db_backend:
tls_config = {
'sslmode': config.get('database', 'sslmode'),
'sslrootcert': config.get('database', 'sslrootcert'),
'sslcert': config.get('database', 'sslcert'),
'sslkey': config.get('database', 'sslkey'),
}
db_options.update(tls_config)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' + db_backend,
@@ -228,6 +251,9 @@ if HAS_MEMCACHED:
HAS_REDIS = config.has_option('redis', 'location')
USE_REDIS_SENTINEL = config.has_option('redis', 'sentinels')
redis_ssl_cert_reqs = config.get('redis', 'ssl_cert_reqs', fallback='none')
USE_REDIS_TLS = redis_ssl_cert_reqs != 'none'
USE_REDIS_MTLS = USE_REDIS_TLS and config.has_option('redis', 'ssl_certfile')
HAS_REDIS_PASSWORD = config.has_option('redis', 'password')
if HAS_REDIS:
OPTIONS = {
@@ -243,6 +269,29 @@ if HAS_REDIS:
OPTIONS["SENTINEL_KWARGS"] = {"socket_timeout": 1}
OPTIONS["SENTINELS"] = [tuple(sentinel) for sentinel in loads(config.get('redis', 'sentinels'))]
if USE_REDIS_TLS or USE_REDIS_MTLS:
tls_config = {}
if not USE_REDIS_MTLS:
tls_config = {
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
}
else:
tls_config = {
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
'ssl_keyfile': config.get('redis', 'ssl_keyfile'),
'ssl_certfile': config.get('redis', 'ssl_certfile'),
}
if USE_REDIS_SENTINEL is False:
# The CONNECTION_POOL_KWARGS option is necessary for self-signed certs. For further details, please check
# https://github.com/jazzband/django-redis/issues/554#issuecomment-949498321
OPTIONS["CONNECTION_POOL_KWARGS"] = tls_config
OPTIONS["REDIS_CLIENT_KWARGS"].update(tls_config)
else:
OPTIONS["SENTINEL_KWARGS"].update(tls_config)
if HAS_REDIS_PASSWORD:
OPTIONS["PASSWORD"] = config.get('redis', 'password')

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.22.1",
"@babel/preset-env": "^7.22.4",
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.22.9",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
"vue": "^2.7.14",

View File

@@ -1,6 +1,31 @@
/*global $ */
setup_collapsible_details = function (el) {
el.find('details.sneak-peek:not([open])').each(function() {
this.open = true;
var $elements = $("> :not(summary)", this).show().filter(':not(.sneak-peek-trigger)').attr('aria-hidden', 'true');
var container = this;
var trigger = $('summary, .sneak-peek-trigger button', container);
function onclick(e) {
e.preventDefault();
container.addEventListener('transitionend', function() {
$(container).removeClass('sneak-peek');
container.style.removeProperty('height');
}, {once: true});
container.style.height = container.scrollHeight + 'px';
$('.sneak-peek-trigger', container).fadeOut(function() {
$(this).remove();
});
$elements.removeAttr('aria-hidden');
trigger.off('click', onclick);
}
trigger.on('click', onclick);
});
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
el.find("details summary").click(function (e) {
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {

View File

@@ -123,6 +123,8 @@ var form_handlers = function (el) {
// Vouchers
el.find("#voucher-bulk-codes-generate").click(function () {
if (!$("#voucher-bulk-codes-num").get(0).reportValidity())
return;
var num = $("#voucher-bulk-codes-num").val();
var prefix = $('#voucher-bulk-codes-prefix').val();
if (num != "") {

View File

@@ -307,30 +307,6 @@ $(function () {
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
$('details.sneak-peek:not([open])').each(function() {
this.open = true;
var $elements = $("> :not(summary)", this).show().filter(':not(.sneak-peek-trigger)').attr('aria-hidden', 'true');
var container = this;
var trigger = $('summary, .sneak-peek-trigger button', container);
function onclick(e) {
e.preventDefault();
container.addEventListener('transitionend', function() {
$(container).removeClass('sneak-peek');
container.style.removeProperty('height');
}, {once: true});
container.style.height = container.scrollHeight + 'px';
$('.sneak-peek-trigger', container).fadeOut(function() {
$(this).remove();
});
$elements.removeAttr('aria-hidden');
trigger.off('click', onclick);
}
trigger.on('click', onclick);
});
// Copy answers
$(".js-copy-answers").click(function (e) {
e.preventDefault();

View File

@@ -0,0 +1,71 @@
'use strict';
var walletdetection = {
applepay: async function () {
// This is a weak check for Apple Pay - in order to do a proper check, we would need to also call
// canMakePaymentsWithActiveCard(merchantIdentifier)
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments());
},
googlepay: async function () {
// Checking for Google Pay is a little bit more involved, since it requires including the Google Pay JS SDK, and
// providing a lot of information.
// So for the time being, we only check if Google Pay is available in TEST-mode, which should hopefully give us a
// good enough idea if Google Pay could be present on this device; even though there are still a lot of other
// factors that could inhibit Google Pay from actually being offered to the customer.
return $.ajax({
url: 'https://pay.google.com/gp/p/js/pay.js',
dataType: 'script',
}).then(function() {
const paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
return paymentsClient.isReadyToPay({
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
allowedCardNetworks: ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]
}
}],
})
}).then(function (response) {
return !!response.result;
});
},
name_map: {
applepay: gettext('Apple Pay'),
googlepay: gettext('Google Pay'),
}
}
$(function () {
const wallets = $('[data-wallets]')
.map(function(index, pm) {
return pm.getAttribute("data-wallets").split("|");
})
.get()
.flat()
.filter(function(item, pos, self) {
// filter out empty or duplicate values
return item && self.indexOf(item) == pos;
});
wallets.forEach(function(wallet) {
const labels = $('[data-wallets*='+wallet+'] + label strong, [data-wallets*='+wallet+'] + strong')
.append('<span class="wallet wallet-loading"> <i aria-hidden="true" class="fa fa-cog fa-spin"></i></span>')
walletdetection[wallet]()
.then(function(result) {
const spans = labels.find(".wallet-loading:nth-of-type(1)");
if (result) {
spans.removeClass('wallet-loading').hide().text(', ' + walletdetection.name_map[wallet]).fadeIn(300);
} else {
spans.remove();
}
})
.catch(function(result) {
labels.find(".wallet-loading:nth-of-type(1)").remove();
})
});
});

View File

@@ -179,3 +179,7 @@
flex: 1;
}
}
.wallet-loading + .wallet-loading {
display: none;
}

View File

@@ -126,7 +126,7 @@ def test_event_validate(token_client, organizer, team, event):
assert resp.data == {"_format": ["\"FOOBAR\" is not a valid choice."]}
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_org_validate_events(token_client, organizer, team, event):
resp = token_client.post('/api/v1/organizers/{}/exporters/orderlist/run/'.format(organizer.slug), data={
'_format': 'xlsx',
@@ -164,7 +164,7 @@ def test_org_validate_events(token_client, organizer, team, event):
assert resp.data == {"events": [f"Object with slug={event.slug} does not exist."]}
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_org_run_limit_events(token_client, organizer, team, event, event2):
resp = token_client.post('/api/v1/organizers/{}/exporters/eventdata/run/'.format(organizer.slug), data={
'_format': 'default',
@@ -199,7 +199,7 @@ def test_org_run_limit_events(token_client, organizer, team, event, event2):
assert resp.getvalue().strip().count(b"\n") == 1
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_run_success(token_client, organizer, team, event):
resp = token_client.post('/api/v1/organizers/{}/events/{}/exporters/orderlist/run/'.format(organizer.slug, event.slug), data={
'_format': 'xlsx',
@@ -212,7 +212,7 @@ def test_run_success(token_client, organizer, team, event):
assert resp["Content-Type"] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_run_success_old_date_frame(token_client, organizer, team, event):
resp = token_client.post('/api/v1/organizers/{}/events/{}/exporters/orderlist/run/'.format(organizer.slug, event.slug), data={
'_format': 'xlsx',
@@ -261,7 +261,7 @@ def test_gone_without_celery(token_client, organizer, team, event):
assert resp.status_code == 410
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_org_level_export(token_client, organizer, team, event):
resp = token_client.post('/api/v1/organizers/{}/exporters/giftcardlist/run/'.format(organizer.slug), data={
'date': '2022-10-05T00:00:00Z',

View File

@@ -119,6 +119,23 @@ def test_sendmail_rule_create_min_fail(token_client, organizer, event):
)
@scopes_disabled()
@pytest.mark.django_db
def test_sendmail_rule_offset_zero(token_client, organizer, event):
create_rule(
token_client, organizer, event,
data={
'subject': {'en': 'meow'},
'template': {'en': 'creative text here'},
'send_date': '2018-03-17T13:31Z',
'send_offset_days': '0',
'send_offset_time': '08:40',
'date_is_absolute': False,
},
expected_failure=False,
)
@scopes_disabled()
@pytest.mark.django_db
def test_sendmail_rule_create_minimal(token_client, organizer, event):

View File

@@ -156,7 +156,7 @@ def test_event_fail_user_no_permission(event, user, team):
assert djmail.outbox[0].to == [user.email]
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
@freeze_time("2023-01-18 03:00:00+01:00")
def test_event_ok(event, user, team):
djmail.outbox = []
@@ -286,7 +286,7 @@ def test_organizer_fail_user_does_not_have_specific_permission(event, user, team
assert djmail.outbox[0].to == [user.email]
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
@freeze_time("2023-01-18 03:00:00+01:00")
def test_organizer_limited_to_events(event, user, team):
djmail.outbox = []
@@ -323,7 +323,7 @@ def test_organizer_limited_to_events(event, user, team):
assert len(djmail.outbox[0].attachments[0][1].splitlines()) == 2
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
@freeze_time("2023-01-18 03:00:00+01:00")
def test_organizer_ok(event, user, team):
djmail.outbox = []

View File

@@ -633,7 +633,7 @@ class EventsTest(SoupTest):
},
follow=True
)
assert doc.select('.alert-warning')
assert doc.select('.alert-danger')
self.event1.settings.flush()
# not yet saved
assert "mail_from" not in self.event1.settings._cache()

View File

@@ -52,7 +52,7 @@ def env():
return event, user, t
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_event_export(client, env):
client.login(email="dummy@dummy.dummy", password="dummy")
response = client.get("/control/event/dummy/dummy/orders/export/?identifier=itemdata")
@@ -69,7 +69,7 @@ def test_event_export(client, env):
assert len(b"".join(response.streaming_content).split(b"\n")) == 3
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_event_export_schedule(client, env):
client.login(email="dummy@dummy.dummy", password="dummy")
response = client.get("/control/event/dummy/dummy/orders/export/?identifier=itemdata")
@@ -161,7 +161,7 @@ def test_event_export_schedule(client, env):
assert env[0].scheduled_exports.count() == 0
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_event_limited_permission(client, env):
env[2].can_change_event_settings = False
env[2].save()
@@ -212,7 +212,7 @@ def test_event_limited_permission(client, env):
assert response.status_code == 302
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_organizer_export(client, env):
client.login(email="dummy@dummy.dummy", password="dummy")
response = client.get("/control/organizer/dummy/export/?identifier=eventdata")
@@ -230,7 +230,7 @@ def test_organizer_export(client, env):
assert len(b"".join(response.streaming_content).split(b"\n")) == 3
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_organizer_export_schedule(client, env):
client.login(email="dummy@dummy.dummy", password="dummy")
response = client.get("/control/organizer/dummy/export/?identifier=eventdata")
@@ -312,7 +312,6 @@ def test_organizer_export_schedule(client, env):
"schedule-mail_subject": "Product data, my friend!",
"schedule-mail_template": "Mail body"
}, follow=True)
print(response.content)
assert b"Your export schedule has been saved, but no next export is planned" in response.content
s.refresh_from_db()
assert s.schedule_next_run is None
@@ -329,7 +328,7 @@ def test_organizer_export_schedule(client, env):
assert env[0].organizer.scheduled_exports.count() == 0
@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
def test_organizer_limited_permission(client, env):
env[2].can_change_organizer_settings = False
env[2].save()

View File

@@ -221,7 +221,7 @@ class OrganizerTest(SoupTest):
},
follow=True
)
assert doc.select('.alert-warning')
assert doc.select('.alert-danger')
self.orga1.settings.flush()
# not yet saved
assert "mail_from" not in self.orga1.settings._cache()

View File

@@ -95,8 +95,12 @@ def test_native_disabled(env, client):
@pytest.mark.django_db
def test_org_register(env, client):
def test_org_register(env, client, mocker):
from pretix.base.signals import customer_created
mocker.patch('pretix.base.signals.customer_created.send')
signer = signing.TimestampSigner(salt='customer-registration-captcha-127.0.0.1')
r = client.post('/bigevents/account/register', {
'email': 'john@example.org',
'name_parts_0': 'John Doe',
@@ -109,6 +113,7 @@ def test_org_register(env, client):
customer = env[0].customers.get(email='john@example.org')
assert not customer.is_verified
assert customer.is_active
customer_created.send.assert_called_once_with(customer.organizer, customer=customer)
r = client.post(
f'/bigevents/account/activate?id={customer.identifier}&token={TokenGenerator().make_token(customer)}', {
@@ -123,7 +128,10 @@ def test_org_register(env, client):
@pytest.mark.django_db
def test_org_register_duplicate_email(env, client):
def test_org_register_duplicate_email(env, client, mocker):
from pretix.base.signals import customer_created
mocker.patch('pretix.base.signals.customer_created.send')
with scopes_disabled():
env[0].customers.create(email='john@example.org')
r = client.post('/bigevents/account/register', {
@@ -132,6 +140,7 @@ def test_org_register_duplicate_email(env, client):
})
assert b'already registered' in r.content
assert r.status_code == 200
customer_created.send.assert_not_called()
@pytest.mark.django_db
@@ -167,7 +176,11 @@ def test_org_activate_invalid_token(env, client):
@pytest.mark.django_db
def test_org_login_logout(env, client):
def test_org_login_logout(env, client, mocker):
from pretix.base.signals import customer_signed_in
mocker.patch('pretix.base.signals.customer_signed_in.send')
customer = None
with scopes_disabled():
customer = env[0].customers.create(email='john@example.org', is_verified=True)
customer.set_password('foo')
@@ -179,6 +192,8 @@ def test_org_login_logout(env, client):
})
assert r.status_code == 302
customer_signed_in.send.assert_called_once_with(customer.organizer, customer=customer)
r = client.get('/bigevents/account/')
assert r.status_code == 200
@@ -190,7 +205,10 @@ def test_org_login_logout(env, client):
@pytest.mark.django_db
def test_org_login_invalid_password(env, client):
def test_org_login_invalid_password(env, client, mocker):
from pretix.base.signals import customer_signed_in
mocker.patch('pretix.base.signals.customer_signed_in.send')
with scopes_disabled():
customer = env[0].customers.create(email='john@example.org', is_verified=True)
customer.set_password('foo')
@@ -202,10 +220,15 @@ def test_org_login_invalid_password(env, client):
})
assert r.status_code == 200
assert b'alert-danger' in r.content
customer_signed_in.send.assert_not_called()
@pytest.mark.django_db
def test_org_login_not_verified(env, client):
def test_org_login_not_verified(env, client, mocker):
from pretix.base.signals import customer_signed_in
mocker.patch('pretix.base.signals.customer_signed_in.send')
customer = None
with scopes_disabled():
customer = env[0].customers.create(email='john@example.org', is_verified=False)
customer.set_password('foo')
@@ -217,10 +240,14 @@ def test_org_login_not_verified(env, client):
})
assert r.status_code == 200
assert b'alert-danger' in r.content
customer_signed_in.send.assert_not_called()
@pytest.mark.django_db
def test_org_login_not_active(env, client):
def test_org_login_not_active(env, client, mocker):
from pretix.base.signals import customer_signed_in
mocker.patch('pretix.base.signals.customer_signed_in.send')
with scopes_disabled():
customer = env[0].customers.create(email='john@example.org', is_verified=True, is_active=False)
customer.set_password('foo')
@@ -232,6 +259,7 @@ def test_org_login_not_active(env, client):
})
assert r.status_code == 200
assert b'alert-danger' in r.content
customer_signed_in.send.assert_not_called()
@pytest.fixture
@@ -309,12 +337,18 @@ def _sso_login(client, provider, email='test@example.org', popup_origin=None, ex
@pytest.mark.django_db
def test_org_sso_login_new_customer(env, client, provider):
def test_org_sso_login_new_customer(env, client, provider, mocker):
from pretix.base.signals import customer_created, customer_signed_in
mocker.patch('pretix.base.signals.customer_created.send')
mocker.patch('pretix.base.signals.customer_signed_in.send')
_sso_login(client, provider)
with scopes_disabled():
c = Customer.objects.get(provider=provider)
assert c.external_identifier == "abcdf"
customer_created.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
r = client.get('/bigevents/account/')
assert r.status_code == 200
@@ -352,10 +386,15 @@ def test_org_sso_login_new_customer_popup_invalid_origin(env, client, provider):
@pytest.mark.django_db
def test_org_sso_login_returning_customer_new_email(env, client, provider):
def test_org_sso_login_returning_customer_new_email(env, client, provider, mocker):
from pretix.base.signals import customer_signed_in
mocker.patch('pretix.base.signals.customer_signed_in.send')
_sso_login(client, provider)
with scopes_disabled():
c = Customer.objects.get(provider=provider)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.reset_mock()
r = client.get('/bigevents/account/logout')
assert r.status_code == 302
@@ -363,6 +402,7 @@ def test_org_sso_login_returning_customer_new_email(env, client, provider):
_sso_login(client, provider, 'new@example.net')
c.refresh_from_db()
assert c.email == "new@example.net"
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
@pytest.mark.django_db(transaction=True)