forked from CGM_Public/pretix_original
Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd0612277 | ||
|
|
53e84dfb08 | ||
|
|
2e8447486c | ||
|
|
5b184bb1a0 | ||
|
|
8c6f0a5dc1 | ||
|
|
6a53091b91 | ||
|
|
be4bc9a6f3 | ||
|
|
efb1141d59 | ||
|
|
322a730eb2 | ||
|
|
8d2224e725 | ||
|
|
5b819b76f0 | ||
|
|
5d90a42acf | ||
|
|
5398671fde | ||
|
|
f7d4460deb | ||
|
|
f76576a587 | ||
|
|
cf5f0dc7f9 | ||
|
|
567984bd5e | ||
|
|
1c6bd46d21 | ||
|
|
9ba3227837 | ||
|
|
21864885cb | ||
|
|
38173e3a54 | ||
|
|
4baf317934 | ||
|
|
c2b25bad06 | ||
|
|
9e3ad6c05c | ||
|
|
f017de1a21 | ||
|
|
b56bd8541e | ||
|
|
1c9219609a | ||
|
|
0c96f758a8 | ||
|
|
9bd3444aad | ||
|
|
10a83935d9 | ||
|
|
e8ea6e0f5c | ||
|
|
e94e5be878 | ||
|
|
1073ea626e | ||
|
|
23ab8df443 | ||
|
|
d6caf01a38 | ||
|
|
1424ae78e9 | ||
|
|
827382edc3 | ||
|
|
85482bc939 | ||
|
|
42ce545f2f | ||
|
|
e49bc5d78d | ||
|
|
6e7a32ef2a | ||
|
|
37df7a6313 | ||
|
|
d5951415a4 | ||
|
|
691159ed83 | ||
|
|
18f517af44 | ||
|
|
89ba2da7e7 | ||
|
|
c1c47e50c3 | ||
|
|
f262cd632c | ||
|
|
8d58294af1 | ||
|
|
ddc94a8a16 | ||
|
|
83811c0343 | ||
|
|
b2c05a72e5 | ||
|
|
8c56a23562 | ||
|
|
53e1d9c6c4 | ||
|
|
6250ab2165 | ||
|
|
6ada83df9a | ||
|
|
cfd6376936 | ||
|
|
edb0cd0941 | ||
|
|
88ac407cf3 | ||
|
|
5ba56fb5ac | ||
|
|
b51c9f7552 | ||
|
|
0853296663 | ||
|
|
721e7549bc | ||
|
|
aee86de330 | ||
|
|
756a4355d1 | ||
|
|
5119bbd0b1 | ||
|
|
728bd74e28 | ||
|
|
015ffeecbf | ||
|
|
0365f6d9fc | ||
|
|
e208a79c32 | ||
|
|
0037d37960 | ||
|
|
50d9b1e4a3 | ||
|
|
7919d012e6 | ||
|
|
327f95a9cc | ||
|
|
98946ded4b | ||
|
|
cf47b69bd3 | ||
|
|
fa5c69ce0a | ||
|
|
39d85fc112 | ||
|
|
23e222bf13 | ||
|
|
cb068b029f | ||
|
|
9e95f3be1b | ||
|
|
401c02865b | ||
|
|
062450002d |
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: harmon758/postgresql-action@v1
|
- uses: harmon758/postgresql-action@v1
|
||||||
with:
|
with:
|
||||||
postgresql version: '11'
|
postgresql version: '15'
|
||||||
postgresql db: 'pretix'
|
postgresql db: 'pretix'
|
||||||
postgresql user: 'postgres'
|
postgresql user: 'postgres'
|
||||||
postgresql password: 'postgres'
|
postgresql password: 'postgres'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from pretix.settings import *
|
from pretix.settings import *
|
||||||
|
|
||||||
LOGGING['handlers']['mail_admins']['include_html'] = True
|
LOGGING['handlers']['mail_admins']['include_html'] = True
|
||||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
STORAGES["staticfiles"]["BACKEND"] = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ subevent_mode strings Determines h
|
|||||||
``"same"`` (discount is only applied for groups within
|
``"same"`` (discount is only applied for groups within
|
||||||
the same date), or ``"distinct"`` (discount is only applied
|
the same date), or ``"distinct"`` (discount is only applied
|
||||||
for groups with no two same dates).
|
for groups with no two same dates).
|
||||||
condition_all_products boolean If ``true``, the discount applies to all items.
|
condition_all_products boolean If ``true``, the discount condition applies to all items.
|
||||||
condition_limit_products list of integers If ``condition_all_products`` is not set, this is a list
|
condition_limit_products list of integers If ``condition_all_products`` is not set, this is a list
|
||||||
of internal item IDs that the discount applies to.
|
of internal item IDs that the discount condition applies to.
|
||||||
condition_apply_to_addons boolean If ``true``, the discount applies to add-on products as well,
|
condition_apply_to_addons boolean If ``true``, the discount applies to add-on products as well,
|
||||||
otherwise it only applies to top-level items. The discount never
|
otherwise it only applies to top-level items. The discount never
|
||||||
applies to bundled products.
|
applies to bundled products.
|
||||||
@@ -48,6 +48,17 @@ benefit_discount_matching_percent decimal (string) The percenta
|
|||||||
benefit_only_apply_to_cheapest_n_matches integer If set higher than 0, the discount will only be applied to
|
benefit_only_apply_to_cheapest_n_matches integer If set higher than 0, the discount will only be applied to
|
||||||
the cheapest matches. Useful for a "3 for 2"-style discount.
|
the cheapest matches. Useful for a "3 for 2"-style discount.
|
||||||
Cannot be combined with ``condition_min_value``.
|
Cannot be combined with ``condition_min_value``.
|
||||||
|
benefit_same_products boolean If ``true``, the discount benefit applies to the same set of items
|
||||||
|
as the condition (see above).
|
||||||
|
benefit_limit_products list of integers If ``benefit_same_products`` is not set, this is a list
|
||||||
|
of internal item IDs that the discount benefit applies to.
|
||||||
|
benefit_apply_to_addons boolean (Only used if ``benefit_same_products`` is ``false``.)
|
||||||
|
If ``true``, the discount applies to add-on products as well,
|
||||||
|
otherwise it only applies to top-level items. The discount never
|
||||||
|
applies to bundled products.
|
||||||
|
benefit_ignore_voucher_discounted boolean (Only used if ``benefit_same_products`` is ``false``.)
|
||||||
|
If ``true``, the discount does not apply to products which have
|
||||||
|
been discounted by a voucher.
|
||||||
======================================== ========================== =======================================================
|
======================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
@@ -94,6 +105,10 @@ Endpoints
|
|||||||
"condition_ignore_voucher_discounted": false,
|
"condition_ignore_voucher_discounted": false,
|
||||||
"condition_min_count": 3,
|
"condition_min_count": 3,
|
||||||
"condition_min_value": "0.00",
|
"condition_min_value": "0.00",
|
||||||
|
"benefit_same_products": true,
|
||||||
|
"benefit_limit_products": [],
|
||||||
|
"benefit_apply_to_addons": true,
|
||||||
|
"benefit_ignore_voucher_discounted": false,
|
||||||
"benefit_discount_matching_percent": "100.00",
|
"benefit_discount_matching_percent": "100.00",
|
||||||
"benefit_only_apply_to_cheapest_n_matches": 1
|
"benefit_only_apply_to_cheapest_n_matches": 1
|
||||||
}
|
}
|
||||||
@@ -146,6 +161,10 @@ Endpoints
|
|||||||
"condition_ignore_voucher_discounted": false,
|
"condition_ignore_voucher_discounted": false,
|
||||||
"condition_min_count": 3,
|
"condition_min_count": 3,
|
||||||
"condition_min_value": "0.00",
|
"condition_min_value": "0.00",
|
||||||
|
"benefit_same_products": true,
|
||||||
|
"benefit_limit_products": [],
|
||||||
|
"benefit_apply_to_addons": true,
|
||||||
|
"benefit_ignore_voucher_discounted": false,
|
||||||
"benefit_discount_matching_percent": "100.00",
|
"benefit_discount_matching_percent": "100.00",
|
||||||
"benefit_only_apply_to_cheapest_n_matches": 1
|
"benefit_only_apply_to_cheapest_n_matches": 1
|
||||||
}
|
}
|
||||||
@@ -184,6 +203,10 @@ Endpoints
|
|||||||
"condition_ignore_voucher_discounted": false,
|
"condition_ignore_voucher_discounted": false,
|
||||||
"condition_min_count": 3,
|
"condition_min_count": 3,
|
||||||
"condition_min_value": "0.00",
|
"condition_min_value": "0.00",
|
||||||
|
"benefit_same_products": true,
|
||||||
|
"benefit_limit_products": [],
|
||||||
|
"benefit_apply_to_addons": true,
|
||||||
|
"benefit_ignore_voucher_discounted": false,
|
||||||
"benefit_discount_matching_percent": "100.00",
|
"benefit_discount_matching_percent": "100.00",
|
||||||
"benefit_only_apply_to_cheapest_n_matches": 1
|
"benefit_only_apply_to_cheapest_n_matches": 1
|
||||||
}
|
}
|
||||||
@@ -211,6 +234,10 @@ Endpoints
|
|||||||
"condition_ignore_voucher_discounted": false,
|
"condition_ignore_voucher_discounted": false,
|
||||||
"condition_min_count": 3,
|
"condition_min_count": 3,
|
||||||
"condition_min_value": "0.00",
|
"condition_min_value": "0.00",
|
||||||
|
"benefit_same_products": true,
|
||||||
|
"benefit_limit_products": [],
|
||||||
|
"benefit_apply_to_addons": true,
|
||||||
|
"benefit_ignore_voucher_discounted": false,
|
||||||
"benefit_discount_matching_percent": "100.00",
|
"benefit_discount_matching_percent": "100.00",
|
||||||
"benefit_only_apply_to_cheapest_n_matches": 1
|
"benefit_only_apply_to_cheapest_n_matches": 1
|
||||||
}
|
}
|
||||||
@@ -267,6 +294,10 @@ Endpoints
|
|||||||
"condition_ignore_voucher_discounted": false,
|
"condition_ignore_voucher_discounted": false,
|
||||||
"condition_min_count": 3,
|
"condition_min_count": 3,
|
||||||
"condition_min_value": "0.00",
|
"condition_min_value": "0.00",
|
||||||
|
"benefit_same_products": true,
|
||||||
|
"benefit_limit_products": [],
|
||||||
|
"benefit_apply_to_addons": true,
|
||||||
|
"benefit_ignore_voucher_discounted": false,
|
||||||
"benefit_discount_matching_percent": "100.00",
|
"benefit_discount_matching_percent": "100.00",
|
||||||
"benefit_only_apply_to_cheapest_n_matches": 1
|
"benefit_only_apply_to_cheapest_n_matches": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ The invoice resource contains the following public fields:
|
|||||||
Field Type Description
|
Field Type Description
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
number string Invoice number (with prefix)
|
number string Invoice number (with prefix)
|
||||||
|
event string The slug of the parent event
|
||||||
order string Order code of the order this invoice belongs to
|
order string Order code of the order this invoice belongs to
|
||||||
is_cancellation boolean ``true``, if this invoice is the cancellation of a
|
is_cancellation boolean ``true``, if this invoice is the cancellation of a
|
||||||
different invoice.
|
different invoice.
|
||||||
@@ -121,9 +122,13 @@ internal_reference string Customer's refe
|
|||||||
|
|
||||||
The attribute ``lines.subevent`` has been added.
|
The attribute ``lines.subevent`` has been added.
|
||||||
|
|
||||||
|
.. versionchanged:: 2023.8
|
||||||
|
|
||||||
Endpoints
|
The ``event`` attribute has been added. The organizer-level endpoint has been added.
|
||||||
---------
|
|
||||||
|
|
||||||
|
List of all invoices
|
||||||
|
--------------------
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/
|
||||||
|
|
||||||
@@ -152,6 +157,7 @@ Endpoints
|
|||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"number": "SAMPLECONF-00001",
|
"number": "SAMPLECONF-00001",
|
||||||
|
"event": "sampleconf",
|
||||||
"order": "ABC12",
|
"order": "ABC12",
|
||||||
"is_cancellation": false,
|
"is_cancellation": false,
|
||||||
"invoice_from_name": "Big Events LLC",
|
"invoice_from_name": "Big Events LLC",
|
||||||
@@ -221,6 +227,50 @@ Endpoints
|
|||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/invoices/
|
||||||
|
|
||||||
|
Returns a list of all invoices within all events of a given organizer (with sufficient access permissions).
|
||||||
|
|
||||||
|
Supported query parameters and output format of this endpoint are identical to the list endpoint within an event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"number": "SAMPLECONF-00001",
|
||||||
|
"event": "sampleconf",
|
||||||
|
"order": "ABC12",
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
|
|
||||||
|
Fetching individual invoices
|
||||||
|
----------------------------
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/
|
||||||
|
|
||||||
Returns information on one invoice, identified by its invoice number.
|
Returns information on one invoice, identified by its invoice number.
|
||||||
@@ -243,6 +293,7 @@ Endpoints
|
|||||||
|
|
||||||
{
|
{
|
||||||
"number": "SAMPLECONF-00001",
|
"number": "SAMPLECONF-00001",
|
||||||
|
"event": "sampleconf",
|
||||||
"order": "ABC12",
|
"order": "ABC12",
|
||||||
"is_cancellation": false,
|
"is_cancellation": false,
|
||||||
"invoice_from_name": "Big Events LLC",
|
"invoice_from_name": "Big Events LLC",
|
||||||
@@ -337,6 +388,12 @@ Endpoints
|
|||||||
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
|
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
|
||||||
seconds.
|
seconds.
|
||||||
|
|
||||||
|
|
||||||
|
Modifying invoices
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Invoices cannot be edited directly, but the following actions can be triggered:
|
||||||
|
|
||||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/reissue/
|
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/reissue/
|
||||||
|
|
||||||
Cancels the invoice and creates a new one.
|
Cancels the invoice and creates a new one.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ The order resource contains the following public fields:
|
|||||||
Field Type Description
|
Field Type Description
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
code string Order code
|
code string Order code
|
||||||
|
event string The slug of the parent event
|
||||||
status string Order status, one of:
|
status string Order status, one of:
|
||||||
|
|
||||||
* ``n`` – pending
|
* ``n`` – pending
|
||||||
@@ -130,6 +131,10 @@ last_modified datetime Last modificati
|
|||||||
|
|
||||||
The ``valid_if_pending`` attribute has been added.
|
The ``valid_if_pending`` attribute has been added.
|
||||||
|
|
||||||
|
.. versionchanged:: 2023.8
|
||||||
|
|
||||||
|
The ``event`` attribute has been added. The organizer-level endpoint has been added.
|
||||||
|
|
||||||
|
|
||||||
.. _order-position-resource:
|
.. _order-position-resource:
|
||||||
|
|
||||||
@@ -289,6 +294,7 @@ List of all orders
|
|||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"code": "ABC12",
|
"code": "ABC12",
|
||||||
|
"event": "sampleconf",
|
||||||
"status": "p",
|
"status": "p",
|
||||||
"testmode": false,
|
"testmode": false,
|
||||||
"secret": "k24fiuwvu8kxz3y1",
|
"secret": "k24fiuwvu8kxz3y1",
|
||||||
@@ -441,6 +447,48 @@ List of all orders
|
|||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/orders/
|
||||||
|
|
||||||
|
Returns a list of all orders within all events of a given organizer (with sufficient access permissions).
|
||||||
|
|
||||||
|
Supported query parameters and output format of this endpoint are identical to the list endpoint within an event,
|
||||||
|
with the exception that the ``pdf_data`` parameter is not supported here.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/orders/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Page-Generated: 2017-12-01T10:00:00Z
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"code": "ABC12",
|
||||||
|
"event": "sampleconf",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
Fetching individual orders
|
Fetching individual orders
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -466,6 +514,7 @@ Fetching individual orders
|
|||||||
|
|
||||||
{
|
{
|
||||||
"code": "ABC12",
|
"code": "ABC12",
|
||||||
|
"event": "sampleconf",
|
||||||
"status": "p",
|
"status": "p",
|
||||||
"testmode": false,
|
"testmode": false,
|
||||||
"secret": "k24fiuwvu8kxz3y1",
|
"secret": "k24fiuwvu8kxz3y1",
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ limit_products list of integers List of product
|
|||||||
restrict_to_status list List of order states to restrict recipients to. Valid
|
restrict_to_status list List of order states to restrict recipients to. Valid
|
||||||
entries are ``p`` for paid, ``e`` for expired, ``c`` for canceled,
|
entries are ``p`` for paid, ``e`` for expired, ``c`` for canceled,
|
||||||
``n__pending_approval`` for pending approval,
|
``n__pending_approval`` for pending approval,
|
||||||
``n__not_pending_approval_and_not_valid_if_pending`` for payment pending,
|
``n__not_pending_approval_and_not_valid_if_pending`` for payment
|
||||||
``n__valid_if_pending`` for payment pending but already confirmed,
|
pending, ``n__valid_if_pending`` for payment pending but already confirmed,
|
||||||
and ``n__pending_overdue`` for pending with payment overdue.
|
and ``n__pending_overdue`` for pending with payment overdue.
|
||||||
The default is ``["p", "n__valid_if_pending"]``.
|
The default is ``["p", "n__valid_if_pending"]``.
|
||||||
|
checked_in_status string Check-in status to restrict recipients to. Valid strings are:
|
||||||
|
``null`` for no filtering (default), ``checked_in`` for
|
||||||
|
limiting to attendees that are or have been checked in, and
|
||||||
|
``no_checkin`` for limiting to attendees who have not checked in.
|
||||||
date_is_absolute boolean If ``true``, the email is set at a specific point in time.
|
date_is_absolute boolean If ``true``, the email is set at a specific point in time.
|
||||||
send_date datetime If ``date_is_absolute`` is set: Date and time to send the email.
|
send_date datetime If ``date_is_absolute`` is set: Date and time to send the email.
|
||||||
send_offset_days integer If ``date_is_absolute`` is not set, this is the number of days
|
send_offset_days integer If ``date_is_absolute`` is not set, this is the number of days
|
||||||
@@ -89,6 +93,7 @@ Endpoints
|
|||||||
"n__not_pending_approval_and_not_valid_if_pending",
|
"n__not_pending_approval_and_not_valid_if_pending",
|
||||||
"n__valid_if_pending"
|
"n__valid_if_pending"
|
||||||
],
|
],
|
||||||
|
"checked_in_status": null,
|
||||||
"send_date": null,
|
"send_date": null,
|
||||||
"send_offset_days": 1,
|
"send_offset_days": 1,
|
||||||
"send_offset_time": "18:00",
|
"send_offset_time": "18:00",
|
||||||
@@ -139,6 +144,7 @@ Endpoints
|
|||||||
"n__not_pending_approval_and_not_valid_if_pending",
|
"n__not_pending_approval_and_not_valid_if_pending",
|
||||||
"n__valid_if_pending"
|
"n__valid_if_pending"
|
||||||
],
|
],
|
||||||
|
"checked_in_status": null,
|
||||||
"send_date": null,
|
"send_date": null,
|
||||||
"send_offset_days": 1,
|
"send_offset_days": 1,
|
||||||
"send_offset_time": "18:00",
|
"send_offset_time": "18:00",
|
||||||
@@ -180,6 +186,7 @@ Endpoints
|
|||||||
"n__not_pending_approval_and_not_valid_if_pending",
|
"n__not_pending_approval_and_not_valid_if_pending",
|
||||||
"n__valid_if_pending"
|
"n__valid_if_pending"
|
||||||
],
|
],
|
||||||
|
"checked_in_status": "checked_in",
|
||||||
"send_date": null,
|
"send_date": null,
|
||||||
"send_offset_days": 1,
|
"send_offset_days": 1,
|
||||||
"send_offset_time": "18:00",
|
"send_offset_time": "18:00",
|
||||||
@@ -209,6 +216,7 @@ Endpoints
|
|||||||
"n__not_pending_approval_and_not_valid_if_pending",
|
"n__not_pending_approval_and_not_valid_if_pending",
|
||||||
"n__valid_if_pending"
|
"n__valid_if_pending"
|
||||||
],
|
],
|
||||||
|
"checked_in_status": "checked_in",
|
||||||
"send_date": null,
|
"send_date": null,
|
||||||
"send_offset_days": 1,
|
"send_offset_days": 1,
|
||||||
"send_offset_time": "18:00",
|
"send_offset_time": "18:00",
|
||||||
@@ -266,6 +274,7 @@ Endpoints
|
|||||||
"n__not_pending_approval_and_not_valid_if_pending",
|
"n__not_pending_approval_and_not_valid_if_pending",
|
||||||
"n__valid_if_pending"
|
"n__valid_if_pending"
|
||||||
],
|
],
|
||||||
|
"checked_in_status": "checked_in",
|
||||||
"send_date": null,
|
"send_date": null,
|
||||||
"send_offset_days": 1,
|
"send_offset_days": 1,
|
||||||
"send_offset_time": "18:00",
|
"send_offset_time": "18:00",
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ The following values for ``action_types`` are valid with pretix core:
|
|||||||
* ``pretix.event.live.deactivated``
|
* ``pretix.event.live.deactivated``
|
||||||
* ``pretix.event.testmode.activated``
|
* ``pretix.event.testmode.activated``
|
||||||
* ``pretix.event.testmode.deactivated``
|
* ``pretix.event.testmode.deactivated``
|
||||||
|
* ``pretix.customer.created``
|
||||||
|
* ``pretix.customer.changed``
|
||||||
|
* ``pretix.customer.anonymized``
|
||||||
|
|
||||||
Installed plugins might register more valid values.
|
Installed plugins might register more valid values.
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ you to execute a piece of code with a different locale:
|
|||||||
This is very useful e.g. when sending an email to a user that has a different language than the user performing the
|
This is very useful e.g. when sending an email to a user that has a different language than the user performing the
|
||||||
action that causes the mail to be sent.
|
action that causes the mail to be sent.
|
||||||
|
|
||||||
.. _translation features: https://docs.djangoproject.com/en/1.9/topics/i18n/translation/
|
.. _translation features: https://docs.djangoproject.com/en/4.2/topics/i18n/translation/
|
||||||
.. _GNU gettext: https://www.gnu.org/software/gettext/
|
.. _GNU gettext: https://www.gnu.org/software/gettext/
|
||||||
.. _strings: https://django-i18nfield.readthedocs.io/en/latest/strings.html
|
.. _strings: https://django-i18nfield.readthedocs.io/en/latest/strings.html
|
||||||
.. _database fields: https://django-i18nfield.readthedocs.io/en/latest/quickstart.html
|
.. _database fields: https://django-i18nfield.readthedocs.io/en/latest/quickstart.html
|
||||||
|
|||||||
@@ -15,25 +15,33 @@ and the admin panel is available at ``https://pretix.eu/control/event/bigorg/awe
|
|||||||
|
|
||||||
If the organizer now configures a custom domain like ``tickets.bigorg.com``, his event will
|
If the organizer now configures a custom domain like ``tickets.bigorg.com``, his event will
|
||||||
from now on be available on ``https://tickets.bigorg.com/awesomecon/``. The former URL at
|
from now on be available on ``https://tickets.bigorg.com/awesomecon/``. The former URL at
|
||||||
``pretix.eu`` will redirect there. However, the admin panel will still only be available
|
``pretix.eu`` will redirect there. It's also possible to do this for just an event, in which
|
||||||
on ``pretix.eu`` for convenience and security reasons.
|
case the event will be available on ``https://tickets.awesomecon.org/``.
|
||||||
|
|
||||||
|
However, the admin panel will still only be available on ``pretix.eu`` for convenience and security reasons.
|
||||||
|
|
||||||
URL routing
|
URL routing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The hard part about implementing this URL routing in Django is that
|
The hard part about implementing this URL routing in Django is that
|
||||||
``https://pretix.eu/bigorg/awesomecon/`` contains two parameters of nearly arbitrary content
|
``https://pretix.eu/bigorg/awesomecon/`` contains two parameters of nearly arbitrary content
|
||||||
and ``https://tickets.bigorg.com/awesomecon/`` contains only one. The only robust way to do
|
and ``https://tickets.bigorg.com/awesomecon/`` contains only one and ``https://tickets.awesomecon.org/`` does not contain any.
|
||||||
this is by having *separate* URL configuration for those two cases. In pretix, we call the
|
The only robust way to do this is by having *separate* URL configuration for those three cases.
|
||||||
former our ``maindomain`` config and the latter our ``subdomain`` config. For pretix's core
|
|
||||||
modules we do some magic to avoid duplicate configuration, but for a fairly simple plugin with
|
|
||||||
only a handful of routes, we recommend just configuring the two URL sets separately.
|
|
||||||
|
|
||||||
|
In pretix, we therefore do not have a global URL configuration, but three, living in the following modules:
|
||||||
|
|
||||||
|
- ``pretix.multidomain.maindomain_urlconf``
|
||||||
|
- ``pretix.multidomain.organizer_domain_urlconf``
|
||||||
|
- ``pretix.multidomain.event_domain_urlconf``
|
||||||
|
|
||||||
|
We provide some helper utilities to work with these to avoid duplicate configuration of the individual URLs.
|
||||||
The file ``urls.py`` inside your plugin package will be loaded and scanned for URL configuration
|
The file ``urls.py`` inside your plugin package will be loaded and scanned for URL configuration
|
||||||
automatically and should be provided by any plugin that provides any view.
|
automatically and should be provided by any plugin that provides any view.
|
||||||
|
However, unlike plain Django, we look not only for a ``urlpatterns`` attribute on the module but support other
|
||||||
|
attributes like ``event_patterns`` and ``organizer_patterns`` as well.
|
||||||
|
|
||||||
A very basic example that provides one view in the admin panel and one view in the frontend
|
For example, for a simple plugin that adds one URL to the backend and one event-level URL to the frontend, you can
|
||||||
could look like this::
|
create the following configuration in your ``urls.py``::
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
|
|
||||||
@@ -52,7 +60,7 @@ could look like this::
|
|||||||
As you can see, the view in the frontend is not included in the standard Django ``urlpatterns``
|
As you can see, the view in the frontend is not included in the standard Django ``urlpatterns``
|
||||||
setting but in a separate list with the name ``event_patterns``. This will automatically prepend
|
setting but in a separate list with the name ``event_patterns``. This will automatically prepend
|
||||||
the appropriate parameters to the regex (e.g. the event or the event and the organizer, depending
|
the appropriate parameters to the regex (e.g. the event or the event and the organizer, depending
|
||||||
on the called domain).
|
on the called domain). For organizer-level views, ``organizer_patterns`` works the same way.
|
||||||
|
|
||||||
If you only provide URLs in the admin area, you do not need to provide a ``event_patterns`` attribute.
|
If you only provide URLs in the admin area, you do not need to provide a ``event_patterns`` attribute.
|
||||||
|
|
||||||
@@ -71,11 +79,16 @@ is a python method that emulates a behavior similar to ``reverse``:
|
|||||||
|
|
||||||
.. autofunction:: pretix.multidomain.urlreverse.eventreverse
|
.. autofunction:: pretix.multidomain.urlreverse.eventreverse
|
||||||
|
|
||||||
|
If you need to communicate the URL externally, you can use a different method to ensure that it is always an absolute URL:
|
||||||
|
|
||||||
|
.. autofunction:: pretix.multidomain.urlreverse.build_absolute_uri
|
||||||
|
|
||||||
In addition, there is a template tag that works similar to ``url`` but takes an event or organizer object
|
In addition, there is a template tag that works similar to ``url`` but takes an event or organizer object
|
||||||
as its first argument and can be used like this::
|
as its first argument and can be used like this::
|
||||||
|
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
<a href="{% eventurl request.event "presale:event.checkout" step="payment" %}">Pay</a>
|
<a href="{% eventurl request.event "presale:event.checkout" step="payment" %}">Pay</a>
|
||||||
|
<a href="{% abseventurl request.event "presale:event.checkout" step="payment" %}">Pay</a>
|
||||||
|
|
||||||
|
|
||||||
Implementation details
|
Implementation details
|
||||||
|
|||||||
@@ -96,6 +96,20 @@ http://localhost:8000/control/ for the admin view.
|
|||||||
port (for example because you develop on `pretixdroid`_), you can check
|
port (for example because you develop on `pretixdroid`_), you can check
|
||||||
`Django's documentation`_ for more options.
|
`Django's documentation`_ for more options.
|
||||||
|
|
||||||
|
When running the local development webserver, ensure Celery is not configured
|
||||||
|
in ``pretix.cfg``. i.e., you should remove anything such as::
|
||||||
|
|
||||||
|
[celery]
|
||||||
|
backend=redis://redis:6379/2
|
||||||
|
broker=redis://redis:6379/2
|
||||||
|
|
||||||
|
If you choose to use Celery for development, you must also start a Celery worker
|
||||||
|
process::
|
||||||
|
|
||||||
|
celery -A pretix.celery_app worker -l info
|
||||||
|
|
||||||
|
However, beware that code changes will not auto-reload within Celery.
|
||||||
|
|
||||||
.. _`checksandtests`:
|
.. _`checksandtests`:
|
||||||
|
|
||||||
Code checks and unit tests
|
Code checks and unit tests
|
||||||
|
|||||||
+3
-2
@@ -36,7 +36,7 @@ dependencies = [
|
|||||||
"css-inline==0.8.*",
|
"css-inline==0.8.*",
|
||||||
"defusedcsv>=1.1.0",
|
"defusedcsv>=1.1.0",
|
||||||
"dj-static",
|
"dj-static",
|
||||||
"Django==4.1.*",
|
"Django==4.2.*",
|
||||||
"django-bootstrap3==23.1.*",
|
"django-bootstrap3==23.1.*",
|
||||||
"django-compressor==4.3.*",
|
"django-compressor==4.3.*",
|
||||||
"django-countries==7.5.*",
|
"django-countries==7.5.*",
|
||||||
@@ -90,7 +90,7 @@ dependencies = [
|
|||||||
"pytz-deprecation-shim==0.1.*",
|
"pytz-deprecation-shim==0.1.*",
|
||||||
"pyuca",
|
"pyuca",
|
||||||
"qrcode==7.4.*",
|
"qrcode==7.4.*",
|
||||||
"redis==4.5.*,>=4.5.4",
|
"redis==4.6.*",
|
||||||
"reportlab==4.0.*",
|
"reportlab==4.0.*",
|
||||||
"requests==2.31.*",
|
"requests==2.31.*",
|
||||||
"sentry-sdk==1.15.*",
|
"sentry-sdk==1.15.*",
|
||||||
@@ -112,6 +112,7 @@ memcached = ["pylibmc"]
|
|||||||
dev = [
|
dev = [
|
||||||
"coverage",
|
"coverage",
|
||||||
"coveralls",
|
"coveralls",
|
||||||
|
"fakeredis==2.18.*",
|
||||||
"flake8==6.0.*",
|
"flake8==6.0.*",
|
||||||
"freezegun",
|
"freezegun",
|
||||||
"isort==5.12.*",
|
"isort==5.12.*",
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2023.7.0"
|
__version__ = "2023.8.0.dev0"
|
||||||
|
|||||||
@@ -196,7 +196,14 @@ STATICFILES_DIRS = [
|
|||||||
|
|
||||||
STATICI18N_ROOT = os.path.join(BASE_DIR, "pretix/static")
|
STATICI18N_ROOT = os.path.join(BASE_DIR, "pretix/static")
|
||||||
|
|
||||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# if os.path.exists(os.path.join(DATA_DIR, 'static')):
|
# if os.path.exists(os.path.join(DATA_DIR, 'static')):
|
||||||
# STATICFILES_DIRS.insert(0, os.path.join(DATA_DIR, 'static'))
|
# STATICFILES_DIRS.insert(0, os.path.join(DATA_DIR, 'static'))
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ class DiscountSerializer(I18nAwareModelSerializer):
|
|||||||
'available_until', 'subevent_mode', 'condition_all_products', 'condition_limit_products',
|
'available_until', 'subevent_mode', 'condition_all_products', 'condition_limit_products',
|
||||||
'condition_apply_to_addons', 'condition_min_count', 'condition_min_value',
|
'condition_apply_to_addons', 'condition_min_count', 'condition_min_value',
|
||||||
'benefit_discount_matching_percent', 'benefit_only_apply_to_cheapest_n_matches',
|
'benefit_discount_matching_percent', 'benefit_only_apply_to_cheapest_n_matches',
|
||||||
'condition_ignore_voucher_discounted')
|
'benefit_same_products', 'benefit_limit_products', 'benefit_apply_to_addons',
|
||||||
|
'benefit_ignore_voucher_discounted', 'condition_ignore_voucher_discounted')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['condition_limit_products'].queryset = self.context['event'].items.all()
|
self.fields['condition_limit_products'].queryset = self.context['event'].items.all()
|
||||||
|
self.fields['benefit_limit_products'].queryset = self.context['event'].items.all()
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from decimal import Decimal
|
|||||||
import pycountry
|
import pycountry
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@@ -283,11 +284,12 @@ class FailedCheckinSerializer(I18nAwareModelSerializer):
|
|||||||
raw_item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
|
raw_item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
|
||||||
raw_variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
|
raw_variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
|
||||||
raw_subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
|
raw_subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
|
||||||
|
nonce = serializers.CharField(required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Checkin
|
model = Checkin
|
||||||
fields = ('error_reason', 'error_explanation', 'raw_barcode', 'raw_item', 'raw_variation',
|
fields = ('error_reason', 'error_explanation', 'raw_barcode', 'raw_item', 'raw_variation',
|
||||||
'raw_subevent', 'datetime', 'type', 'position')
|
'raw_subevent', 'nonce', 'datetime', 'type', 'position')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -372,11 +374,15 @@ class PdfDataSerializer(serializers.Field):
|
|||||||
self.context['vars_images'] = get_images(self.context['event'])
|
self.context['vars_images'] = get_images(self.context['event'])
|
||||||
|
|
||||||
for k, f in self.context['vars'].items():
|
for k, f in self.context['vars'].items():
|
||||||
try:
|
if 'evaluate_bulk' in f:
|
||||||
res[k] = f['evaluate'](instance, instance.order, ev)
|
# Will be evaluated later by our list serializers
|
||||||
except:
|
res[k] = (f['evaluate_bulk'], instance)
|
||||||
logger.exception('Evaluating PDF variable failed')
|
else:
|
||||||
res[k] = '(error)'
|
try:
|
||||||
|
res[k] = f['evaluate'](instance, instance.order, ev)
|
||||||
|
except:
|
||||||
|
logger.exception('Evaluating PDF variable failed')
|
||||||
|
res[k] = '(error)'
|
||||||
|
|
||||||
if not hasattr(ev, '_cached_meta_data'):
|
if not hasattr(ev, '_cached_meta_data'):
|
||||||
ev._cached_meta_data = ev.meta_data
|
ev._cached_meta_data = ev.meta_data
|
||||||
@@ -429,6 +435,38 @@ class PdfDataSerializer(serializers.Field):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPositionListSerializer(serializers.ListSerializer):
|
||||||
|
|
||||||
|
def to_representation(self, data):
|
||||||
|
# We have a custom implementation of this method because PdfDataSerializer() might keep some elements unevaluated
|
||||||
|
# with a (callable, input) tuple. We'll loop over these entries and evaluate them bulk-wise to save on SQL queries.
|
||||||
|
|
||||||
|
if isinstance(self.parent, OrderSerializer) and isinstance(self.parent.parent, OrderListSerializer):
|
||||||
|
# Do not execute our custom code because it will be executed by OrderListSerializer later for the
|
||||||
|
# full result set.
|
||||||
|
return super().to_representation(data)
|
||||||
|
|
||||||
|
iterable = data.all() if isinstance(data, models.Manager) else data
|
||||||
|
|
||||||
|
data = []
|
||||||
|
evaluate_queue = defaultdict(list)
|
||||||
|
|
||||||
|
for item in iterable:
|
||||||
|
entry = self.child.to_representation(item)
|
||||||
|
if "pdf_data" in entry:
|
||||||
|
for k, v in entry["pdf_data"].items():
|
||||||
|
if isinstance(v, tuple) and callable(v[0]):
|
||||||
|
evaluate_queue[v[0]].append((v[1], entry, k))
|
||||||
|
data.append(entry)
|
||||||
|
|
||||||
|
for func, entries in evaluate_queue.items():
|
||||||
|
results = func([item for (item, entry, k) in entries])
|
||||||
|
for (item, entry, k), result in zip(entries, results):
|
||||||
|
entry["pdf_data"][k] = result
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class OrderPositionSerializer(I18nAwareModelSerializer):
|
class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||||
checkins = CheckinSerializer(many=True, read_only=True)
|
checkins = CheckinSerializer(many=True, read_only=True)
|
||||||
answers = AnswerSerializer(many=True)
|
answers = AnswerSerializer(many=True)
|
||||||
@@ -440,6 +478,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
|||||||
attendee_name = serializers.CharField(required=False)
|
attendee_name = serializers.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
list_serializer_class = OrderPositionListSerializer
|
||||||
model = OrderPosition
|
model = OrderPosition
|
||||||
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
|
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
|
||||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
|
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
|
||||||
@@ -468,6 +507,20 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
|||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
raise TypeError("this serializer is readonly")
|
raise TypeError("this serializer is readonly")
|
||||||
|
|
||||||
|
def to_representation(self, data):
|
||||||
|
if isinstance(self.parent, (OrderListSerializer, OrderPositionListSerializer)):
|
||||||
|
# Do not execute our custom code because it will be executed by OrderListSerializer later for the
|
||||||
|
# full result set.
|
||||||
|
return super().to_representation(data)
|
||||||
|
|
||||||
|
entry = super().to_representation(data)
|
||||||
|
if "pdf_data" in entry:
|
||||||
|
for k, v in entry["pdf_data"].items():
|
||||||
|
if isinstance(v, tuple) and callable(v[0]):
|
||||||
|
entry["pdf_data"][k] = v[0]([v[1]])[0]
|
||||||
|
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
class RequireAttentionField(serializers.Field):
|
class RequireAttentionField(serializers.Field):
|
||||||
def to_representation(self, instance: OrderPosition):
|
def to_representation(self, instance: OrderPosition):
|
||||||
@@ -562,7 +615,7 @@ class PaymentURLField(serializers.URLField):
|
|||||||
def to_representation(self, instance: OrderPayment):
|
def to_representation(self, instance: OrderPayment):
|
||||||
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
|
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||||
return None
|
return None
|
||||||
return build_absolute_uri(self.context['event'], 'presale:event.order.pay', kwargs={
|
return build_absolute_uri(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||||
'order': instance.order.code,
|
'order': instance.order.code,
|
||||||
'secret': instance.order.secret,
|
'secret': instance.order.secret,
|
||||||
'payment': instance.pk,
|
'payment': instance.pk,
|
||||||
@@ -607,13 +660,42 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class OrderURLField(serializers.URLField):
|
class OrderURLField(serializers.URLField):
|
||||||
def to_representation(self, instance: Order):
|
def to_representation(self, instance: Order):
|
||||||
return build_absolute_uri(self.context['event'], 'presale:event.order', kwargs={
|
return build_absolute_uri(instance.event, 'presale:event.order', kwargs={
|
||||||
'order': instance.code,
|
'order': instance.code,
|
||||||
'secret': instance.secret,
|
'secret': instance.secret,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class OrderListSerializer(serializers.ListSerializer):
|
||||||
|
|
||||||
|
def to_representation(self, data):
|
||||||
|
# We have a custom implementation of this method because PdfDataSerializer() might keep some elements
|
||||||
|
# unevaluated with a (callable, input) tuple. We'll loop over these entries and evaluate them bulk-wise to
|
||||||
|
# save on SQL queries.
|
||||||
|
iterable = data.all() if isinstance(data, models.Manager) else data
|
||||||
|
|
||||||
|
data = []
|
||||||
|
evaluate_queue = defaultdict(list)
|
||||||
|
|
||||||
|
for item in iterable:
|
||||||
|
entry = self.child.to_representation(item)
|
||||||
|
for p in entry.get("positions", []):
|
||||||
|
if "pdf_data" in p:
|
||||||
|
for k, v in p["pdf_data"].items():
|
||||||
|
if isinstance(v, tuple) and callable(v[0]):
|
||||||
|
evaluate_queue[v[0]].append((v[1], p, k))
|
||||||
|
data.append(entry)
|
||||||
|
|
||||||
|
for func, entries in evaluate_queue.items():
|
||||||
|
results = func([item for (item, entry, k) in entries])
|
||||||
|
for (item, entry, k), result in zip(entries, results):
|
||||||
|
entry["pdf_data"][k] = result
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class OrderSerializer(I18nAwareModelSerializer):
|
class OrderSerializer(I18nAwareModelSerializer):
|
||||||
|
event = SlugRelatedField(slug_field='slug', read_only=True)
|
||||||
invoice_address = InvoiceAddressSerializer(allow_null=True)
|
invoice_address = InvoiceAddressSerializer(allow_null=True)
|
||||||
positions = OrderPositionSerializer(many=True, read_only=True)
|
positions = OrderPositionSerializer(many=True, read_only=True)
|
||||||
fees = OrderFeeSerializer(many=True, read_only=True)
|
fees = OrderFeeSerializer(many=True, read_only=True)
|
||||||
@@ -627,8 +709,9 @@ class OrderSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
|
list_serializer_class = OrderListSerializer
|
||||||
fields = (
|
fields = (
|
||||||
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||||
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||||
'url', 'customer', 'valid_if_pending'
|
'url', 'customer', 'valid_if_pending'
|
||||||
@@ -1512,6 +1595,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class InvoiceSerializer(I18nAwareModelSerializer):
|
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||||
|
event = SlugRelatedField(slug_field='slug', read_only=True)
|
||||||
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
||||||
refers = serializers.SlugRelatedField(slug_field='full_invoice_no', read_only=True)
|
refers = serializers.SlugRelatedField(slug_field='full_invoice_no', read_only=True)
|
||||||
lines = InlineInvoiceLineSerializer(many=True)
|
lines = InlineInvoiceLineSerializer(many=True)
|
||||||
@@ -1520,7 +1604,7 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Invoice
|
model = Invoice
|
||||||
fields = ('order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
||||||
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||||
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
||||||
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ class CustomerSerializer(I18nAwareModelSerializer):
|
|||||||
data['name_parts']['_scheme'] = self.context['request'].organizer.settings.name_scheme
|
data['name_parts']['_scheme'] = self.context['request'].organizer.settings.name_scheme
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def validate_email(self, value):
|
||||||
|
qs = Customer.objects.filter(organizer=self.context['organizer'], email__iexact=value)
|
||||||
|
if self.instance and self.instance.pk:
|
||||||
|
qs = qs.exclude(pk=self.instance.pk)
|
||||||
|
if qs.exists():
|
||||||
|
raise ValidationError(_("An account with this email address is already registered."))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class CustomerCreateSerializer(CustomerSerializer):
|
class CustomerCreateSerializer(CustomerSerializer):
|
||||||
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ orga_router.register(r'membershiptypes', organizer.MembershipTypeViewSet)
|
|||||||
orga_router.register(r'reusablemedia', media.ReusableMediaViewSet)
|
orga_router.register(r'reusablemedia', media.ReusableMediaViewSet)
|
||||||
orga_router.register(r'teams', organizer.TeamViewSet)
|
orga_router.register(r'teams', organizer.TeamViewSet)
|
||||||
orga_router.register(r'devices', organizer.DeviceViewSet)
|
orga_router.register(r'devices', organizer.DeviceViewSet)
|
||||||
|
orga_router.register(r'orders', order.OrganizerOrderViewSet)
|
||||||
|
orga_router.register(r'invoices', order.InvoiceViewSet)
|
||||||
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
|
||||||
|
|
||||||
team_router = routers.DefaultRouter()
|
team_router = routers.DefaultRouter()
|
||||||
@@ -77,7 +79,7 @@ event_router.register(r'questions', item.QuestionViewSet)
|
|||||||
event_router.register(r'discounts', discount.DiscountViewSet)
|
event_router.register(r'discounts', discount.DiscountViewSet)
|
||||||
event_router.register(r'quotas', item.QuotaViewSet)
|
event_router.register(r'quotas', item.QuotaViewSet)
|
||||||
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
||||||
event_router.register(r'orders', order.OrderViewSet)
|
event_router.register(r'orders', order.EventOrderViewSet)
|
||||||
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
||||||
event_router.register(r'invoices', order.InvoiceViewSet)
|
event_router.register(r'invoices', order.InvoiceViewSet)
|
||||||
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
|
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
|
||||||
|
|||||||
@@ -164,8 +164,21 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
secret=serializer.validated_data['raw_barcode']
|
secret=serializer.validated_data['raw_barcode']
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
clist = self.get_object()
|
||||||
|
if serializer.validated_data.get('nonce'):
|
||||||
|
if kwargs.get('position'):
|
||||||
|
prev = kwargs['position'].all_checkins.filter(nonce=serializer.validated_data['nonce']).first()
|
||||||
|
else:
|
||||||
|
prev = clist.checkins.filter(
|
||||||
|
nonce=serializer.validated_data['nonce'],
|
||||||
|
raw_barcode=serializer.validated_data['raw_barcode'],
|
||||||
|
).first()
|
||||||
|
if prev:
|
||||||
|
# Ignore because nonce is already handled
|
||||||
|
return Response(serializer.data, status=201)
|
||||||
|
|
||||||
c = serializer.save(
|
c = serializer.save(
|
||||||
list=self.get_object(),
|
list=clist,
|
||||||
successful=False,
|
successful=False,
|
||||||
forced=True,
|
forced=True,
|
||||||
force_sent=True,
|
force_sent=True,
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ class InitializeView(APIView):
|
|||||||
device.software_brand = serializer.validated_data.get('software_brand')
|
device.software_brand = serializer.validated_data.get('software_brand')
|
||||||
device.software_version = serializer.validated_data.get('software_version')
|
device.software_version = serializer.validated_data.get('software_version')
|
||||||
device.info = serializer.validated_data.get('info')
|
device.info = serializer.validated_data.get('info')
|
||||||
print(serializer.validated_data, request.data)
|
|
||||||
device.rsa_pubkey = serializer.validated_data.get('rsa_pubkey')
|
device.rsa_pubkey = serializer.validated_data.get('rsa_pubkey')
|
||||||
device.api_token = generate_api_token()
|
device.api_token = generate_api_token()
|
||||||
device.save()
|
device.save()
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
'subeventitem_set',
|
'subeventitem_set',
|
||||||
'subeventitemvariation_set',
|
'subeventitemvariation_set',
|
||||||
'meta_values',
|
'meta_values',
|
||||||
|
'meta_values__property',
|
||||||
Prefetch(
|
Prefetch(
|
||||||
'seat_category_mappings',
|
'seat_category_mappings',
|
||||||
to_attr='_seat_category_mappings',
|
to_attr='_seat_category_mappings',
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from rest_framework.exceptions import (
|
|||||||
APIException, NotFound, PermissionDenied, ValidationError,
|
APIException, NotFound, PermissionDenied, ValidationError,
|
||||||
)
|
)
|
||||||
from rest_framework.mixins import CreateModelMixin
|
from rest_framework.mixins import CreateModelMixin
|
||||||
|
from rest_framework.permissions import SAFE_METHODS
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from pretix.api.models import OAuthAccessToken
|
from pretix.api.models import OAuthAccessToken
|
||||||
@@ -185,7 +186,7 @@ with scopes_disabled():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrderViewSet(viewsets.ModelViewSet):
|
class OrderViewSetMixin:
|
||||||
serializer_class = OrderSerializer
|
serializer_class = OrderSerializer
|
||||||
queryset = Order.objects.none()
|
queryset = Order.objects.none()
|
||||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||||
@@ -193,19 +194,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
|
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
|
||||||
filterset_class = OrderFilter
|
filterset_class = OrderFilter
|
||||||
lookup_field = 'code'
|
lookup_field = 'code'
|
||||||
permission = 'can_view_orders'
|
|
||||||
write_permission = 'can_change_orders'
|
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_base_queryset(self):
|
||||||
ctx = super().get_serializer_context()
|
raise NotImplementedError()
|
||||||
ctx['event'] = self.request.event
|
|
||||||
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
|
||||||
ctx['exclude'] = self.request.query_params.getlist('exclude')
|
|
||||||
ctx['include'] = self.request.query_params.getlist('include')
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.request.event.orders
|
qs = self.get_base_queryset()
|
||||||
if 'fees' not in self.request.GET.getlist('exclude'):
|
if 'fees' not in self.request.GET.getlist('exclude'):
|
||||||
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
|
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
|
||||||
fqs = OrderFee.all
|
fqs = OrderFee.all
|
||||||
@@ -227,11 +221,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
opq = OrderPosition.all
|
opq = OrderPosition.all
|
||||||
else:
|
else:
|
||||||
opq = OrderPosition.objects
|
opq = OrderPosition.objects
|
||||||
if request.query_params.get('pdf_data', 'false') == 'true':
|
if request.query_params.get('pdf_data', 'false') == 'true' and getattr(request, 'event', None):
|
||||||
prefetch_related_objects([request.organizer], 'meta_properties')
|
prefetch_related_objects([request.organizer], 'meta_properties')
|
||||||
prefetch_related_objects(
|
prefetch_related_objects(
|
||||||
[request.event],
|
[request.event],
|
||||||
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'), to_attr='meta_values_cached'),
|
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'),
|
||||||
|
to_attr='meta_values_cached'),
|
||||||
'questions',
|
'questions',
|
||||||
'item_meta_properties',
|
'item_meta_properties',
|
||||||
)
|
)
|
||||||
@@ -266,13 +261,12 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_output_provider(self, identifier):
|
def get_serializer_context(self):
|
||||||
responses = register_ticket_outputs.send(self.request.event)
|
ctx = super().get_serializer_context()
|
||||||
for receiver, response in responses:
|
ctx['exclude'] = self.request.query_params.getlist('exclude')
|
||||||
prov = response(self.request.event)
|
ctx['include'] = self.request.query_params.getlist('include')
|
||||||
if prov.identifier == identifier:
|
ctx['pdf_data'] = False
|
||||||
return prov
|
return ctx
|
||||||
raise NotFound('Unknown output provider.')
|
|
||||||
|
|
||||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
|
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
|
||||||
def list(self, request, **kwargs):
|
def list(self, request, **kwargs):
|
||||||
@@ -289,6 +283,45 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data, headers={'X-Page-Generated': date})
|
return Response(serializer.data, headers={'X-Page-Generated': date})
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
|
def get_base_queryset(self):
|
||||||
|
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||||
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
|
return Order.objects.filter(
|
||||||
|
event__organizer=self.request.organizer,
|
||||||
|
event__in=self.request.auth.get_events_with_permission(perm)
|
||||||
|
)
|
||||||
|
elif self.request.user.is_authenticated:
|
||||||
|
return Order.objects.filter(
|
||||||
|
event__organizer=self.request.organizer,
|
||||||
|
event__in=self.request.user.get_events_with_permission(perm)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
|
class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
ctx = super().get_serializer_context()
|
||||||
|
ctx['event'] = self.request.event
|
||||||
|
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def get_base_queryset(self):
|
||||||
|
return self.request.event.orders
|
||||||
|
|
||||||
|
def _get_output_provider(self, identifier):
|
||||||
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
|
for receiver, response in responses:
|
||||||
|
prov = response(self.request.event)
|
||||||
|
if prov.identifier == identifier:
|
||||||
|
return prov
|
||||||
|
raise NotFound('Unknown output provider.')
|
||||||
|
|
||||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||||
def download(self, request, output, **kwargs):
|
def download(self, request, output, **kwargs):
|
||||||
provider = self._get_output_provider(output)
|
provider = self._get_output_provider(output)
|
||||||
@@ -1782,11 +1815,24 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
write_permission = 'can_change_orders'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.invoices.prefetch_related('lines').select_related('order', 'refers').annotate(
|
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||||
|
if getattr(self.request, 'event', None):
|
||||||
|
qs = self.request.event.invoices
|
||||||
|
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
|
qs = Invoice.objects.filter(
|
||||||
|
event__organizer=self.request.organizer,
|
||||||
|
event__in=self.request.auth.get_events_with_permission(perm)
|
||||||
|
)
|
||||||
|
elif self.request.user.is_authenticated:
|
||||||
|
qs = Invoice.objects.filter(
|
||||||
|
event__organizer=self.request.organizer,
|
||||||
|
event__in=self.request.user.get_events_with_permission(perm)
|
||||||
|
)
|
||||||
|
return qs.prefetch_related('lines').select_related('order', 'refers').annotate(
|
||||||
nr=Concat('prefix', 'invoice_no')
|
nr=Concat('prefix', 'invoice_no')
|
||||||
)
|
)
|
||||||
|
|
||||||
@action(detail=True, )
|
@action(detail=True)
|
||||||
def download(self, request, **kwargs):
|
def download(self, request, **kwargs):
|
||||||
invoice = self.get_object()
|
invoice = self.get_object()
|
||||||
|
|
||||||
@@ -1805,7 +1851,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def regenerate(self, request, **kwarts):
|
def regenerate(self, request, **kwargs):
|
||||||
inv = self.get_object()
|
inv = self.get_object()
|
||||||
if inv.canceled:
|
if inv.canceled:
|
||||||
raise ValidationError('The invoice has already been canceled.')
|
raise ValidationError('The invoice has already been canceled.')
|
||||||
@@ -1815,7 +1861,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
raise PermissionDenied('The invoice file is no longer stored on the server.')
|
||||||
elif inv.sent_to_organizer:
|
elif inv.sent_to_organizer:
|
||||||
raise PermissionDenied('The invoice file has already been exported.')
|
raise PermissionDenied('The invoice file has already been exported.')
|
||||||
elif now().astimezone(self.request.event.timezone).date() - inv.date > datetime.timedelta(days=1):
|
elif now().astimezone(inv.event.timezone).date() - inv.date > datetime.timedelta(days=1):
|
||||||
raise PermissionDenied('The invoice file is too old to be regenerated.')
|
raise PermissionDenied('The invoice file is too old to be regenerated.')
|
||||||
else:
|
else:
|
||||||
inv = regenerate_invoice(inv)
|
inv = regenerate_invoice(inv)
|
||||||
@@ -1830,7 +1876,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def reissue(self, request, **kwarts):
|
def reissue(self, request, **kwargs):
|
||||||
inv = self.get_object()
|
inv = self.get_object()
|
||||||
if inv.canceled:
|
if inv.canceled:
|
||||||
raise ValidationError('The invoice has already been canceled.')
|
raise ValidationError('The invoice has already been canceled.')
|
||||||
|
|||||||
@@ -202,6 +202,21 @@ class ParametrizedWaitingListEntryWebhookEvent(ParametrizedWebhookEvent):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ParametrizedCustomerWebhookEvent(ParametrizedWebhookEvent):
|
||||||
|
|
||||||
|
def build_payload(self, logentry: LogEntry):
|
||||||
|
customer = logentry.content_object
|
||||||
|
if not customer:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'notification_id': logentry.pk,
|
||||||
|
'organizer': customer.organizer.slug,
|
||||||
|
'customer': customer.identifier,
|
||||||
|
'action': logentry.action_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_webhook_events, dispatch_uid="base_register_default_webhook_events")
|
@receiver(register_webhook_events, dispatch_uid="base_register_default_webhook_events")
|
||||||
def register_default_webhook_events(sender, **kwargs):
|
def register_default_webhook_events(sender, **kwargs):
|
||||||
return (
|
return (
|
||||||
@@ -350,6 +365,18 @@ def register_default_webhook_events(sender, **kwargs):
|
|||||||
'pretix.event.orders.waitinglist.voucher_assigned',
|
'pretix.event.orders.waitinglist.voucher_assigned',
|
||||||
_('Waiting list entry received voucher'),
|
_('Waiting list entry received voucher'),
|
||||||
),
|
),
|
||||||
|
ParametrizedCustomerWebhookEvent(
|
||||||
|
'pretix.customer.created',
|
||||||
|
_('Customer account created'),
|
||||||
|
),
|
||||||
|
ParametrizedCustomerWebhookEvent(
|
||||||
|
'pretix.customer.changed',
|
||||||
|
_('Customer account changed'),
|
||||||
|
),
|
||||||
|
ParametrizedCustomerWebhookEvent(
|
||||||
|
'pretix.customer.anonymized',
|
||||||
|
_('Customer account anonymized'),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,27 +62,27 @@ class NamespacedCache:
|
|||||||
prefix = int(time.time())
|
prefix = int(time.time())
|
||||||
self.cache.set(self.prefixkey, prefix)
|
self.cache.set(self.prefixkey, prefix)
|
||||||
|
|
||||||
def set(self, key: str, value: str, timeout: int=300):
|
def set(self, key: str, value: any, timeout: int=300):
|
||||||
return self.cache.set(self._prefix_key(key), value, timeout)
|
return self.cache.set(self._prefix_key(key), value, timeout)
|
||||||
|
|
||||||
def get(self, key: str) -> str:
|
def get(self, key: str) -> any:
|
||||||
return self.cache.get(self._prefix_key(key, known_prefix=self._last_prefix))
|
return self.cache.get(self._prefix_key(key, known_prefix=self._last_prefix))
|
||||||
|
|
||||||
def get_or_set(self, key: str, default: Callable, timeout=300) -> str:
|
def get_or_set(self, key: str, default: Callable, timeout=300) -> any:
|
||||||
return self.cache.get_or_set(
|
return self.cache.get_or_set(
|
||||||
self._prefix_key(key, known_prefix=self._last_prefix),
|
self._prefix_key(key, known_prefix=self._last_prefix),
|
||||||
default=default,
|
default=default,
|
||||||
timeout=timeout
|
timeout=timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_many(self, keys: List[str]) -> Dict[str, str]:
|
def get_many(self, keys: List[str]) -> Dict[str, any]:
|
||||||
values = self.cache.get_many([self._prefix_key(key) for key in keys])
|
values = self.cache.get_many([self._prefix_key(key) for key in keys])
|
||||||
newvalues = {}
|
newvalues = {}
|
||||||
for k, v in values.items():
|
for k, v in values.items():
|
||||||
newvalues[self._strip_prefix(k)] = v
|
newvalues[self._strip_prefix(k)] = v
|
||||||
return newvalues
|
return newvalues
|
||||||
|
|
||||||
def set_many(self, values: Dict[str, str], timeout=300):
|
def set_many(self, values: Dict[str, any], timeout=300):
|
||||||
newvalues = {}
|
newvalues = {}
|
||||||
for k, v in values.items():
|
for k, v in values.items():
|
||||||
newvalues[self._prefix_key(k)] = v
|
newvalues[self._prefix_key(k)] = v
|
||||||
|
|||||||
@@ -134,8 +134,11 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
|||||||
def template_name(self):
|
def template_name(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def compile_markdown(self, plaintext):
|
||||||
|
return markdown_compile_email(plaintext)
|
||||||
|
|
||||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
|
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
|
||||||
body_md = markdown_compile_email(plain_body)
|
body_md = self.compile_markdown(plain_body)
|
||||||
htmlctx = {
|
htmlctx = {
|
||||||
'site': settings.PRETIX_INSTANCE_NAME,
|
'site': settings.PRETIX_INSTANCE_NAME,
|
||||||
'site_url': settings.SITE_URL,
|
'site_url': settings.SITE_URL,
|
||||||
@@ -153,7 +156,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
|||||||
|
|
||||||
if plain_signature:
|
if plain_signature:
|
||||||
signature_md = plain_signature.replace('\n', '<br>\n')
|
signature_md = plain_signature.replace('\n', '<br>\n')
|
||||||
signature_md = markdown_compile_email(signature_md)
|
signature_md = self.compile_markdown(signature_md)
|
||||||
htmlctx['signature'] = signature_md
|
htmlctx['signature'] = signature_md
|
||||||
|
|
||||||
if order:
|
if order:
|
||||||
|
|||||||
@@ -549,7 +549,9 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
headers.append(_('End date'))
|
headers.append(_('End date'))
|
||||||
headers += [
|
headers += [
|
||||||
_('Product'),
|
_('Product'),
|
||||||
|
_('Product ID'),
|
||||||
_('Variation'),
|
_('Variation'),
|
||||||
|
_('Variation ID'),
|
||||||
_('Price'),
|
_('Price'),
|
||||||
_('Tax rate'),
|
_('Tax rate'),
|
||||||
_('Tax rule'),
|
_('Tax rule'),
|
||||||
@@ -656,7 +658,9 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
row.append('')
|
row.append('')
|
||||||
row += [
|
row += [
|
||||||
str(op.item),
|
str(op.item),
|
||||||
|
str(op.item_id),
|
||||||
str(op.variation) if op.variation else '',
|
str(op.variation) if op.variation else '',
|
||||||
|
str(op.variation_id) if op.variation_id else '',
|
||||||
op.price,
|
op.price,
|
||||||
op.tax_rate,
|
op.tax_rate,
|
||||||
str(op.tax_rule) if op.tax_rule else '',
|
str(op.tax_rule) if op.tax_rule else '',
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment")
|
(url.url_name == "event.checkout" and url.kwargs['step'] == "payment")
|
||||||
):
|
):
|
||||||
h['script-src'].append('https://pay.google.com')
|
h['script-src'].append('https://pay.google.com')
|
||||||
|
h['frame-src'].append('https://pay.google.com')
|
||||||
|
h['connect-src'].append('https://google.com/pay')
|
||||||
if settings.LOG_CSP:
|
if settings.LOG_CSP:
|
||||||
h['report-uri'] = ["/csp_report/"]
|
h['report-uri'] = ["/csp_report/"]
|
||||||
if 'Content-Security-Policy' in resp:
|
if 'Content-Security-Policy' in resp:
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 4.2.4 on 2023-08-28 12:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("pretixbase", "0244_mediumkeyset"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="discount",
|
||||||
|
name="benefit_apply_to_addons",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="discount",
|
||||||
|
name="benefit_ignore_voucher_discounted",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="discount",
|
||||||
|
name="benefit_limit_products",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="benefit_discounts", to="pretixbase.item"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="discount",
|
||||||
|
name="benefit_same_products",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -97,7 +97,7 @@ def _transactions_mark_order_dirty(order_id, using=None):
|
|||||||
if getattr(dirty_transactions, 'order_ids', None) is None:
|
if getattr(dirty_transactions, 'order_ids', None) is None:
|
||||||
dirty_transactions.order_ids = set()
|
dirty_transactions.order_ids = set()
|
||||||
|
|
||||||
if _check_for_dirty_orders not in [func for savepoint_id, func in conn.run_on_commit]:
|
if _check_for_dirty_orders not in [func for (savepoint_id, func, *__) in conn.run_on_commit]:
|
||||||
transaction.on_commit(_check_for_dirty_orders, using)
|
transaction.on_commit(_check_for_dirty_orders, using)
|
||||||
dirty_transactions.order_ids.clear() # This is necessary to clean up after old threads with rollbacked transactions
|
dirty_transactions.order_ids.clear() # This is necessary to clean up after old threads with rollbacked transactions
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class Discount(LoggedModel):
|
|||||||
)
|
)
|
||||||
condition_apply_to_addons = models.BooleanField(
|
condition_apply_to_addons = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
verbose_name=_("Apply to add-on products"),
|
verbose_name=_("Count add-on products"),
|
||||||
help_text=_("Discounts never apply to bundled products"),
|
help_text=_("Discounts never apply to bundled products"),
|
||||||
)
|
)
|
||||||
condition_ignore_voucher_discounted = models.BooleanField(
|
condition_ignore_voucher_discounted = models.BooleanField(
|
||||||
@@ -107,7 +107,7 @@ class Discount(LoggedModel):
|
|||||||
verbose_name=_("Ignore products discounted by a voucher"),
|
verbose_name=_("Ignore products discounted by a voucher"),
|
||||||
help_text=_("If this option is checked, products that already received a discount through a voucher will not "
|
help_text=_("If this option is checked, products that already received a discount through a voucher will not "
|
||||||
"be considered for this discount. However, products that use a voucher only to e.g. unlock a "
|
"be considered for this discount. However, products that use a voucher only to e.g. unlock a "
|
||||||
"hidden product or gain access to sold-out quota will still receive the discount."),
|
"hidden product or gain access to sold-out quota will still be considered."),
|
||||||
)
|
)
|
||||||
condition_min_count = models.PositiveIntegerField(
|
condition_min_count = models.PositiveIntegerField(
|
||||||
verbose_name=_('Minimum number of matching products'),
|
verbose_name=_('Minimum number of matching products'),
|
||||||
@@ -120,6 +120,19 @@ class Discount(LoggedModel):
|
|||||||
default=Decimal('0.00'),
|
default=Decimal('0.00'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
benefit_same_products = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
verbose_name=_("Apply discount to same set of products"),
|
||||||
|
help_text=_("By default, the discount is applied across the same selection of products than the condition for "
|
||||||
|
"the discount given above. If you want, you can however also select a different selection of "
|
||||||
|
"products.")
|
||||||
|
)
|
||||||
|
benefit_limit_products = models.ManyToManyField(
|
||||||
|
'Item',
|
||||||
|
verbose_name=_("Apply discount to specific products"),
|
||||||
|
related_name='benefit_discounts',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
benefit_discount_matching_percent = models.DecimalField(
|
benefit_discount_matching_percent = models.DecimalField(
|
||||||
verbose_name=_('Percentual discount on matching products'),
|
verbose_name=_('Percentual discount on matching products'),
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
@@ -139,6 +152,18 @@ class Discount(LoggedModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
)
|
)
|
||||||
|
benefit_apply_to_addons = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
verbose_name=_("Apply to add-on products"),
|
||||||
|
help_text=_("Discounts never apply to bundled products"),
|
||||||
|
)
|
||||||
|
benefit_ignore_voucher_discounted = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Ignore products discounted by a voucher"),
|
||||||
|
help_text=_("If this option is checked, products that already received a discount through a voucher will not "
|
||||||
|
"be discounted. However, products that use a voucher only to e.g. unlock a hidden product or gain "
|
||||||
|
"access to sold-out quota will still receive the discount."),
|
||||||
|
)
|
||||||
|
|
||||||
# more feature ideas:
|
# more feature ideas:
|
||||||
# - max_usages_per_order
|
# - max_usages_per_order
|
||||||
@@ -187,6 +212,14 @@ class Discount(LoggedModel):
|
|||||||
'on a minimum value.')
|
'on a minimum value.')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if data.get('subevent_mode') == cls.SUBEVENT_MODE_DISTINCT and not data.get('benefit_same_products'):
|
||||||
|
raise ValidationError(
|
||||||
|
{'benefit_same_products': [
|
||||||
|
_('You cannot apply the discount to a different set of products if the discount is only valid '
|
||||||
|
'for bookings of different dates.')
|
||||||
|
]}
|
||||||
|
)
|
||||||
|
|
||||||
def allow_delete(self):
|
def allow_delete(self):
|
||||||
return not self.orderposition_set.exists()
|
return not self.orderposition_set.exists()
|
||||||
|
|
||||||
@@ -197,6 +230,7 @@ class Discount(LoggedModel):
|
|||||||
'condition_min_value': self.condition_min_value,
|
'condition_min_value': self.condition_min_value,
|
||||||
'benefit_only_apply_to_cheapest_n_matches': self.benefit_only_apply_to_cheapest_n_matches,
|
'benefit_only_apply_to_cheapest_n_matches': self.benefit_only_apply_to_cheapest_n_matches,
|
||||||
'subevent_mode': self.subevent_mode,
|
'subevent_mode': self.subevent_mode,
|
||||||
|
'benefit_same_products': self.benefit_same_products,
|
||||||
})
|
})
|
||||||
|
|
||||||
def is_available_by_time(self, now_dt=None) -> bool:
|
def is_available_by_time(self, now_dt=None) -> bool:
|
||||||
@@ -207,14 +241,14 @@ class Discount(LoggedModel):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _apply_min_value(self, positions, idx_group, result):
|
def _apply_min_value(self, positions, condition_idx_group, benefit_idx_group, result):
|
||||||
if self.condition_min_value and sum(positions[idx][2] for idx in idx_group) < self.condition_min_value:
|
if self.condition_min_value and sum(positions[idx][2] for idx in condition_idx_group) < self.condition_min_value:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.condition_min_count or self.benefit_only_apply_to_cheapest_n_matches:
|
if self.condition_min_count or self.benefit_only_apply_to_cheapest_n_matches:
|
||||||
raise ValueError('Validation invariant violated.')
|
raise ValueError('Validation invariant violated.')
|
||||||
|
|
||||||
for idx in idx_group:
|
for idx in benefit_idx_group:
|
||||||
previous_price = positions[idx][2]
|
previous_price = positions[idx][2]
|
||||||
new_price = round_decimal(
|
new_price = round_decimal(
|
||||||
previous_price * (Decimal('100.00') - self.benefit_discount_matching_percent) / Decimal('100.00'),
|
previous_price * (Decimal('100.00') - self.benefit_discount_matching_percent) / Decimal('100.00'),
|
||||||
@@ -222,8 +256,8 @@ class Discount(LoggedModel):
|
|||||||
)
|
)
|
||||||
result[idx] = new_price
|
result[idx] = new_price
|
||||||
|
|
||||||
def _apply_min_count(self, positions, idx_group, result):
|
def _apply_min_count(self, positions, condition_idx_group, benefit_idx_group, result):
|
||||||
if len(idx_group) < self.condition_min_count:
|
if len(condition_idx_group) < self.condition_min_count:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.condition_min_count or self.condition_min_value:
|
if not self.condition_min_count or self.condition_min_value:
|
||||||
@@ -233,15 +267,17 @@ class Discount(LoggedModel):
|
|||||||
if not self.condition_min_count:
|
if not self.condition_min_count:
|
||||||
raise ValueError('Validation invariant violated.')
|
raise ValueError('Validation invariant violated.')
|
||||||
|
|
||||||
idx_group = sorted(idx_group, key=lambda idx: (positions[idx][2], -idx)) # sort by line_price
|
condition_idx_group = sorted(condition_idx_group, key=lambda idx: (positions[idx][2], -idx)) # sort by line_price
|
||||||
|
benefit_idx_group = sorted(benefit_idx_group, key=lambda idx: (positions[idx][2], -idx)) # sort by line_price
|
||||||
|
|
||||||
# Prevent over-consuming of items, i.e. if our discount is "buy 2, get 1 free", we only
|
# Prevent over-consuming of items, i.e. if our discount is "buy 2, get 1 free", we only
|
||||||
# want to match multiples of 3
|
# want to match multiples of 3
|
||||||
consume_idx = idx_group[:len(idx_group) // self.condition_min_count * self.condition_min_count]
|
n_groups = min(len(condition_idx_group) // self.condition_min_count, len(benefit_idx_group))
|
||||||
benefit_idx = idx_group[:len(idx_group) // self.condition_min_count * self.benefit_only_apply_to_cheapest_n_matches]
|
consume_idx = condition_idx_group[:n_groups * self.condition_min_count]
|
||||||
|
benefit_idx = benefit_idx_group[:n_groups * self.benefit_only_apply_to_cheapest_n_matches]
|
||||||
else:
|
else:
|
||||||
consume_idx = idx_group
|
consume_idx = condition_idx_group
|
||||||
benefit_idx = idx_group
|
benefit_idx = benefit_idx_group
|
||||||
|
|
||||||
for idx in benefit_idx:
|
for idx in benefit_idx:
|
||||||
previous_price = positions[idx][2]
|
previous_price = positions[idx][2]
|
||||||
@@ -276,7 +312,7 @@ class Discount(LoggedModel):
|
|||||||
limit_products = {p.pk for p in self.condition_limit_products.all()}
|
limit_products = {p.pk for p in self.condition_limit_products.all()}
|
||||||
|
|
||||||
# First, filter out everything not even covered by our product scope
|
# First, filter out everything not even covered by our product scope
|
||||||
initial_candidates = [
|
condition_candidates = [
|
||||||
idx
|
idx
|
||||||
for idx, (item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount) in positions.items()
|
for idx, (item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount) in positions.items()
|
||||||
if (
|
if (
|
||||||
@@ -286,11 +322,25 @@ class Discount(LoggedModel):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if self.benefit_same_products:
|
||||||
|
benefit_candidates = list(condition_candidates)
|
||||||
|
else:
|
||||||
|
benefit_products = {p.pk for p in self.benefit_limit_products.all()}
|
||||||
|
benefit_candidates = [
|
||||||
|
idx
|
||||||
|
for idx, (item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount) in positions.items()
|
||||||
|
if (
|
||||||
|
item_id in benefit_products and
|
||||||
|
(self.benefit_apply_to_addons or not is_addon_to) and
|
||||||
|
(not self.benefit_ignore_voucher_discounted or voucher_discount is None or voucher_discount == Decimal('0.00'))
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
if self.subevent_mode == self.SUBEVENT_MODE_MIXED: # also applies to non-series events
|
if self.subevent_mode == self.SUBEVENT_MODE_MIXED: # also applies to non-series events
|
||||||
if self.condition_min_count:
|
if self.condition_min_count:
|
||||||
self._apply_min_count(positions, initial_candidates, result)
|
self._apply_min_count(positions, condition_candidates, benefit_candidates, result)
|
||||||
else:
|
else:
|
||||||
self._apply_min_value(positions, initial_candidates, result)
|
self._apply_min_value(positions, condition_candidates, benefit_candidates, result)
|
||||||
|
|
||||||
elif self.subevent_mode == self.SUBEVENT_MODE_SAME:
|
elif self.subevent_mode == self.SUBEVENT_MODE_SAME:
|
||||||
def key(idx):
|
def key(idx):
|
||||||
@@ -299,17 +349,18 @@ class Discount(LoggedModel):
|
|||||||
# Build groups of candidates with the same subevent, then apply our regular algorithm
|
# Build groups of candidates with the same subevent, then apply our regular algorithm
|
||||||
# to each group
|
# to each group
|
||||||
|
|
||||||
_groups = groupby(sorted(initial_candidates, key=key), key=key)
|
_groups = groupby(sorted(condition_candidates, key=key), key=key)
|
||||||
candidate_groups = [list(g) for k, g in _groups]
|
candidate_groups = [(k, list(g)) for k, g in _groups]
|
||||||
|
|
||||||
for g in candidate_groups:
|
for subevent_id, g in candidate_groups:
|
||||||
|
benefit_g = [idx for idx in benefit_candidates if positions[idx][1] == subevent_id]
|
||||||
if self.condition_min_count:
|
if self.condition_min_count:
|
||||||
self._apply_min_count(positions, g, result)
|
self._apply_min_count(positions, g, benefit_g, result)
|
||||||
else:
|
else:
|
||||||
self._apply_min_value(positions, g, result)
|
self._apply_min_value(positions, g, benefit_g, result)
|
||||||
|
|
||||||
elif self.subevent_mode == self.SUBEVENT_MODE_DISTINCT:
|
elif self.subevent_mode == self.SUBEVENT_MODE_DISTINCT:
|
||||||
if self.condition_min_value:
|
if self.condition_min_value or not self.benefit_same_products:
|
||||||
raise ValueError('Validation invariant violated.')
|
raise ValueError('Validation invariant violated.')
|
||||||
|
|
||||||
# Build optimal groups of candidates with distinct subevents, then apply our regular algorithm
|
# Build optimal groups of candidates with distinct subevents, then apply our regular algorithm
|
||||||
@@ -336,7 +387,7 @@ class Discount(LoggedModel):
|
|||||||
candidates = []
|
candidates = []
|
||||||
cardinality = None
|
cardinality = None
|
||||||
for se, l in subevent_to_idx.items():
|
for se, l in subevent_to_idx.items():
|
||||||
l = [ll for ll in l if ll in initial_candidates and ll not in current_group]
|
l = [ll for ll in l if ll in condition_candidates and ll not in current_group]
|
||||||
if cardinality and len(l) != cardinality:
|
if cardinality and len(l) != cardinality:
|
||||||
continue
|
continue
|
||||||
if se not in {positions[idx][1] for idx in current_group}:
|
if se not in {positions[idx][1] for idx in current_group}:
|
||||||
@@ -373,5 +424,5 @@ class Discount(LoggedModel):
|
|||||||
break
|
break
|
||||||
|
|
||||||
for g in candidate_groups:
|
for g in candidate_groups:
|
||||||
self._apply_min_count(positions, g, result)
|
self._apply_min_count(positions, g, g, result)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -907,14 +907,18 @@ class Event(EventMixin, LoggedModel):
|
|||||||
self.items.filter(hidden_if_available_id=oldid).update(hidden_if_available=q)
|
self.items.filter(hidden_if_available_id=oldid).update(hidden_if_available=q)
|
||||||
|
|
||||||
for d in Discount.objects.filter(event=other).prefetch_related('condition_limit_products'):
|
for d in Discount.objects.filter(event=other).prefetch_related('condition_limit_products'):
|
||||||
items = list(d.condition_limit_products.all())
|
c_items = list(d.condition_limit_products.all())
|
||||||
|
b_items = list(d.benefit_limit_products.all())
|
||||||
d.pk = None
|
d.pk = None
|
||||||
d.event = self
|
d.event = self
|
||||||
d.save(force_insert=True)
|
d.save(force_insert=True)
|
||||||
d.log_action('pretix.object.cloned')
|
d.log_action('pretix.object.cloned')
|
||||||
for i in items:
|
for i in c_items:
|
||||||
if i.pk in item_map:
|
if i.pk in item_map:
|
||||||
d.condition_limit_products.add(item_map[i.pk])
|
d.condition_limit_products.add(item_map[i.pk])
|
||||||
|
for i in b_items:
|
||||||
|
if i.pk in item_map:
|
||||||
|
d.benefit_limit_products.add(item_map[i.pk])
|
||||||
|
|
||||||
question_map = {}
|
question_map = {}
|
||||||
for q in Question.objects.filter(event=other).prefetch_related('items', 'options'):
|
for q in Question.objects.filter(event=other).prefetch_related('items', 'options'):
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from typing import Optional, Tuple
|
|||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
|
import django_redis
|
||||||
from dateutil.tz import datetime_exists
|
from dateutil.tz import datetime_exists
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -57,7 +58,6 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.timezone import is_naive, make_aware, now
|
from django.utils.timezone import is_naive, make_aware, now
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||||
from django_countries.fields import Country
|
from django_countries.fields import Country
|
||||||
from django_redis import get_redis_connection
|
|
||||||
from django_scopes import ScopedManager
|
from django_scopes import ScopedManager
|
||||||
from i18nfield.fields import I18nCharField, I18nTextField
|
from i18nfield.fields import I18nCharField, I18nTextField
|
||||||
|
|
||||||
@@ -1910,8 +1910,13 @@ class Quota(LoggedModel):
|
|||||||
|
|
||||||
def rebuild_cache(self, now_dt=None):
|
def rebuild_cache(self, now_dt=None):
|
||||||
if settings.HAS_REDIS:
|
if settings.HAS_REDIS:
|
||||||
rc = get_redis_connection("redis")
|
rc = django_redis.get_redis_connection("redis")
|
||||||
rc.hdel(f'quotas:{self.event_id}:availabilitycache', str(self.pk))
|
p = rc.pipeline()
|
||||||
|
p.hdel(f'quotas:{self.event_id}:availabilitycache', str(self.pk))
|
||||||
|
p.hdel(f'quotas:{self.event_id}:availabilitycache:nocw', str(self.pk))
|
||||||
|
p.hdel(f'quotas:{self.event_id}:availabilitycache:igcl', str(self.pk))
|
||||||
|
p.hdel(f'quotas:{self.event_id}:availabilitycache:nocw:igcl', str(self.pk))
|
||||||
|
p.execute()
|
||||||
self.availability(now_dt=now_dt)
|
self.availability(now_dt=now_dt)
|
||||||
|
|
||||||
def availability(
|
def availability(
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ class LogEntry(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('-datetime', '-id')
|
ordering = ('-datetime', '-id')
|
||||||
index_together = [
|
indexes = [models.Index(fields=["datetime", "id"])]
|
||||||
['datetime', 'id']
|
|
||||||
]
|
|
||||||
|
|
||||||
def display(self):
|
def display(self):
|
||||||
from ..signals import logentry_display
|
from ..signals import logentry_display
|
||||||
|
|||||||
@@ -121,7 +121,10 @@ class ReusableMedium(LoggedModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (("identifier", "type", "organizer"),)
|
unique_together = (("identifier", "type", "organizer"),)
|
||||||
index_together = (("identifier", "type", "organizer"), ("updated", "id"))
|
indexes = [
|
||||||
|
models.Index(fields=("identifier", "type", "organizer")),
|
||||||
|
models.Index(fields=("updated", "id")),
|
||||||
|
]
|
||||||
ordering = "identifier", "type", "organizer"
|
ordering = "identifier", "type", "organizer"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -270,9 +270,9 @@ class Order(LockModel, LoggedModel):
|
|||||||
verbose_name = _("Order")
|
verbose_name = _("Order")
|
||||||
verbose_name_plural = _("Orders")
|
verbose_name_plural = _("Orders")
|
||||||
ordering = ("-datetime", "-pk")
|
ordering = ("-datetime", "-pk")
|
||||||
index_together = [
|
indexes = [
|
||||||
["datetime", "id"],
|
models.Index(fields=["datetime", "id"]),
|
||||||
["last_modified", "id"],
|
models.Index(fields=["last_modified", "id"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -1676,7 +1676,7 @@ class OrderPayment(models.Model):
|
|||||||
"""
|
"""
|
||||||
Marks the order as failed and sets info to ``info``, but only if the order is in ``created`` or ``pending``
|
Marks the order as failed and sets info to ``info``, but only if the order is in ``created`` or ``pending``
|
||||||
state. This is equivalent to setting ``state`` to ``OrderPayment.PAYMENT_STATE_FAILED`` and logging a failure,
|
state. This is equivalent to setting ``state`` to ``OrderPayment.PAYMENT_STATE_FAILED`` and logging a failure,
|
||||||
but it adds strong database logging since we do not want to report a failure for an order that has just
|
but it adds strong database locking since we do not want to report a failure for an order that has just
|
||||||
been marked as paid.
|
been marked as paid.
|
||||||
:param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
|
:param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
|
||||||
"""
|
"""
|
||||||
@@ -2756,8 +2756,8 @@ class Transaction(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = 'datetime', 'pk'
|
ordering = 'datetime', 'pk'
|
||||||
index_together = [
|
indexes = [
|
||||||
['datetime', 'id']
|
models.Index(fields=['datetime', 'id'])
|
||||||
]
|
]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -340,10 +340,17 @@ class TaxRule(LoggedModel):
|
|||||||
rules = self._custom_rules
|
rules = self._custom_rules
|
||||||
if invoice_address:
|
if invoice_address:
|
||||||
for r in rules:
|
for r in rules:
|
||||||
if r['country'] == 'EU' and not is_eu_country(invoice_address.country):
|
if r['country'] == 'ZZ': # Rule: Any country
|
||||||
continue
|
pass
|
||||||
if r['country'] not in ('ZZ', 'EU') and r['country'] != str(invoice_address.country):
|
elif r['country'] == 'EU': # Rule: Any EU country
|
||||||
continue
|
if not is_eu_country(invoice_address.country):
|
||||||
|
continue
|
||||||
|
elif '-' in r['country']: # Rule: Specific country and state
|
||||||
|
if r['country'] != str(invoice_address.country) + '-' + str(invoice_address.state):
|
||||||
|
continue
|
||||||
|
else: # Rule: Specific country
|
||||||
|
if r['country'] != str(invoice_address.country):
|
||||||
|
continue
|
||||||
if r['address_type'] == 'individual' and invoice_address.is_business:
|
if r['address_type'] == 'individual' and invoice_address.is_business:
|
||||||
continue
|
continue
|
||||||
if r['address_type'] in ('business', 'business_vat_id') and not invoice_address.is_business:
|
if r['address_type'] in ('business', 'business_vat_id') and not invoice_address.is_business:
|
||||||
|
|||||||
@@ -805,7 +805,7 @@ class QuestionColumn(ImportColumn):
|
|||||||
return self.q.clean_answer(value)
|
return self.q.clean_answer(value)
|
||||||
|
|
||||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||||
if value:
|
if value is not None:
|
||||||
if not hasattr(order, '_answers'):
|
if not hasattr(order, '_answers'):
|
||||||
order._answers = []
|
order._answers = []
|
||||||
if isinstance(value, QuestionOption):
|
if isinstance(value, QuestionOption):
|
||||||
|
|||||||
@@ -108,7 +108,10 @@ DEFAULT_VARIABLES = OrderedDict((
|
|||||||
("positionid", {
|
("positionid", {
|
||||||
"label": _("Order position number"),
|
"label": _("Order position number"),
|
||||||
"editor_sample": "1",
|
"editor_sample": "1",
|
||||||
"evaluate": lambda orderposition, order, event: str(orderposition.positionid)
|
"evaluate": lambda orderposition, order, event: str(orderposition.positionid),
|
||||||
|
# There is no performance gain in using evaluate_bulk here, but we want to make sure it is used somewhere
|
||||||
|
# in core to make sure we notice if the implementation of the API breaks.
|
||||||
|
"evaluate_bulk": lambda orderpositions: [str(p.positionid) for p in orderpositions],
|
||||||
}),
|
}),
|
||||||
("order_positionid", {
|
("order_positionid", {
|
||||||
"label": _("Order code and position number"),
|
"label": _("Order code and position number"),
|
||||||
@@ -699,10 +702,10 @@ def get_seat(op: OrderPosition):
|
|||||||
|
|
||||||
def generate_compressed_addon_list(op, order, event):
|
def generate_compressed_addon_list(op, order, event):
|
||||||
itemcount = defaultdict(int)
|
itemcount = defaultdict(int)
|
||||||
addons = (
|
addons = [p for p in (
|
||||||
op.addons.all() if 'addons' in getattr(op, '_prefetched_objects_cache', {})
|
op.addons.all() if 'addons' in getattr(op, '_prefetched_objects_cache', {})
|
||||||
else op.addons.select_related('item', 'variation')
|
else op.addons.select_related('item', 'variation')
|
||||||
)
|
) if not p.canceled]
|
||||||
for pos in addons:
|
for pos in addons:
|
||||||
itemcount[pos.item, pos.variation] += 1
|
itemcount[pos.item, pos.variation] += 1
|
||||||
|
|
||||||
|
|||||||
@@ -1078,6 +1078,7 @@ class CartManager:
|
|||||||
quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt)
|
quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt)
|
||||||
err = None
|
err = None
|
||||||
new_cart_positions = []
|
new_cart_positions = []
|
||||||
|
deleted_positions = set()
|
||||||
|
|
||||||
err = err or self._check_min_max_per_product()
|
err = err or self._check_min_max_per_product()
|
||||||
|
|
||||||
@@ -1089,7 +1090,10 @@ class CartManager:
|
|||||||
if op.position.expires > self.now_dt:
|
if op.position.expires > self.now_dt:
|
||||||
for q in op.position.quotas:
|
for q in op.position.quotas:
|
||||||
quotas_ok[q] += 1
|
quotas_ok[q] += 1
|
||||||
op.position.addons.all().delete()
|
addons = op.position.addons.all()
|
||||||
|
deleted_positions |= {a.pk for a in addons}
|
||||||
|
addons.delete()
|
||||||
|
deleted_positions.add(op.position.pk)
|
||||||
op.position.delete()
|
op.position.delete()
|
||||||
|
|
||||||
elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
|
||||||
@@ -1239,20 +1243,28 @@ class CartManager:
|
|||||||
if op.seat and not op.seat.is_available(ignore_cart=op.position, sales_channel=self._sales_channel,
|
if op.seat and not op.seat.is_available(ignore_cart=op.position, sales_channel=self._sales_channel,
|
||||||
ignore_voucher_id=op.position.voucher_id):
|
ignore_voucher_id=op.position.voucher_id):
|
||||||
err = err or error_messages['seat_unavailable']
|
err = err or error_messages['seat_unavailable']
|
||||||
op.position.addons.all().delete()
|
|
||||||
|
addons = op.position.addons.all()
|
||||||
|
deleted_positions |= {a.pk for a in addons}
|
||||||
|
deleted_positions.add(op.position.pk)
|
||||||
|
addons.delete()
|
||||||
op.position.delete()
|
op.position.delete()
|
||||||
elif available_count == 1:
|
elif available_count == 1:
|
||||||
op.position.expires = self._expiry
|
op.position.expires = self._expiry
|
||||||
op.position.listed_price = op.listed_price
|
op.position.listed_price = op.listed_price
|
||||||
op.position.price_after_voucher = op.price_after_voucher
|
op.position.price_after_voucher = op.price_after_voucher
|
||||||
# op.position.price will be updated by recompute_final_prices_and_taxes()
|
# op.position.price will be updated by recompute_final_prices_and_taxes()
|
||||||
try:
|
if op.position.pk not in deleted_positions:
|
||||||
op.position.save(force_update=True, update_fields=['expires', 'listed_price', 'price_after_voucher'])
|
try:
|
||||||
except DatabaseError:
|
op.position.save(force_update=True, update_fields=['expires', 'listed_price', 'price_after_voucher'])
|
||||||
# Best effort... The position might have been deleted in the meantime!
|
except DatabaseError:
|
||||||
pass
|
# Best effort... The position might have been deleted in the meantime!
|
||||||
|
pass
|
||||||
elif available_count == 0:
|
elif available_count == 0:
|
||||||
op.position.addons.all().delete()
|
addons = op.position.addons.all()
|
||||||
|
deleted_positions |= {a.pk for a in addons}
|
||||||
|
deleted_positions.add(op.position.pk)
|
||||||
|
addons.delete()
|
||||||
op.position.delete()
|
op.position.delete()
|
||||||
else:
|
else:
|
||||||
raise AssertionError("ExtendOperation cannot affect more than one item")
|
raise AssertionError("ExtendOperation cannot affect more than one item")
|
||||||
|
|||||||
@@ -886,7 +886,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
|||||||
if isinstance(auth, Device):
|
if isinstance(auth, Device):
|
||||||
device = auth
|
device = auth
|
||||||
|
|
||||||
last_cis = list(op.checkins.order_by('-datetime').filter(list=clist).only('type', 'nonce'))
|
last_cis = list(op.checkins.order_by('-datetime').filter(list=clist).only('type', 'nonce', 'position_id'))
|
||||||
entry_allowed = (
|
entry_allowed = (
|
||||||
type == Checkin.TYPE_EXIT or
|
type == Checkin.TYPE_EXIT or
|
||||||
clist.allow_multiple_entries or
|
clist.allow_multiple_entries or
|
||||||
|
|||||||
@@ -2476,6 +2476,11 @@ class OrderChangeManager:
|
|||||||
split_order.status = Order.STATUS_PAID
|
split_order.status = Order.STATUS_PAID
|
||||||
else:
|
else:
|
||||||
split_order.status = Order.STATUS_PENDING
|
split_order.status = Order.STATUS_PENDING
|
||||||
|
if self.order.status == Order.STATUS_PAID:
|
||||||
|
split_order.set_expires(
|
||||||
|
now(),
|
||||||
|
list(set(p.subevent_id for p in split_positions))
|
||||||
|
)
|
||||||
split_order.save()
|
split_order.save()
|
||||||
|
|
||||||
if offset_amount > Decimal('0.00'):
|
if offset_amount > Decimal('0.00'):
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ def apply_discounts(event: Event, sales_channel: str,
|
|||||||
Q(available_until__isnull=True) | Q(available_until__gte=now()),
|
Q(available_until__isnull=True) | Q(available_until__gte=now()),
|
||||||
sales_channels__contains=sales_channel,
|
sales_channels__contains=sales_channel,
|
||||||
active=True,
|
active=True,
|
||||||
).prefetch_related('condition_limit_products').order_by('position', 'pk')
|
).prefetch_related('condition_limit_products', 'benefit_limit_products').order_by('position', 'pk')
|
||||||
for discount in discount_qs:
|
for discount in discount_qs:
|
||||||
result = discount.apply({
|
result = discount.apply({
|
||||||
idx: (item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount)
|
idx: (item_id, subevent_id, line_price_gross, is_addon_to, voucher_discount)
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ import time
|
|||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
|
|
||||||
|
import django_redis
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Case, Count, F, Func, Max, OuterRef, Q, Subquery, Sum, Value, When,
|
Case, Count, F, Func, Max, OuterRef, Q, Subquery, Sum, Value, When,
|
||||||
)
|
)
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_redis import get_redis_connection
|
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Checkin, Order, OrderPosition, Quota, Voucher,
|
CartPosition, Checkin, Order, OrderPosition, Quota, Voucher,
|
||||||
@@ -102,6 +102,12 @@ class QuotaAvailability:
|
|||||||
self.count_waitinglist = defaultdict(int)
|
self.count_waitinglist = defaultdict(int)
|
||||||
self.count_cart = defaultdict(int)
|
self.count_cart = defaultdict(int)
|
||||||
|
|
||||||
|
self._cache_key_suffix = ""
|
||||||
|
if not self._count_waitinglist:
|
||||||
|
self._cache_key_suffix += ":nocw"
|
||||||
|
if self._ignore_closed:
|
||||||
|
self._cache_key_suffix += ":igcl"
|
||||||
|
|
||||||
self.sizes = {}
|
self.sizes = {}
|
||||||
|
|
||||||
def queue(self, *quota):
|
def queue(self, *quota):
|
||||||
@@ -121,17 +127,14 @@ class QuotaAvailability:
|
|||||||
if self._full_results:
|
if self._full_results:
|
||||||
raise ValueError("You cannot combine full_results and allow_cache.")
|
raise ValueError("You cannot combine full_results and allow_cache.")
|
||||||
|
|
||||||
elif not self._count_waitinglist:
|
|
||||||
raise ValueError("If you set allow_cache, you need to set count_waitinglist.")
|
|
||||||
|
|
||||||
elif settings.HAS_REDIS:
|
elif settings.HAS_REDIS:
|
||||||
rc = get_redis_connection("redis")
|
rc = django_redis.get_redis_connection("redis")
|
||||||
quotas_by_event = defaultdict(list)
|
quotas_by_event = defaultdict(list)
|
||||||
for q in [_q for _q in self._queue if _q.id in quota_ids_set]:
|
for q in [_q for _q in self._queue if _q.id in quota_ids_set]:
|
||||||
quotas_by_event[q.event_id].append(q)
|
quotas_by_event[q.event_id].append(q)
|
||||||
|
|
||||||
for eventid, evquotas in quotas_by_event.items():
|
for eventid, evquotas in quotas_by_event.items():
|
||||||
d = rc.hmget(f'quotas:{eventid}:availabilitycache', [str(q.pk) for q in evquotas])
|
d = rc.hmget(f'quotas:{eventid}:availabilitycache{self._cache_key_suffix}', [str(q.pk) for q in evquotas])
|
||||||
for redisval, q in zip(d, evquotas):
|
for redisval, q in zip(d, evquotas):
|
||||||
if redisval is not None:
|
if redisval is not None:
|
||||||
data = [rv for rv in redisval.decode().split(',')]
|
data = [rv for rv in redisval.decode().split(',')]
|
||||||
@@ -164,12 +167,12 @@ class QuotaAvailability:
|
|||||||
if not settings.HAS_REDIS or not quotas:
|
if not settings.HAS_REDIS or not quotas:
|
||||||
return
|
return
|
||||||
|
|
||||||
rc = get_redis_connection("redis")
|
rc = django_redis.get_redis_connection("redis")
|
||||||
# We write the computed availability to redis in a per-event hash as
|
# We write the computed availability to redis in a per-event hash as
|
||||||
#
|
#
|
||||||
# quota_id -> (availability_state, availability_number, timestamp).
|
# quota_id -> (availability_state, availability_number, timestamp).
|
||||||
#
|
#
|
||||||
# We store this in a hash instead of inidividual values to avoid making two many redis requests
|
# We store this in a hash instead of individual values to avoid making too many redis requests
|
||||||
# which would introduce latency.
|
# which would introduce latency.
|
||||||
|
|
||||||
# The individual entries in the hash are "valid" for 120 seconds. This means in a typical peak scenario with
|
# The individual entries in the hash are "valid" for 120 seconds. This means in a typical peak scenario with
|
||||||
@@ -179,16 +182,16 @@ class QuotaAvailability:
|
|||||||
# these quotas. We choose 10 seconds since that should be well above the duration of a write.
|
# these quotas. We choose 10 seconds since that should be well above the duration of a write.
|
||||||
|
|
||||||
lock_name = '_'.join([str(p) for p in sorted([q.pk for q in quotas])])
|
lock_name = '_'.join([str(p) for p in sorted([q.pk for q in quotas])])
|
||||||
if rc.exists(f'quotas:availabilitycachewrite:{lock_name}'):
|
if rc.exists(f'quotas:availabilitycachewrite:{lock_name}{self._cache_key_suffix}'):
|
||||||
return
|
return
|
||||||
rc.setex(f'quotas:availabilitycachewrite:{lock_name}', '1', 10)
|
rc.setex(f'quotas:availabilitycachewrite:{lock_name}{self._cache_key_suffix}', '1', 10)
|
||||||
|
|
||||||
update = defaultdict(list)
|
update = defaultdict(list)
|
||||||
for q in quotas:
|
for q in quotas:
|
||||||
update[q.event_id].append(q)
|
update[q.event_id].append(q)
|
||||||
|
|
||||||
for eventid, quotas in update.items():
|
for eventid, quotas in update.items():
|
||||||
rc.hmset(f'quotas:{eventid}:availabilitycache', {
|
rc.hmset(f'quotas:{eventid}:availabilitycache{self._cache_key_suffix}', {
|
||||||
str(q.id): ",".join(
|
str(q.id): ",".join(
|
||||||
[str(i) for i in self.results[q]] +
|
[str(i) for i in self.results[q]] +
|
||||||
[str(int(time.time()))]
|
[str(int(time.time()))]
|
||||||
@@ -197,7 +200,7 @@ class QuotaAvailability:
|
|||||||
# To make sure old events do not fill up our redis instance, we set an expiry on the cache. However, we set it
|
# To make sure old events do not fill up our redis instance, we set an expiry on the cache. However, we set it
|
||||||
# on 7 days even though we mostly ignore values older than 2 monites. The reasoning is that we have some places
|
# on 7 days even though we mostly ignore values older than 2 monites. The reasoning is that we have some places
|
||||||
# where we set allow_cache_stale and use the old entries anyways to save on performance.
|
# where we set allow_cache_stale and use the old entries anyways to save on performance.
|
||||||
rc.expire(f'quotas:{eventid}:availabilitycache', 3600 * 24 * 7)
|
rc.expire(f'quotas:{eventid}:availabilitycache{self._cache_key_suffix}', 3600 * 24 * 7)
|
||||||
|
|
||||||
# We used to also delete item_quota_cache:* from the event cache here, but as the cache
|
# We used to also delete item_quota_cache:* from the event cache here, but as the cache
|
||||||
# gets more complex, this does not seem worth it. The cache is only present for up to
|
# gets more complex, this does not seem worth it. The cache is only present for up to
|
||||||
|
|||||||
@@ -22,13 +22,15 @@
|
|||||||
import sys
|
import sys
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.db.models import Exists, F, OuterRef, Q, Sum
|
from django.db.models import (
|
||||||
|
Exists, F, OuterRef, Prefetch, Q, Sum, prefetch_related_objects,
|
||||||
|
)
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, SeatCategoryMapping, User, WaitingListEntry,
|
Event, EventMetaValue, SeatCategoryMapping, User, WaitingListEntry,
|
||||||
)
|
)
|
||||||
from pretix.base.models.waitinglist import WaitingListException
|
from pretix.base.models.waitinglist import WaitingListException
|
||||||
from pretix.base.services.tasks import EventTask
|
from pretix.base.services.tasks import EventTask
|
||||||
@@ -59,8 +61,21 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
|
|||||||
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
|
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
|
||||||
seats_available[(m.product_id, m.subevent_id)] = num_free_seets_for_product - num_valid_vouchers_for_product
|
seats_available[(m.product_id, m.subevent_id)] = num_free_seets_for_product - num_valid_vouchers_for_product
|
||||||
|
|
||||||
qs = WaitingListEntry.objects.filter(
|
prefetch_related_objects(
|
||||||
event=event, voucher__isnull=True
|
[event.organizer],
|
||||||
|
'meta_properties'
|
||||||
|
)
|
||||||
|
prefetch_related_objects(
|
||||||
|
[event],
|
||||||
|
Prefetch(
|
||||||
|
'meta_values',
|
||||||
|
EventMetaValue.objects.select_related('property'),
|
||||||
|
to_attr='meta_values_cached'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
qs = event.waitinglistentries.filter(
|
||||||
|
voucher__isnull=True
|
||||||
).select_related('item', 'variation', 'subevent').prefetch_related(
|
).select_related('item', 'variation', 'subevent').prefetch_related(
|
||||||
'item__quotas', 'variation__quotas'
|
'item__quotas', 'variation__quotas'
|
||||||
).order_by('-priority', 'created')
|
).order_by('-priority', 'created')
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ def slow_delete(qs, batch_size=1000, sleep_time=.5, progress_callback=None, prog
|
|||||||
break
|
break
|
||||||
if total_deleted >= 0.8 * batch_size:
|
if total_deleted >= 0.8 * batch_size:
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
if progress_callback and progress_total:
|
||||||
|
progress_callback((progress_offset + total_deleted) / progress_total)
|
||||||
return total_deleted
|
return total_deleted
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -683,12 +683,16 @@ dictionaries as values that contain keys like in the following example::
|
|||||||
"product": {
|
"product": {
|
||||||
"label": _("Product name"),
|
"label": _("Product name"),
|
||||||
"editor_sample": _("Sample product"),
|
"editor_sample": _("Sample product"),
|
||||||
"evaluate": lambda orderposition, order, event: str(orderposition.item)
|
"evaluate": lambda orderposition, order, event: str(orderposition.item),
|
||||||
|
"evaluate_bulk": lambda orderpositions: [str(op.item) for op in orderpositions],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
The ``evaluate`` member will be called with the order position, order and event as arguments. The event might
|
The ``evaluate`` member will be called with the order position, order and event as arguments. The event might
|
||||||
also be a subevent, if applicable.
|
also be a subevent, if applicable.
|
||||||
|
|
||||||
|
The ``evaluate_bulk`` member is optional but can significantly improve performance in some situations because you
|
||||||
|
can perform database fetches in bulk instead of single queries for every position.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ class LinkifyAndCleanExtension(Extension):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def markdown_compile_email(source):
|
def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes=ALLOWED_ATTRIBUTES):
|
||||||
linker = bleach.Linker(
|
linker = bleach.Linker(
|
||||||
url_re=URL_RE,
|
url_re=URL_RE,
|
||||||
email_re=EMAIL_RE,
|
email_re=EMAIL_RE,
|
||||||
@@ -306,8 +306,8 @@ def markdown_compile_email(source):
|
|||||||
EmailNl2BrExtension(),
|
EmailNl2BrExtension(),
|
||||||
LinkifyAndCleanExtension(
|
LinkifyAndCleanExtension(
|
||||||
linker,
|
linker,
|
||||||
tags=ALLOWED_TAGS,
|
tags=allowed_tags,
|
||||||
attributes=ALLOWED_ATTRIBUTES,
|
attributes=allowed_attributes,
|
||||||
protocols=ALLOWED_PROTOCOLS,
|
protocols=ALLOWED_PROTOCOLS,
|
||||||
strip=False,
|
strip=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -50,11 +50,16 @@ class DiscountForm(I18nModelForm):
|
|||||||
'condition_ignore_voucher_discounted',
|
'condition_ignore_voucher_discounted',
|
||||||
'benefit_discount_matching_percent',
|
'benefit_discount_matching_percent',
|
||||||
'benefit_only_apply_to_cheapest_n_matches',
|
'benefit_only_apply_to_cheapest_n_matches',
|
||||||
|
'benefit_same_products',
|
||||||
|
'benefit_limit_products',
|
||||||
|
'benefit_apply_to_addons',
|
||||||
|
'benefit_ignore_voucher_discounted',
|
||||||
]
|
]
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'available_from': SplitDateTimeField,
|
'available_from': SplitDateTimeField,
|
||||||
'available_until': SplitDateTimeField,
|
'available_until': SplitDateTimeField,
|
||||||
'condition_limit_products': ItemMultipleChoiceField,
|
'condition_limit_products': ItemMultipleChoiceField,
|
||||||
|
'benefit_limit_products': ItemMultipleChoiceField,
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
'subevent_mode': forms.RadioSelect,
|
'subevent_mode': forms.RadioSelect,
|
||||||
@@ -64,11 +69,14 @@ class DiscountForm(I18nModelForm):
|
|||||||
'data-inverse-dependency': '<[name$=all_products]',
|
'data-inverse-dependency': '<[name$=all_products]',
|
||||||
'class': 'scrolling-multiple-choice',
|
'class': 'scrolling-multiple-choice',
|
||||||
}),
|
}),
|
||||||
|
'benefit_limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||||
|
'class': 'scrolling-multiple-choice',
|
||||||
|
}),
|
||||||
'benefit_only_apply_to_cheapest_n_matches': forms.NumberInput(
|
'benefit_only_apply_to_cheapest_n_matches': forms.NumberInput(
|
||||||
attrs={
|
attrs={
|
||||||
'data-display-dependency': '#id_condition_min_count',
|
'data-display-dependency': '#id_condition_min_count',
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -85,6 +93,7 @@ class DiscountForm(I18nModelForm):
|
|||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
)
|
)
|
||||||
self.fields['condition_limit_products'].queryset = self.event.items.all()
|
self.fields['condition_limit_products'].queryset = self.event.items.all()
|
||||||
|
self.fields['benefit_limit_products'].queryset = self.event.items.all()
|
||||||
self.fields['condition_min_count'].required = False
|
self.fields['condition_min_count'].required = False
|
||||||
self.fields['condition_min_count'].widget.is_required = False
|
self.fields['condition_min_count'].widget.is_required = False
|
||||||
self.fields['condition_min_value'].required = False
|
self.fields['condition_min_value'].required = False
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from decimal import Decimal
|
|||||||
from urllib.parse import urlencode, urlparse
|
from urllib.parse import urlencode, urlparse
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
import pycountry
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||||
@@ -65,7 +66,8 @@ from pretix.base.models import Event, Organizer, TaxRule, Team
|
|||||||
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
||||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||||
from pretix.base.settings import (
|
from pretix.base.settings import (
|
||||||
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_event_settings,
|
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SCHEMES,
|
||||||
|
PERSON_NAME_TITLE_GROUPS, validate_event_settings,
|
||||||
)
|
)
|
||||||
from pretix.base.validators import multimail_validate
|
from pretix.base.validators import multimail_validate
|
||||||
from pretix.control.forms import (
|
from pretix.control.forms import (
|
||||||
@@ -1428,9 +1430,20 @@ class CountriesAndEU(CachedCountries):
|
|||||||
cache_subkey = 'with_any_or_eu'
|
cache_subkey = 'with_any_or_eu'
|
||||||
|
|
||||||
|
|
||||||
|
class CountriesAndEUAndStates(CountriesAndEU):
|
||||||
|
def __iter__(self):
|
||||||
|
for country_code, country_name in super().__iter__():
|
||||||
|
yield country_code, country_name
|
||||||
|
if country_code in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||||
|
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[country_code]
|
||||||
|
yield from sorted(((state.code, country_name + " - " + state.name)
|
||||||
|
for state in pycountry.subdivisions.get(country_code=country_code)
|
||||||
|
if state.type in types), key=lambda s: s[1])
|
||||||
|
|
||||||
|
|
||||||
class TaxRuleLineForm(I18nForm):
|
class TaxRuleLineForm(I18nForm):
|
||||||
country = LazyTypedChoiceField(
|
country = LazyTypedChoiceField(
|
||||||
choices=CountriesAndEU(),
|
choices=CountriesAndEUAndStates(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
address_type = forms.ChoiceField(
|
address_type = forms.ChoiceField(
|
||||||
|
|||||||
@@ -1732,8 +1732,8 @@ class CheckinListAttendeeFilterForm(FilterForm):
|
|||||||
'-timestamp': (OrderBy(F('last_entry'), nulls_last=True, descending=True), '-order__code'),
|
'-timestamp': (OrderBy(F('last_entry'), nulls_last=True, descending=True), '-order__code'),
|
||||||
'item': ('item__name', 'variation__value', 'order__code'),
|
'item': ('item__name', 'variation__value', 'order__code'),
|
||||||
'-item': ('-item__name', '-variation__value', '-order__code'),
|
'-item': ('-item__name', '-variation__value', '-order__code'),
|
||||||
'seat': ('seat__sorting_rank', 'seat__guid'),
|
'seat': ('seat__sorting_rank', 'seat__seat_guid'),
|
||||||
'-seat': ('-seat__sorting_rank', '-seat__guid'),
|
'-seat': ('-seat__sorting_rank', '-seat__seat_guid'),
|
||||||
'date': ('subevent__date_from', 'subevent__id', 'order__code'),
|
'date': ('subevent__date_from', 'subevent__id', 'order__code'),
|
||||||
'-date': ('-subevent__date_from', 'subevent__id', '-order__code'),
|
'-date': ('-subevent__date_from', 'subevent__id', '-order__code'),
|
||||||
'name': {'_order': F('display_name').asc(nulls_first=True),
|
'name': {'_order': F('display_name').asc(nulls_first=True),
|
||||||
@@ -1940,7 +1940,7 @@ class VoucherFilterForm(FilterForm):
|
|||||||
'item__category__position',
|
'item__category__position',
|
||||||
'item__category',
|
'item__category',
|
||||||
'item__position',
|
'item__position',
|
||||||
'item__variation__position',
|
'variation__position',
|
||||||
'quota__name',
|
'quota__name',
|
||||||
),
|
),
|
||||||
'subevent': 'subevent__date_from',
|
'subevent': 'subevent__date_from',
|
||||||
@@ -1950,7 +1950,7 @@ class VoucherFilterForm(FilterForm):
|
|||||||
'-item__category__position',
|
'-item__category__position',
|
||||||
'-item__category',
|
'-item__category',
|
||||||
'-item__position',
|
'-item__position',
|
||||||
'-item__variation__position',
|
'-variation__position',
|
||||||
'-quota__name',
|
'-quota__name',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,12 +86,14 @@ class GlobalSettingsForm(SettingsForm):
|
|||||||
('leaflet_tiles', forms.CharField(
|
('leaflet_tiles', forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Leaflet tiles URL pattern"),
|
label=_("Leaflet tiles URL pattern"),
|
||||||
help_text=_("e.g. {sample}").format(sample="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png")
|
help_text=_("e.g. {sample}").format(sample="https://tile.openstreetmap.org/{z}/{x}/{y}.png")
|
||||||
)),
|
)),
|
||||||
('leaflet_tiles_attribution', forms.CharField(
|
('leaflet_tiles_attribution', forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_("Leaflet tiles attribution"),
|
label=_("Leaflet tiles attribution"),
|
||||||
help_text=_("e.g. {sample}").format(sample='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors')
|
help_text=_("e.g. {sample}").format(
|
||||||
|
sample='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
)
|
||||||
)),
|
)),
|
||||||
])
|
])
|
||||||
responses = register_global_settings.send(self)
|
responses = register_global_settings.send(self)
|
||||||
|
|||||||
@@ -461,11 +461,6 @@ class ItemCreateForm(I18nModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.cleaned_data.get('copy_from'):
|
if self.cleaned_data.get('copy_from'):
|
||||||
for mv in self.cleaned_data['copy_from'].meta_values.all():
|
|
||||||
mv.pk = None
|
|
||||||
mv.item = instance
|
|
||||||
mv.save(force_insert=True)
|
|
||||||
|
|
||||||
for question in self.cleaned_data['copy_from'].questions.all():
|
for question in self.cleaned_data['copy_from'].questions.all():
|
||||||
question.items.add(instance)
|
question.items.add(instance)
|
||||||
question.log_action('pretix.event.question.changed', user=self.user, data={
|
question.log_action('pretix.event.question.changed', user=self.user, data={
|
||||||
|
|||||||
@@ -340,6 +340,9 @@ class VoucherBulkForm(VoucherForm):
|
|||||||
|
|
||||||
def clean_send_recipients(self):
|
def clean_send_recipients(self):
|
||||||
raw = self.cleaned_data['send_recipients']
|
raw = self.cleaned_data['send_recipients']
|
||||||
|
if self.cleaned_data.get('send', None) is False:
|
||||||
|
# No need to validate addresses if the section was turned off
|
||||||
|
return []
|
||||||
if not raw:
|
if not raw:
|
||||||
return []
|
return []
|
||||||
r = raw.split('\n')
|
r = raw.split('\n')
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
|||||||
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||||
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||||
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
|
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
|
||||||
|
'pretix.giftcards.acceptance.acceptor.removed': _('A gift card acceptor has been removed.'),
|
||||||
'pretix.giftcards.acceptance.issuer.removed': _('A gift card issuer has been removed or declined.'),
|
'pretix.giftcards.acceptance.issuer.removed': _('A gift card issuer has been removed or declined.'),
|
||||||
'pretix.giftcards.acceptance.issuer.accepted': _('A new gift card issuer has been accepted.'),
|
'pretix.giftcards.acceptance.issuer.accepted': _('A new gift card issuer has been accepted.'),
|
||||||
'pretix.webhook.created': _('The webhook has been created.'),
|
'pretix.webhook.created': _('The webhook has been created.'),
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
|
<li>
|
||||||
|
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="png" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
|
||||||
|
target="_blank" download>
|
||||||
|
{% blocktrans with filetype="PNG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="svg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
|
||||||
|
target="_blank" download>
|
||||||
|
{% blocktrans with filetype="SVG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="jpeg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
|
||||||
|
target="_blank" download>
|
||||||
|
{% blocktrans with filetype="JPG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="gif" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
|
||||||
|
target="_blank" download>
|
||||||
|
{% blocktrans with filetype="GIF" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -27,28 +27,7 @@
|
|||||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
|
||||||
<i class="fa fa-qrcode" aria-hidden="true"></i>
|
<i class="fa fa-qrcode" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
{% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=0 %}
|
||||||
<li>
|
|
||||||
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="png" %}" target="_blank" download>
|
|
||||||
{% blocktrans with filetype="PNG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="svg" %}" target="_blank" download>
|
|
||||||
{% blocktrans with filetype="SVG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="jpeg" %}" target="_blank" download>
|
|
||||||
{% blocktrans with filetype="JPG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{% url "control:event.qrcode" event=request.event.slug organizer=request.organizer.slug filetype="gif" %}" target="_blank" download>
|
|
||||||
{% blocktrans with filetype="GIF" %}Download QR code as {{ filetype }} image{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -337,7 +337,7 @@
|
|||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
The waiting list currently is not compatible with some advanced features of pretix such as
|
The waiting list currently is not compatible with some advanced features of pretix such as
|
||||||
add-on products or product bundles.
|
hidden products, add-on products or product bundles.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
|
|||||||
@@ -48,6 +48,12 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Benefit" context "discount" %}</legend>
|
<legend>{% trans "Benefit" context "discount" %}</legend>
|
||||||
|
{% bootstrap_field form.benefit_same_products layout="control" %}
|
||||||
|
<div data-display-dependency="#id_benefit_same_products" data-inverse>
|
||||||
|
{% bootstrap_field form.benefit_limit_products layout="control" %}
|
||||||
|
{% bootstrap_field form.benefit_apply_to_addons layout="control" %}
|
||||||
|
{% bootstrap_field form.benefit_ignore_voucher_discounted layout="control" %}
|
||||||
|
</div>
|
||||||
{% bootstrap_field form.benefit_discount_matching_percent layout="control" addon_after="%" %}
|
{% bootstrap_field form.benefit_discount_matching_percent layout="control" addon_after="%" %}
|
||||||
{% bootstrap_field form.benefit_only_apply_to_cheapest_n_matches layout="control" %}
|
{% bootstrap_field form.benefit_only_apply_to_cheapest_n_matches layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
<strong>
|
<strong>
|
||||||
{{ sums.count }}
|
{{ sums.sum_count }}
|
||||||
</strong>
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|||||||
@@ -295,6 +295,11 @@
|
|||||||
{% bootstrap_field sform.invoice_regenerate_allowed layout="control" %}
|
{% bootstrap_field sform.invoice_regenerate_allowed layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-lg-2">
|
<div class="col-xs-12 col-lg-2">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -307,10 +312,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group submit-group">
|
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
|
||||||
{% trans "Save" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -42,10 +42,18 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label" for="id_url">{% trans "Voucher link" %}</label>
|
<label class="col-md-3 control-label" for="id_url">{% trans "Voucher link" %}</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" name="url"
|
<div class="input-group">
|
||||||
value="{% abseventurl request.event "presale:event.redeem" %}?voucher={{ voucher.code|urlencode }}{% if voucher.subevent_id %}&subevent={{ voucher.subevent_id }}{% endif %}"
|
<input type="text" name="url"
|
||||||
class="form-control"
|
value="{{ url }}"
|
||||||
id="id_url" readonly>
|
class="form-control"
|
||||||
|
id="id_url" readonly>
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fa fa-qrcode" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=url %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -96,7 +96,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% if "can_change_vouchers" in request.eventpermset %}
|
{% if "can_change_vouchers" in request.eventpermset %}
|
||||||
<input type="checkbox" data-toggle-table />
|
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label">
|
||||||
|
<input type="checkbox" data-toggle-table />
|
||||||
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@@ -139,7 +141,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if "can_change_vouchers" in request.eventpermset %}
|
{% if "can_change_vouchers" in request.eventpermset %}
|
||||||
<input type="checkbox" name="voucher" class="" value="{{ v.pk }}"/>
|
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label">
|
||||||
|
<input type="checkbox" name="voucher" class="batch-select-checkbox" value="{{ v.pk }}"/>
|
||||||
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -194,9 +198,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% if "can_change_vouchers" in request.eventpermset %}
|
{% if "can_change_vouchers" in request.eventpermset %}
|
||||||
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
<div class="batch-select-actions">
|
||||||
{% trans "Delete selected" %}
|
<button type="submit" class="btn btn-danger" name="action" value="delete">
|
||||||
</button>
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
|
{% trans "Delete selected" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% include "pretixcontrol/pagination.html" %}
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
|||||||
@@ -198,12 +198,12 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
|
|||||||
else item.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
|
else item.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
|
||||||
)
|
)
|
||||||
if row[1] is None:
|
if row[1] is None:
|
||||||
happy += 1
|
happy += wlt['cnt']
|
||||||
elif row[1] > 0:
|
elif row[1] > 0:
|
||||||
happy += 1
|
happy += min(wlt['cnt'], row[1])
|
||||||
for q in quotas:
|
for q in quotas:
|
||||||
if q.size is not None:
|
if q.size is not None:
|
||||||
quota_cache[q.pk] = (quota_cache[q.pk][0], quota_cache[q.pk][1] - 1)
|
quota_cache[q.pk] = (quota_cache[q.pk][0], quota_cache[q.pk][1] - min(wlt['cnt'], row[1]))
|
||||||
|
|
||||||
widgets.append({
|
widgets.append({
|
||||||
'content': None if lazy else NUM_WIDGET.format(
|
'content': None if lazy else NUM_WIDGET.format(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from collections import OrderedDict
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlparse, urlsplit
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import bleach
|
import bleach
|
||||||
@@ -50,6 +50,7 @@ from django.apps import apps
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
@@ -61,6 +62,7 @@ from django.http import (
|
|||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
|
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
|
||||||
from django.views.generic import FormView, ListView
|
from django.views.generic import FormView, ListView
|
||||||
@@ -783,8 +785,8 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
v = str(request.event.settings.mail_text_order_payment_failed)
|
v = str(request.event.settings.mail_text_order_placed)
|
||||||
v = format_map(v, self.placeholders('mail_text_order_payment_failed'))
|
v = format_map(v, self.placeholders('mail_text_order_placed'))
|
||||||
renderers = request.event.get_html_mail_renderers()
|
renderers = request.event.get_html_mail_renderers()
|
||||||
if request.GET.get('renderer') in renderers:
|
if request.GET.get('renderer') in renderers:
|
||||||
with rolledback_transaction():
|
with rolledback_transaction():
|
||||||
@@ -1530,6 +1532,12 @@ class EventQRCode(EventPermissionRequiredMixin, View):
|
|||||||
def get(self, request, *args, filetype, **kwargs):
|
def get(self, request, *args, filetype, **kwargs):
|
||||||
url = build_absolute_uri(request.event, 'presale:event.index')
|
url = build_absolute_uri(request.event, 'presale:event.index')
|
||||||
|
|
||||||
|
if "url" in request.GET:
|
||||||
|
if url_has_allowed_host_and_scheme(request.GET["url"], allowed_hosts=[urlparse(url).netloc]):
|
||||||
|
url = request.GET["url"]
|
||||||
|
else:
|
||||||
|
raise PermissionDenied("Untrusted URL")
|
||||||
|
|
||||||
qr = qrcode.QRCode(
|
qr = qrcode.QRCode(
|
||||||
version=1,
|
version=1,
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_M,
|
error_correction=qrcode.constants.ERROR_CORRECT_M,
|
||||||
|
|||||||
@@ -1188,30 +1188,46 @@ class MetaDataEditorMixin:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def meta_forms(self):
|
def meta_forms(self):
|
||||||
if hasattr(self, 'object') and self.object:
|
if getattr(self, 'object', None):
|
||||||
val_instances = {
|
val_instances = {
|
||||||
v.property_id: v for v in self.object.meta_values.all()
|
v.property_id: v for v in self.object.meta_values.all()
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
val_instances = {}
|
val_instances = {}
|
||||||
|
|
||||||
|
if getattr(self, 'copy_from', None):
|
||||||
|
defaults = {
|
||||||
|
v.property_id: v.value for v in self.copy_from.meta_values.all()
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
defaults = {}
|
||||||
|
|
||||||
formlist = []
|
formlist = []
|
||||||
|
|
||||||
for p in self.request.event.item_meta_properties.all():
|
for p in self.request.event.item_meta_properties.all():
|
||||||
formlist.append(self._make_meta_form(p, val_instances))
|
formlist.append(self._make_meta_form(p, val_instances, defaults))
|
||||||
return formlist
|
return formlist
|
||||||
|
|
||||||
def _make_meta_form(self, p, val_instances):
|
def _make_meta_form(self, p, val_instances, defaults):
|
||||||
return self.meta_form(
|
return self.meta_form(
|
||||||
prefix='prop-{}'.format(p.pk),
|
prefix='prop-{}'.format(p.pk),
|
||||||
property=p,
|
property=p,
|
||||||
instance=val_instances.get(p.pk, self.meta_model(property=p, item=self.object)),
|
instance=val_instances.get(
|
||||||
|
p.pk,
|
||||||
|
self.meta_model(
|
||||||
|
property=p,
|
||||||
|
item=self.object if getattr(self, 'object', None) else None,
|
||||||
|
value=defaults.get(p.pk, None)
|
||||||
|
)
|
||||||
|
),
|
||||||
data=(self.request.POST if self.request.method == "POST" else None)
|
data=(self.request.POST if self.request.method == "POST" else None)
|
||||||
)
|
)
|
||||||
|
|
||||||
def save_meta(self):
|
def save_meta(self):
|
||||||
for f in self.meta_forms:
|
for f in self.meta_forms:
|
||||||
if f.cleaned_data.get('value'):
|
if f.cleaned_data.get('value'):
|
||||||
|
if not f.instance.item_id:
|
||||||
|
f.instance.item = self.object
|
||||||
f.save()
|
f.save()
|
||||||
elif f.instance and f.instance.pk:
|
elif f.instance and f.instance.pk:
|
||||||
f.instance.delete()
|
f.instance.delete()
|
||||||
@@ -1257,6 +1273,7 @@ class ItemCreate(EventPermissionRequiredMixin, MetaDataEditorMixin, CreateView):
|
|||||||
messages.success(self.request, _('Your changes have been saved.'))
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
|
|
||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
self.save_meta()
|
||||||
form.instance.log_action('pretix.event.item.added', user=self.request.user, data={
|
form.instance.log_action('pretix.event.item.added', user=self.request.user, data={
|
||||||
k: (form.cleaned_data.get(k).name
|
k: (form.cleaned_data.get(k).name
|
||||||
if isinstance(form.cleaned_data.get(k), File)
|
if isinstance(form.cleaned_data.get(k), File)
|
||||||
@@ -1283,6 +1300,14 @@ class ItemCreate(EventPermissionRequiredMixin, MetaDataEditorMixin, CreateView):
|
|||||||
ctx['meta_forms'] = self.meta_forms
|
ctx['meta_forms'] = self.meta_forms
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.object = None
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid() and all([f.is_valid() for f in self.meta_forms]):
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
|
class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
|
||||||
form_class = ItemUpdateForm
|
form_class = ItemUpdateForm
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ class OrderTransactions(OrderView):
|
|||||||
'item', 'variation', 'subevent'
|
'item', 'variation', 'subevent'
|
||||||
).order_by('datetime')
|
).order_by('datetime')
|
||||||
ctx['sums'] = self.order.transactions.aggregate(
|
ctx['sums'] = self.order.transactions.aggregate(
|
||||||
count=Sum('count'),
|
sum_count=Sum('count'),
|
||||||
full_price=Sum(F('count') * F('price')),
|
full_price=Sum(F('count') * F('price')),
|
||||||
full_tax_value=Sum(F('count') * F('tax_value')),
|
full_tax_value=Sum(F('count') * F('tax_value')),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1054,8 +1054,8 @@ class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, Organizer
|
|||||||
limit_events_list=Subquery(
|
limit_events_list=Subquery(
|
||||||
Device.limit_events.through.objects.filter(
|
Device.limit_events.through.objects.filter(
|
||||||
device_id=OuterRef('pk')
|
device_id=OuterRef('pk')
|
||||||
).order_by('device_id', 'event_id').values('device_id').annotate(
|
).order_by().values('device_id').annotate(
|
||||||
g=GroupConcat('event_id', separator=',')
|
g=GroupConcat('event_id', separator=',', ordered=True)
|
||||||
).values('g')
|
).values('g')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ def variations_select2(request, **kwargs):
|
|||||||
F('item__category__position').asc(nulls_first=True),
|
F('item__category__position').asc(nulls_first=True),
|
||||||
'item__category_id',
|
'item__category_id',
|
||||||
'item__position',
|
'item__position',
|
||||||
'item__pk'
|
'item__pk',
|
||||||
'position',
|
'position',
|
||||||
'value'
|
'value'
|
||||||
).select_related('item')
|
).select_related('item')
|
||||||
@@ -718,7 +718,7 @@ def itemvarquota_select2(request, **kwargs):
|
|||||||
itemqs = request.event.items.prefetch_related('variations').filter(
|
itemqs = request.event.items.prefetch_related('variations').filter(
|
||||||
Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query)
|
Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query)
|
||||||
)
|
)
|
||||||
quotaqs = request.event.quotas.filter(quotaf).select_related('subevent')
|
quotaqs = request.event.quotas.filter(quotaf).select_related('subevent').order_by('-subevent__date_from', 'name')
|
||||||
more = False
|
more = False
|
||||||
else:
|
else:
|
||||||
if page == 1:
|
if page == 1:
|
||||||
@@ -727,7 +727,7 @@ def itemvarquota_select2(request, **kwargs):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
itemqs = request.event.items.none()
|
itemqs = request.event.items.none()
|
||||||
quotaqs = request.event.quotas.filter(name__icontains=query).select_related('subevent')
|
quotaqs = request.event.quotas.filter(name__icontains=query).select_related('subevent').order_by('-subevent__date_from', 'name')
|
||||||
total = quotaqs.count()
|
total = quotaqs.count()
|
||||||
pagesize = 20
|
pagesize = 20
|
||||||
offset = (page - 1) * pagesize
|
offset = (page - 1) * pagesize
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import bleach
|
import bleach
|
||||||
from defusedcsv import csv
|
from defusedcsv import csv
|
||||||
@@ -75,6 +76,7 @@ from pretix.control.views import PaginationMixin
|
|||||||
from pretix.helpers.compat import CompatDeleteView
|
from pretix.helpers.compat import CompatDeleteView
|
||||||
from pretix.helpers.format import format_map
|
from pretix.helpers.format import format_map
|
||||||
from pretix.helpers.models import modelcopy
|
from pretix.helpers.models import modelcopy
|
||||||
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
|
|
||||||
|
|
||||||
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||||
@@ -315,6 +317,13 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
|
|||||||
expires__gte=now()
|
expires__gte=now()
|
||||||
).count()
|
).count()
|
||||||
ctx['redeemed_in_carts'] = redeemed_in_carts
|
ctx['redeemed_in_carts'] = redeemed_in_carts
|
||||||
|
|
||||||
|
url_params = {
|
||||||
|
'voucher': self.object.code
|
||||||
|
}
|
||||||
|
if self.object.subevent_id:
|
||||||
|
url_params['subevent'] = self.object.subevent_id
|
||||||
|
ctx['url'] = build_absolute_uri(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,18 +66,26 @@ class GroupConcat(Aggregate):
|
|||||||
function = 'group_concat'
|
function = 'group_concat'
|
||||||
template = '%(function)s(%(field)s, "%(separator)s")'
|
template = '%(function)s(%(field)s, "%(separator)s")'
|
||||||
|
|
||||||
def __init__(self, *expressions, **extra):
|
def __init__(self, *expressions, ordered=False, **extra):
|
||||||
|
self.ordered = ordered
|
||||||
if 'separator' not in extra:
|
if 'separator' not in extra:
|
||||||
# For PostgreSQL separator is an obligatory
|
# For PostgreSQL separator is an obligatory
|
||||||
extra.update({'separator': ','})
|
extra.update({'separator': ','})
|
||||||
super().__init__(*expressions, **extra)
|
super().__init__(*expressions, **extra)
|
||||||
|
|
||||||
def as_postgresql(self, compiler, connection):
|
def as_postgresql(self, compiler, connection):
|
||||||
return super().as_sql(
|
if self.ordered:
|
||||||
compiler, connection,
|
return super().as_sql(
|
||||||
function='string_agg',
|
compiler, connection,
|
||||||
template="%(function)s(%(field)s::text, '%(separator)s')",
|
function='string_agg',
|
||||||
)
|
template="%(function)s(%(field)s::text, '%(separator)s' ORDER BY %(field)s ASC)",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return super().as_sql(
|
||||||
|
compiler, connection,
|
||||||
|
function='string_agg',
|
||||||
|
template="%(function)s(%(field)s::text, '%(separator)s')",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReplicaRouter:
|
class ReplicaRouter:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-27 11:58+0000\n"
|
"PO-Revision-Date: 2023-08-16 22:00+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Felix Hartnagel <felix@fhcom.de>\n"
|
||||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/de_Informal/>\n"
|
"pretix/pretix/de_Informal/>\n"
|
||||||
"Language: de_Informal\n"
|
"Language: de_Informal\n"
|
||||||
@@ -16172,7 +16172,7 @@ msgstr "Kontoeinstellungen"
|
|||||||
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:13
|
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:13
|
||||||
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:14
|
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:14
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Anmelden"
|
msgstr "Abmelden"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/base.html:245
|
#: pretix/control/templates/pretixcontrol/base.html:245
|
||||||
msgid "Organizer account"
|
msgid "Organizer account"
|
||||||
@@ -31019,7 +31019,7 @@ msgstr ""
|
|||||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||||
msgid "Please note that we still await your payment to complete the process."
|
msgid "Please note that we still await your payment to complete the process."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Bitte beachten Sie, dass wir noch deine Zahlung erwarten, um den Prozess "
|
"Bitte beachte, dass wir noch deine Zahlung erwarten, um den Prozess "
|
||||||
"abzuschließen."
|
"abzuschließen."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-13 07:22+0000\n"
|
"PO-Revision-Date: 2023-08-02 02:00+0000\n"
|
||||||
"Last-Translator: Martin Gross <gross@rami.io>\n"
|
"Last-Translator: Patrizia Cotza <str.cotza@gmail.com>\n"
|
||||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"es/>\n"
|
"es/>\n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:78
|
#: pretix/_base_settings.py:78
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -2435,6 +2435,8 @@ msgstr "Disponibilidad de cuotas"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Download a spreadsheet of all quotas including their current availability."
|
"Download a spreadsheet of all quotas including their current availability."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Descargar un archivo Excel con todas las cuotas incluyendo su disponibilidad "
|
||||||
|
"actual."
|
||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:1082
|
#: pretix/base/exporters/orderlist.py:1082
|
||||||
#: pretix/control/templates/pretixcontrol/items/quotas.html:45
|
#: pretix/control/templates/pretixcontrol/items/quotas.html:45
|
||||||
@@ -2499,6 +2501,7 @@ msgstr "Tarjeta de regalo"
|
|||||||
#: pretix/base/exporters/orderlist.py:1132
|
#: pretix/base/exporters/orderlist.py:1132
|
||||||
msgid "Download a spreadsheet of all gift card transactions."
|
msgid "Download a spreadsheet of all gift card transactions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Descargar una hoja de cálculo con todas las transacciones de tarjeta regalo."
|
||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:1160
|
#: pretix/base/exporters/orderlist.py:1160
|
||||||
#: pretix/base/exporters/orderlist.py:1207
|
#: pretix/base/exporters/orderlist.py:1207
|
||||||
@@ -2571,6 +2574,8 @@ msgstr "Redenciones de tarjetas de regalo"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Download a spreadsheet of all payments or refunds that involve gift cards."
|
"Download a spreadsheet of all payments or refunds that involve gift cards."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Descargar una hoja de cálculo con todos los pagos y devoluciones que "
|
||||||
|
"contienen tarjetas regalo."
|
||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:1207
|
#: pretix/base/exporters/orderlist.py:1207
|
||||||
#: pretix/control/templates/pretixcontrol/giftcards/payment.html:16
|
#: pretix/control/templates/pretixcontrol/giftcards/payment.html:16
|
||||||
@@ -2777,6 +2782,8 @@ msgid ""
|
|||||||
"Due to technical reasons you cannot set inputs, that need to be masked (e.g. "
|
"Due to technical reasons you cannot set inputs, that need to be masked (e.g. "
|
||||||
"passwords), to %(value)s."
|
"passwords), to %(value)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Por razones técnicas, no puedes introducir datos que deben estar ocultos ("
|
||||||
|
"ej. contraseñas) en el campo %(value)s."
|
||||||
|
|
||||||
#: pretix/base/forms/auth.py:57 pretix/base/forms/auth.py:168
|
#: pretix/base/forms/auth.py:57 pretix/base/forms/auth.py:168
|
||||||
msgid "Keep me logged in"
|
msgid "Keep me logged in"
|
||||||
@@ -2848,6 +2855,8 @@ msgid ""
|
|||||||
"You uploaded an image in landscape orientation. Please upload an image in "
|
"You uploaded an image in landscape orientation. Please upload an image in "
|
||||||
"portrait orientation."
|
"portrait orientation."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Has cargado una imagen con formato horizontal. Por favor sube una imagen en "
|
||||||
|
"vertical."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py:471
|
#: pretix/base/forms/questions.py:471
|
||||||
msgid "Please upload an image where the width is 3/4 of the height."
|
msgid "Please upload an image where the width is 3/4 of the height."
|
||||||
@@ -2870,6 +2879,8 @@ msgid ""
|
|||||||
"If you keep this empty, the ticket will be valid starting at the time of "
|
"If you keep this empty, the ticket will be valid starting at the time of "
|
||||||
"purchase."
|
"purchase."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Si mantienes este campo vacío, la entrada será válida empezando en el "
|
||||||
|
"momento de la compra."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py:664 pretix/base/forms/questions.py:992
|
#: pretix/base/forms/questions.py:664 pretix/base/forms/questions.py:992
|
||||||
msgid "Street and Number"
|
msgid "Street and Number"
|
||||||
@@ -2885,6 +2896,8 @@ msgid ""
|
|||||||
"Optional, but depending on the country you reside in we might need to charge "
|
"Optional, but depending on the country you reside in we might need to charge "
|
||||||
"you additional taxes if you do not enter it."
|
"you additional taxes if you do not enter it."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Opcional, pero dependiendo de tu país de residencia, es posible que haya que "
|
||||||
|
"aplicar cargos adicionales si no nos facilitas tu dirección."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py:1033 pretix/base/forms/questions.py:1039
|
#: 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."
|
msgid "If you are registered in Switzerland, you can enter your UID instead."
|
||||||
@@ -2895,6 +2908,8 @@ msgid ""
|
|||||||
"Optional, but it might be required for you to claim tax benefits on your "
|
"Optional, but it might be required for you to claim tax benefits on your "
|
||||||
"invoice depending on your and the seller’s country of residence."
|
"invoice depending on your and the seller’s country of residence."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Opcional, pero puede que sea necesario si aplican beneficios fiscales en tu "
|
||||||
|
"factura dependiendo del país de residencia del vendedor."
|
||||||
|
|
||||||
#: pretix/base/forms/questions.py:1129
|
#: pretix/base/forms/questions.py:1129
|
||||||
msgid "You need to provide a company name."
|
msgid "You need to provide a company name."
|
||||||
@@ -2927,7 +2942,7 @@ msgstr "La contraseña actual que ingresó no es correcta."
|
|||||||
|
|
||||||
#: pretix/base/forms/user.py:58
|
#: pretix/base/forms/user.py:58
|
||||||
msgid "Please choose a password different to your current one."
|
msgid "Please choose a password different to your current one."
|
||||||
msgstr ""
|
msgstr "Elige una contraseña diferente a la actual."
|
||||||
|
|
||||||
#: pretix/base/forms/user.py:63 pretix/presale/forms/customer.py:373
|
#: pretix/base/forms/user.py:63 pretix/presale/forms/customer.py:373
|
||||||
#: pretix/presale/forms/customer.py:442
|
#: pretix/presale/forms/customer.py:442
|
||||||
@@ -3139,7 +3154,7 @@ msgstr "Monto"
|
|||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
msgid "Single price: {net_price} net / {gross_price} gross"
|
msgid "Single price: {net_price} net / {gross_price} gross"
|
||||||
msgstr ""
|
msgstr "Precio único: {net_price} neto / {gross_price} bruto"
|
||||||
|
|
||||||
#: pretix/base/invoice.py:659
|
#: pretix/base/invoice.py:659
|
||||||
#, fuzzy, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
@@ -3236,7 +3251,7 @@ msgstr "Por favor, seleccione una cuota."
|
|||||||
|
|
||||||
#: pretix/base/media.py:61
|
#: pretix/base/media.py:61
|
||||||
msgid "Barcode / QR-Code"
|
msgid "Barcode / QR-Code"
|
||||||
msgstr ""
|
msgstr "Código de barras / Código QR"
|
||||||
|
|
||||||
#: pretix/base/media.py:77
|
#: pretix/base/media.py:77
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html:237
|
#: pretix/control/templates/pretixcontrol/organizers/edit.html:237
|
||||||
@@ -3435,7 +3450,7 @@ msgstr "Tipo de ticket no está permitido"
|
|||||||
|
|
||||||
#: pretix/base/models/checkin.py:351
|
#: pretix/base/models/checkin.py:351
|
||||||
msgid "Ticket code is ambiguous on list"
|
msgid "Ticket code is ambiguous on list"
|
||||||
msgstr ""
|
msgstr "El código de la entrada es ambiguo en la lista"
|
||||||
|
|
||||||
#: pretix/base/models/checkin.py:352
|
#: pretix/base/models/checkin.py:352
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -3486,6 +3501,8 @@ msgid ""
|
|||||||
"The identifier may only contain letters, numbers, dots, dashes, and "
|
"The identifier may only contain letters, numbers, dots, dashes, and "
|
||||||
"underscores. It must start and end with a letter or number."
|
"underscores. It must start and end with a letter or number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"El identificador solo puede contener letras, números, puntos, y barras "
|
||||||
|
"bajas. Tiene que empezar y terminar con una letra o un número."
|
||||||
|
|
||||||
#: pretix/base/models/customers.py:299 pretix/base/models/orders.py:1392
|
#: pretix/base/models/customers.py:299 pretix/base/models/orders.py:1392
|
||||||
#: pretix/base/models/orders.py:2971 pretix/base/settings.py:1093
|
#: pretix/base/models/orders.py:2971 pretix/base/settings.py:1093
|
||||||
@@ -3501,7 +3518,7 @@ msgstr "Seleccione país"
|
|||||||
#: pretix/base/models/customers.py:370
|
#: pretix/base/models/customers.py:370
|
||||||
msgctxt "openidconnect"
|
msgctxt "openidconnect"
|
||||||
msgid "Confidential"
|
msgid "Confidential"
|
||||||
msgstr ""
|
msgstr "Confidencial"
|
||||||
|
|
||||||
#: pretix/base/models/customers.py:371
|
#: pretix/base/models/customers.py:371
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -3520,7 +3537,7 @@ msgstr "Código de transacción"
|
|||||||
#: pretix/base/models/customers.py:378
|
#: pretix/base/models/customers.py:378
|
||||||
msgctxt "openidconnect"
|
msgctxt "openidconnect"
|
||||||
msgid "Implicit"
|
msgid "Implicit"
|
||||||
msgstr ""
|
msgstr "Implícito"
|
||||||
|
|
||||||
#: pretix/base/models/customers.py:382
|
#: pretix/base/models/customers.py:382
|
||||||
msgid "OpenID Connect access (required)"
|
msgid "OpenID Connect access (required)"
|
||||||
@@ -3572,7 +3589,7 @@ msgstr "Este identificador ya se utiliza para una pregunta diferente."
|
|||||||
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
||||||
#: pretix/plugins/checkinlists/exporters.py:671
|
#: pretix/plugins/checkinlists/exporters.py:671
|
||||||
msgid "Gate"
|
msgid "Gate"
|
||||||
msgstr ""
|
msgstr "Puerta"
|
||||||
|
|
||||||
#: pretix/base/models/devices.py:132
|
#: pretix/base/models/devices.py:132
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:83
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html:83
|
||||||
@@ -3964,7 +3981,7 @@ msgstr "Parametrizaciones adicionales"
|
|||||||
#: pretix/base/models/exports.py:61 pretix/base/models/exports.py:66
|
#: pretix/base/models/exports.py:61 pretix/base/models/exports.py:66
|
||||||
#: pretix/base/models/exports.py:71
|
#: pretix/base/models/exports.py:71
|
||||||
msgid "You can specify multiple recipients separated by commas."
|
msgid "You can specify multiple recipients separated by commas."
|
||||||
msgstr ""
|
msgstr "Puedes especificar múltiples destinatarios separados por comas."
|
||||||
|
|
||||||
#: pretix/base/models/exports.py:64
|
#: pretix/base/models/exports.py:64
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -4006,6 +4023,7 @@ msgstr "Hora de inicio del evento"
|
|||||||
#: pretix/base/models/exports.py:86
|
#: pretix/base/models/exports.py:86
|
||||||
msgid "The actual start time might be delayed depending on system load."
|
msgid "The actual start time might be delayed depending on system load."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"La hora de inicio real puede atrasarse dependiendo de la carga del sistema."
|
||||||
|
|
||||||
#: pretix/base/models/fields.py:33
|
#: pretix/base/models/fields.py:33
|
||||||
msgid "No value can contain the delimiter character."
|
msgid "No value can contain the delimiter character."
|
||||||
@@ -4417,11 +4435,11 @@ msgstr "minutos"
|
|||||||
|
|
||||||
#: pretix/base/models/items.py:626
|
#: pretix/base/models/items.py:626
|
||||||
msgid "Hours"
|
msgid "Hours"
|
||||||
msgstr ""
|
msgstr "Horas"
|
||||||
|
|
||||||
#: pretix/base/models/items.py:630
|
#: pretix/base/models/items.py:630
|
||||||
msgid "Days"
|
msgid "Days"
|
||||||
msgstr ""
|
msgstr "Días"
|
||||||
|
|
||||||
#: pretix/base/models/items.py:634
|
#: pretix/base/models/items.py:634
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -4453,7 +4471,7 @@ msgstr "El elemento seleccionado no pertenece a este evento."
|
|||||||
|
|
||||||
#: pretix/base/models/items.py:650
|
#: pretix/base/models/items.py:650
|
||||||
msgid "Reusable media policy"
|
msgid "Reusable media policy"
|
||||||
msgstr ""
|
msgstr "Condiciones de utilización de imágenes"
|
||||||
|
|
||||||
#: pretix/base/models/items.py:652
|
#: pretix/base/models/items.py:652
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -5142,7 +5160,7 @@ msgstr "Tarjeta de crédito"
|
|||||||
|
|
||||||
#: pretix/base/models/memberships.py:44
|
#: pretix/base/models/memberships.py:44
|
||||||
msgid "Membership is transferable"
|
msgid "Membership is transferable"
|
||||||
msgstr ""
|
msgstr "La suscripción es transferible"
|
||||||
|
|
||||||
#: pretix/base/models/memberships.py:45
|
#: pretix/base/models/memberships.py:45
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -5152,7 +5170,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/memberships.py:50
|
#: pretix/base/models/memberships.py:50
|
||||||
msgid "Parallel usage is allowed"
|
msgid "Parallel usage is allowed"
|
||||||
msgstr ""
|
msgstr "El uso paralelo está permitido"
|
||||||
|
|
||||||
#: pretix/base/models/memberships.py:51
|
#: pretix/base/models/memberships.py:51
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -5235,7 +5253,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/orders.py:234
|
#: pretix/base/models/orders.py:234
|
||||||
msgid "We'll show you this order to be due for a follow-up on this day."
|
msgid "We'll show you this order to be due for a follow-up on this day."
|
||||||
msgstr ""
|
msgstr "Te mostraremos esta compra en el seguimiento de ese día."
|
||||||
|
|
||||||
#: pretix/base/models/orders.py:240
|
#: pretix/base/models/orders.py:240
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -5414,7 +5432,7 @@ msgstr "Tarifa de cancelación"
|
|||||||
|
|
||||||
#: pretix/base/models/orders.py:2132
|
#: pretix/base/models/orders.py:2132
|
||||||
msgid "Insurance fee"
|
msgid "Insurance fee"
|
||||||
msgstr ""
|
msgstr "Prima de seguro"
|
||||||
|
|
||||||
#: pretix/base/models/orders.py:2133
|
#: pretix/base/models/orders.py:2133
|
||||||
msgid "Other fees"
|
msgid "Other fees"
|
||||||
@@ -5615,7 +5633,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/tax.py:168
|
#: pretix/base/models/tax.py:168
|
||||||
msgid "Official name"
|
msgid "Official name"
|
||||||
msgstr ""
|
msgstr "Nombre oficial"
|
||||||
|
|
||||||
#: pretix/base/models/tax.py:169
|
#: pretix/base/models/tax.py:169
|
||||||
msgid "Should be short, e.g. \"VAT\""
|
msgid "Should be short, e.g. \"VAT\""
|
||||||
@@ -5698,7 +5716,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/tax.py:372
|
#: pretix/base/models/tax.py:372
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
msgid "VAT liability rests with the service recipient."
|
msgid "VAT liability rests with the service recipient."
|
||||||
msgstr ""
|
msgstr "La responsabilidad del IVA es del destinatario del servicio."
|
||||||
|
|
||||||
#: pretix/base/models/vouchers.py:171
|
#: pretix/base/models/vouchers.py:171
|
||||||
msgid "No effect"
|
msgid "No effect"
|
||||||
@@ -5985,7 +6003,7 @@ msgstr "Debe elegir el producto \"{prod}\" para este asiento."
|
|||||||
#: pretix/base/models/vouchers.py:500
|
#: pretix/base/models/vouchers.py:500
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "The seat \"{id}\" is already sold or currently blocked."
|
msgid "The seat \"{id}\" is already sold or currently blocked."
|
||||||
msgstr ""
|
msgstr "El puesto\"{id}\" ya se ha vendido o está bloqueado."
|
||||||
|
|
||||||
#: pretix/base/models/waitinglist.py:64
|
#: pretix/base/models/waitinglist.py:64
|
||||||
msgid "On waiting list since"
|
msgid "On waiting list since"
|
||||||
@@ -6507,7 +6525,7 @@ msgstr "Habilitar método de pago"
|
|||||||
|
|
||||||
#: pretix/base/payment.py:441
|
#: pretix/base/payment.py:441
|
||||||
msgid "Share this link with customers who should use this payment method."
|
msgid "Share this link with customers who should use this payment method."
|
||||||
msgstr ""
|
msgstr "Comparte este link con clientes que deben usar este método de pago."
|
||||||
|
|
||||||
#: pretix/base/payment.py:487
|
#: pretix/base/payment.py:487
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
@@ -6948,7 +6966,7 @@ msgstr "Dirección de facturación empresa"
|
|||||||
|
|
||||||
#: pretix/base/pdf.py:339
|
#: pretix/base/pdf.py:339
|
||||||
msgid "Sesame Street 42"
|
msgid "Sesame Street 42"
|
||||||
msgstr ""
|
msgstr "Calle Sésamo 42"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:344
|
#: pretix/base/pdf.py:344
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -7331,6 +7349,8 @@ msgid ""
|
|||||||
"All payments for this event need to be confirmed already, so no new orders "
|
"All payments for this event need to be confirmed already, so no new orders "
|
||||||
"can be created."
|
"can be created."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Todos los pagos de este evento tienen que estar ya confirmados, por lo que "
|
||||||
|
"no se pueden crear nuevos pedidos."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:136
|
#: pretix/base/services/cart.py:136
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -7579,12 +7599,12 @@ msgstr "Razón desconocida"
|
|||||||
#: pretix/base/services/checkin.py:246
|
#: pretix/base/services/checkin.py:246
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Only allowed before {datetime}"
|
msgid "Only allowed before {datetime}"
|
||||||
msgstr ""
|
msgstr "Solo está permitido antes del {datetime}"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:248
|
#: pretix/base/services/checkin.py:248
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Only allowed after {datetime}"
|
msgid "Only allowed after {datetime}"
|
||||||
msgstr ""
|
msgstr "Solo está permitido después de {datetime}"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:251
|
#: pretix/base/services/checkin.py:251
|
||||||
msgid "Ticket type not allowed"
|
msgid "Ticket type not allowed"
|
||||||
@@ -7653,22 +7673,22 @@ msgstr "Domingo"
|
|||||||
#: pretix/base/services/checkin.py:307
|
#: pretix/base/services/checkin.py:307
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{variable} is not {value}"
|
msgid "{variable} is not {value}"
|
||||||
msgstr ""
|
msgstr "{variable} no es {value}"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:309
|
#: pretix/base/services/checkin.py:309
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Maximum {variable} exceeded"
|
msgid "Maximum {variable} exceeded"
|
||||||
msgstr ""
|
msgstr "Máximo {variable} superado"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:311
|
#: pretix/base/services/checkin.py:311
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Minimum {variable} exceeded"
|
msgid "Minimum {variable} exceeded"
|
||||||
msgstr ""
|
msgstr "Mínimo {variable} superado"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:313
|
#: pretix/base/services/checkin.py:313
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{variable} is {value}"
|
msgid "{variable} is {value}"
|
||||||
msgstr ""
|
msgstr "{variable} es {value}"
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:763
|
#: pretix/base/services/checkin.py:763
|
||||||
msgid "This order position has been canceled."
|
msgid "This order position has been canceled."
|
||||||
@@ -7831,7 +7851,7 @@ msgstr "Product de Ejemplo A"
|
|||||||
#: pretix/base/services/invoices.py:519
|
#: pretix/base/services/invoices.py:519
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "New invoice: {number}"
|
msgid "New invoice: {number}"
|
||||||
msgstr ""
|
msgstr "Nueva factura: {number}"
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:521
|
#: pretix/base/services/invoices.py:521
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -7843,6 +7863,13 @@ msgid ""
|
|||||||
"We are sending this email because you configured us to do so in your event "
|
"We are sending this email because you configured us to do so in your event "
|
||||||
"settings."
|
"settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Hola,\n"
|
||||||
|
"\n"
|
||||||
|
"Una nueva factura para tu pedido {order} de {event} se ha creado, la puedes "
|
||||||
|
"encontrar adjunta.\n"
|
||||||
|
"\n"
|
||||||
|
"Te estamos enviando este email porque lo has configurado así en la página de "
|
||||||
|
"configuración del evento."
|
||||||
|
|
||||||
#: pretix/base/services/mail.py:266
|
#: pretix/base/services/mail.py:266
|
||||||
#, fuzzy, python-brace-format
|
#, fuzzy, python-brace-format
|
||||||
@@ -10051,17 +10078,7 @@ msgstr ""
|
|||||||
"su equipo {event}"
|
"su equipo {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2108
|
#: pretix/base/settings.py:2108
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "your order {code} for {event} has been canceled.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the details of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello {attendee_name},\n"
|
"Hello {attendee_name},\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10073,15 +10090,15 @@ msgid ""
|
|||||||
"Best regards, \n"
|
"Best regards, \n"
|
||||||
"Your {event} team"
|
"Your {event} team"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Hola, \n"
|
"Hola, {attendee_name}: \n"
|
||||||
"\n"
|
"\n"
|
||||||
"su pedido {code} para {event} ha sido cancelado. \n"
|
"Te has registrado correctamente para {event}. \n"
|
||||||
"\n"
|
"\n"
|
||||||
"Puede ver los detalles de su pedido en\n"
|
"Puedes ver el estado y los detalles de tu entrada aquí:\n"
|
||||||
"{url}. \n"
|
"{url}. \n"
|
||||||
"\n"
|
"\n"
|
||||||
"Saludos cordiales, \n"
|
"Saludos cordiales, \n"
|
||||||
"su equipo {event}"
|
"El equipo {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2128
|
#: pretix/base/settings.py:2128
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -10621,19 +10638,7 @@ msgid "Order approved and confirmed: {code}"
|
|||||||
msgstr "Pedido aprobado y confirmado: {code}"
|
msgstr "Pedido aprobado y confirmado: {code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2462
|
#: pretix/base/settings.py:2462
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "your order for {event} was successful. As you only ordered free "
|
|
||||||
#| "products,\n"
|
|
||||||
#| "no payment is required.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can change your order details and view the status of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10648,15 +10653,15 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Hola, \n"
|
"Hola, \n"
|
||||||
"\n"
|
"\n"
|
||||||
"su pedido para {event} fue un exitoso. Como sólo ha pedido productos "
|
"se ha aprobado tu pedido para {event} y te damos la bienvenida a nuestro "
|
||||||
"gratuitos , \n"
|
"evento. Como solo ha pedido productos gratuitos, no se requiere ningún pago. "
|
||||||
"no se requiere ningún pago. \n"
|
"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Puede cambiar los detalles de su pedido y ver el estado del mismo en\n"
|
"Puede cambiar los detalles de su pedido y ver el estado del mismo en\n"
|
||||||
"{url}\n"
|
"{url}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Saludos cordiales, \n"
|
"Saludos cordiales, \n"
|
||||||
"su equipo {event}"
|
"El equipo de {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2495
|
#: pretix/base/settings.py:2495
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -10717,17 +10722,7 @@ msgid "Your ticket is ready for download: {code}"
|
|||||||
msgstr "Su ticket está listo para descargar: {code}"
|
msgstr "Su ticket está listo para descargar: {code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2536
|
#: pretix/base/settings.py:2536
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello {attendee_name},\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "you are registered for {event}.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "If you did not do so already, you can download your ticket here:\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello {attendee_name},\n"
|
"Hello {attendee_name},\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10739,11 +10734,11 @@ msgid ""
|
|||||||
"Best regards, \n"
|
"Best regards, \n"
|
||||||
"Your {event} team"
|
"Your {event} team"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Hola {attendee_name},\n"
|
"Hola, {attendee_name}:\n"
|
||||||
"\n"
|
"\n"
|
||||||
"está registrado en {event}.\n"
|
"está registrado en {event}.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Si aún no lo ha hecho, puede descargar su entrada aquí :\n"
|
"Si aún no lo ha hecho, puede descargar su entrada aquí:\n"
|
||||||
"{url} \n"
|
"{url} \n"
|
||||||
"\n"
|
"\n"
|
||||||
"Saludos cordiales, \n"
|
"Saludos cordiales, \n"
|
||||||
@@ -12453,10 +12448,8 @@ msgid "Ask for {fields}, display like {example}"
|
|||||||
msgstr "Pregunta por {fields}, despliega como {example}"
|
msgstr "Pregunta por {fields}, despliega como {example}"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py:634 pretix/control/forms/organizer.py:454
|
#: pretix/control/forms/event.py:634 pretix/control/forms/organizer.py:454
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Free price input"
|
|
||||||
msgid "Free text input"
|
msgid "Free text input"
|
||||||
msgstr "Entrada de precio gratuita"
|
msgstr "Entrada de texto libre"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py:666
|
#: pretix/control/forms/event.py:666
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -12733,10 +12726,8 @@ msgid "Subject for approved free order"
|
|||||||
msgstr "Pedido aprobado"
|
msgstr "Pedido aprobado"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py:1233
|
#: pretix/control/forms/event.py:1233
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Approved order"
|
|
||||||
msgid "Text for approved free order"
|
msgid "Text for approved free order"
|
||||||
msgstr "Pedido aprobado"
|
msgstr "Texto aprobado"
|
||||||
|
|
||||||
#: pretix/control/forms/event.py:1236 pretix/control/forms/event.py:1254
|
#: pretix/control/forms/event.py:1236 pretix/control/forms/event.py:1254
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -14259,7 +14250,7 @@ msgstr "ticket secreto:"
|
|||||||
|
|
||||||
#: pretix/control/forms/orders.py:458
|
#: pretix/control/forms/orders.py:458
|
||||||
msgid "Validity start"
|
msgid "Validity start"
|
||||||
msgstr ""
|
msgstr "Incio de validez"
|
||||||
|
|
||||||
#: pretix/control/forms/orders.py:463
|
#: pretix/control/forms/orders.py:463
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -14410,10 +14401,8 @@ msgid "Keep a fixed cancellation fee per ticket"
|
|||||||
msgstr "Mantener una tarifa de cancelación fija"
|
msgstr "Mantener una tarifa de cancelación fija"
|
||||||
|
|
||||||
#: pretix/control/forms/orders.py:828
|
#: pretix/control/forms/orders.py:828
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Generate tickets for non-admission products"
|
|
||||||
msgid "Free tickets and add-on products are not counted"
|
msgid "Free tickets and add-on products are not counted"
|
||||||
msgstr "Generar tickets para productos no admitidos"
|
msgstr "Las entradas gratuitas en productos complementarios no contabilizan"
|
||||||
|
|
||||||
#: pretix/control/forms/orders.py:838
|
#: pretix/control/forms/orders.py:838
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -14607,29 +14596,23 @@ msgid "Gift card value"
|
|||||||
msgstr "Tarjeta de regalo"
|
msgstr "Tarjeta de regalo"
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:700
|
#: pretix/control/forms/organizer.py:700
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This ticket has already been redeemed."
|
|
||||||
msgid "An medium with this type and identifier is already registered."
|
msgid "An medium with this type and identifier is already registered."
|
||||||
msgstr "Este ticket ya ha sido canjeado."
|
msgstr "Un medio con este tipo y este identificador ya ha sido registrado."
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:801
|
#: pretix/control/forms/organizer.py:801
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This ticket has already been redeemed."
|
|
||||||
msgid "An account with this customer ID is already registered."
|
msgid "An account with this customer ID is already registered."
|
||||||
msgstr "Este ticket ya ha sido canjeado."
|
msgstr "Una cuenta con este identificador de usuario ya está registrado."
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:802 pretix/presale/forms/customer.py:439
|
#: pretix/control/forms/organizer.py:802 pretix/presale/forms/customer.py:439
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This ticket has already been redeemed."
|
|
||||||
msgid "An account with this email address is already registered."
|
msgid "An account with this email address is already registered."
|
||||||
msgstr "Este ticket ya ha sido canjeado."
|
msgstr "Una cuenta con esta dirección de correo ya está registrada."
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:818
|
#: pretix/control/forms/organizer.py:818
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:60
|
#: pretix/control/templates/pretixcontrol/organizers/customer.html:60
|
||||||
#: pretix/presale/forms/customer.py:156 pretix/presale/forms/customer.py:472
|
#: pretix/presale/forms/customer.py:156 pretix/presale/forms/customer.py:472
|
||||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:32
|
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:32
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr ""
|
msgstr "Teléfono"
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:925
|
#: pretix/control/forms/organizer.py:925
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -14655,7 +14638,7 @@ msgstr "Clave Secreta"
|
|||||||
#: pretix/control/forms/organizer.py:937
|
#: pretix/control/forms/organizer.py:937
|
||||||
msgctxt "sso_oidc"
|
msgctxt "sso_oidc"
|
||||||
msgid "Scope"
|
msgid "Scope"
|
||||||
msgstr ""
|
msgstr "Alcance"
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:938
|
#: pretix/control/forms/organizer.py:938
|
||||||
msgctxt "sso_oidc"
|
msgctxt "sso_oidc"
|
||||||
@@ -14665,7 +14648,7 @@ msgstr ""
|
|||||||
#: pretix/control/forms/organizer.py:942
|
#: pretix/control/forms/organizer.py:942
|
||||||
msgctxt "sso_oidc"
|
msgctxt "sso_oidc"
|
||||||
msgid "User ID field"
|
msgid "User ID field"
|
||||||
msgstr ""
|
msgstr "Campo identificador del usuario"
|
||||||
|
|
||||||
#: pretix/control/forms/organizer.py:943
|
#: pretix/control/forms/organizer.py:943
|
||||||
msgctxt "sso_oidc"
|
msgctxt "sso_oidc"
|
||||||
@@ -17064,6 +17047,8 @@ msgid ""
|
|||||||
"We've detected that you are using <strong>Microsoft Internet Explorer</"
|
"We've detected that you are using <strong>Microsoft Internet Explorer</"
|
||||||
"strong>."
|
"strong>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Hemos detectado que estás usando <strong> Microsoft Internet Explorer "
|
||||||
|
"</strong>."
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/base.html:328
|
#: pretix/control/templates/pretixcontrol/base.html:328
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -17141,6 +17126,8 @@ msgid ""
|
|||||||
"For security reasons, please change your password before you continue. "
|
"For security reasons, please change your password before you continue. "
|
||||||
"Afterwards you will be redirected to your original destination."
|
"Afterwards you will be redirected to your original destination."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Por motivos de seguridad, por favor cambia tu contraseña antes de continuar. "
|
||||||
|
"Serás redirigido después a la página de origen."
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/base.html:446
|
#: pretix/control/templates/pretixcontrol/base.html:446
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -17329,7 +17316,7 @@ msgstr "Resultado"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:108
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:108
|
||||||
msgid "Cash"
|
msgid "Cash"
|
||||||
msgstr ""
|
msgstr "Efectivo"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:9
|
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:9
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:41
|
#: pretix/control/templates/pretixcontrol/checkin/checkins.html:41
|
||||||
@@ -20839,7 +20826,7 @@ msgstr "No hay solicitudes registradas todavía."
|
|||||||
#: pretix/control/templates/pretixcontrol/oauth/app_register.html:4
|
#: pretix/control/templates/pretixcontrol/oauth/app_register.html:4
|
||||||
#: pretix/control/templates/pretixcontrol/oauth/app_register.html:6
|
#: pretix/control/templates/pretixcontrol/oauth/app_register.html:6
|
||||||
msgid "Register a new application"
|
msgid "Register a new application"
|
||||||
msgstr "Registrar una nueva aplicación"
|
msgstr "Hacer un nuevo registro"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html:4
|
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html:4
|
||||||
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html:6
|
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html:6
|
||||||
@@ -22706,7 +22693,7 @@ msgstr "Crear varias fechas"
|
|||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:13
|
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:13
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(number)s selected"
|
msgid "%(number)s selected"
|
||||||
msgstr ""
|
msgstr "%(number)s selecionado"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:36
|
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:36
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html:24
|
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html:24
|
||||||
@@ -26928,10 +26915,8 @@ msgid "The device has been removed."
|
|||||||
msgstr "El dispositivo ha sido retirado."
|
msgstr "El dispositivo ha sido retirado."
|
||||||
|
|
||||||
#: pretix/control/views/user.py:449
|
#: pretix/control/views/user.py:449
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This ticket has already been redeemed."
|
|
||||||
msgid "This security device is already registered."
|
msgid "This security device is already registered."
|
||||||
msgstr "Este ticket ya ha sido canjeado."
|
msgstr "Este dispositivo ya está registrado."
|
||||||
|
|
||||||
#: pretix/control/views/user.py:471 pretix/control/views/user.py:532
|
#: pretix/control/views/user.py:471 pretix/control/views/user.py:532
|
||||||
msgid "A new two-factor authentication device has been added to your account."
|
msgid "A new two-factor authentication device has been added to your account."
|
||||||
@@ -28815,6 +28800,8 @@ msgid ""
|
|||||||
"After placing your order, you will be able to select your desired payment "
|
"After placing your order, you will be able to select your desired payment "
|
||||||
"method, including PayPal."
|
"method, including PayPal."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Después de hacer el pedido, podrás elegir el método de pago preferido, "
|
||||||
|
"incluyendo PayPal."
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_form.html:5
|
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_form.html:5
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -29843,10 +29830,8 @@ msgid "Blocked Seats"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/plugins/statistics/templates/pretixplugins/statistics/index.html:86
|
#: pretix/plugins/statistics/templates/pretixplugins/statistics/index.html:86
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Free order"
|
|
||||||
msgid "Free Seats"
|
msgid "Free Seats"
|
||||||
msgstr "Pedido gratuito"
|
msgstr "Asiento gratuito"
|
||||||
|
|
||||||
#: pretix/plugins/statistics/templates/pretixplugins/statistics/index.html:94
|
#: pretix/plugins/statistics/templates/pretixplugins/statistics/index.html:94
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -31066,6 +31051,8 @@ msgid ""
|
|||||||
"An account with this email address is already registered. Please try to log "
|
"An account with this email address is already registered. Please try to log "
|
||||||
"in or reset your password instead."
|
"in or reset your password instead."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Una cuenta con esta dirección de correo ya está registrada. Puedes entrar o "
|
||||||
|
"recuperar la contraseña."
|
||||||
|
|
||||||
#: pretix/presale/forms/customer.py:189
|
#: pretix/presale/forms/customer.py:189
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -31707,7 +31694,7 @@ msgstr "cantidad mínima a pedir: %(num)s"
|
|||||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:354
|
#: pretix/presale/templates/pretixpresale/event/voucher.html:354
|
||||||
msgctxt "price"
|
msgctxt "price"
|
||||||
msgid "free"
|
msgid "free"
|
||||||
msgstr ""
|
msgstr "gratis"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:77
|
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:77
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:51
|
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:51
|
||||||
@@ -32400,7 +32387,7 @@ msgstr ""
|
|||||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:22
|
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:22
|
||||||
#: pretix/presale/templates/pretixpresale/organizers/calendar.html:24
|
#: pretix/presale/templates/pretixpresale/organizers/calendar.html:24
|
||||||
msgid "Select month to show"
|
msgid "Select month to show"
|
||||||
msgstr ""
|
msgstr "Seleccione un mes a mostrar"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:32
|
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:32
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:36
|
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:36
|
||||||
@@ -32423,7 +32410,7 @@ msgstr ""
|
|||||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:22
|
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:22
|
||||||
#: pretix/presale/templates/pretixpresale/organizers/calendar_week.html:26
|
#: pretix/presale/templates/pretixpresale/organizers/calendar_week.html:26
|
||||||
msgid "Select week to show"
|
msgid "Select week to show"
|
||||||
msgstr ""
|
msgstr "Selecciona semana a mostrar"
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:43
|
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:43
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -32779,6 +32766,7 @@ msgstr "Cambiar detalles"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"You need to select a payment method above before you can request an invoice."
|
"You need to select a payment method above before you can request an invoice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Es necesario seleccionar un método de pago antes de solicitar una factura."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:267
|
#: pretix/presale/templates/pretixpresale/event/order.html:267
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:274
|
#: pretix/presale/templates/pretixpresale/event/order.html:274
|
||||||
@@ -33141,6 +33129,8 @@ msgid ""
|
|||||||
"Please select the desired changes to your ticket. Note that you can only "
|
"Please select the desired changes to your ticket. Note that you can only "
|
||||||
"perform changes that do not change the total price of the ticket."
|
"perform changes that do not change the total price of the ticket."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Selecciona los cambios que quieres hacer en tu entrada. Ten en cuenta que "
|
||||||
|
"solo puedes hacer cambios que no cambien el valor total de la entrada."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/position_change_confirm.html:19
|
#: pretix/presale/templates/pretixpresale/event/position_change_confirm.html:19
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -33702,7 +33692,7 @@ msgstr "No se ha encontrado el organizador seleccionado."
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Your selected payment method can only be used for a payment of at least "
|
"Your selected payment method can only be used for a payment of at least "
|
||||||
"{amount}."
|
"{amount}."
|
||||||
msgstr ""
|
msgstr "El método de pago solo se puede usar para un pago de mínimo {amount}."
|
||||||
|
|
||||||
#: pretix/presale/views/cart.py:183
|
#: pretix/presale/views/cart.py:183
|
||||||
msgid "Please enter positive numbers only."
|
msgid "Please enter positive numbers only."
|
||||||
@@ -33981,6 +33971,8 @@ msgid ""
|
|||||||
"Thank you very much! We will assign your spot on the waiting list to someone "
|
"Thank you very much! We will assign your spot on the waiting list to someone "
|
||||||
"else."
|
"else."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"¡Muchas gracias! Le asignaremos tu puesto en la lista de espera a otra "
|
||||||
|
"persona."
|
||||||
|
|
||||||
#: pretix/presale/views/widget.py:341
|
#: pretix/presale/views/widget.py:341
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -34001,7 +33993,7 @@ msgstr "de %(start_date)s"
|
|||||||
|
|
||||||
#: pretix/settings.py:710
|
#: pretix/settings.py:710
|
||||||
msgid "User profile only"
|
msgid "User profile only"
|
||||||
msgstr ""
|
msgstr "Solo perfil de usuario"
|
||||||
|
|
||||||
#: pretix/settings.py:711
|
#: pretix/settings.py:711
|
||||||
msgid "Read access"
|
msgid "Read access"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-22 21:00+0000\n"
|
"PO-Revision-Date: 2023-08-16 22:00+0000\n"
|
||||||
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
|
"Last-Translator: Maurice Kaag <maurice@kaag.me>\n"
|
||||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||||
">\n"
|
">\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
@@ -13,7 +13,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:78
|
#: pretix/_base_settings.py:78
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -247,9 +247,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/api/serializers/item.py:185 pretix/control/forms/item.py:1084
|
#: pretix/api/serializers/item.py:185 pretix/control/forms/item.py:1084
|
||||||
msgid "The bundled item must not have bundles on its own."
|
msgid "The bundled item must not have bundles on its own."
|
||||||
msgstr ""
|
msgstr "Un produit groupé ne doit pas contenir des produits groupés."
|
||||||
"Un forfait ne doit pas contenir des produits, qui sont eux-mêmes des "
|
|
||||||
"forfaits."
|
|
||||||
|
|
||||||
#: pretix/api/serializers/item.py:262
|
#: pretix/api/serializers/item.py:262
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1666,7 +1664,7 @@ msgstr "Nécessite une attention particulière"
|
|||||||
#: pretix/base/exporters/items.py:91 pretix/base/models/items.py:553
|
#: pretix/base/exporters/items.py:91 pretix/base/models/items.py:553
|
||||||
#: pretix/base/models/items.py:1018
|
#: pretix/base/models/items.py:1018
|
||||||
msgid "Original price"
|
msgid "Original price"
|
||||||
msgstr "Facture originale"
|
msgstr "Prix d'origine"
|
||||||
|
|
||||||
#: pretix/base/exporters/items.py:92 pretix/base/models/items.py:565
|
#: pretix/base/exporters/items.py:92 pretix/base/models/items.py:565
|
||||||
msgid "This product is a gift card"
|
msgid "This product is a gift card"
|
||||||
@@ -3097,7 +3095,7 @@ msgstr "Annulation"
|
|||||||
#: pretix/base/invoice.py:620 pretix/base/invoice.py:628
|
#: pretix/base/invoice.py:620 pretix/base/invoice.py:628
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Déscription"
|
msgstr "Description"
|
||||||
|
|
||||||
#: pretix/base/invoice.py:621 pretix/base/invoice.py:629
|
#: pretix/base/invoice.py:621 pretix/base/invoice.py:629
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
@@ -6227,24 +6225,18 @@ msgid "Ambiguous option selected."
|
|||||||
msgstr "Option ambiguë sélectionnée."
|
msgstr "Option ambiguë sélectionnée."
|
||||||
|
|
||||||
#: pretix/base/orderimport.py:845
|
#: pretix/base/orderimport.py:845
|
||||||
#, fuzzy
|
|
||||||
#| msgid "No matching seat was found."
|
|
||||||
msgid "No matching customer was found."
|
msgid "No matching customer was found."
|
||||||
msgstr "Aucun siège correspondant n’a été trouvé."
|
msgstr "Aucun client correspondant n’a été trouvé."
|
||||||
|
|
||||||
#: pretix/base/payment.py:86
|
#: pretix/base/payment.py:86
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Apply"
|
|
||||||
msgctxt "payment"
|
msgctxt "payment"
|
||||||
msgid "Apple Pay"
|
msgid "Apple Pay"
|
||||||
msgstr "Appliquer"
|
msgstr "Apple Pay"
|
||||||
|
|
||||||
#: pretix/base/payment.py:87
|
#: pretix/base/payment.py:87
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Android (Google Play)"
|
|
||||||
msgctxt "payment"
|
msgctxt "payment"
|
||||||
msgid "Google Pay"
|
msgid "Google Pay"
|
||||||
msgstr "Android (Google Play)"
|
msgstr "Google Pay"
|
||||||
|
|
||||||
#: pretix/base/payment.py:256
|
#: pretix/base/payment.py:256
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:115
|
#: pretix/presale/templates/pretixpresale/event/order.html:115
|
||||||
@@ -6811,16 +6803,12 @@ msgid "List of Add-Ons"
|
|||||||
msgstr "Liste des Addons"
|
msgstr "Liste des Addons"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:364
|
#: pretix/base/pdf.py:364
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "Add-on 1\n"
|
|
||||||
#| "Add-on 2"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Add-on 1\n"
|
"Add-on 1\n"
|
||||||
"2x Add-on 2"
|
"2x Add-on 2"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Add-on 1\n"
|
"Add-on 1\n"
|
||||||
"Add-on 2"
|
"2x Add-on 2"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:370 pretix/control/forms/filter.py:1275
|
#: pretix/base/pdf.py:370 pretix/control/forms/filter.py:1275
|
||||||
#: pretix/control/forms/filter.py:1277
|
#: pretix/control/forms/filter.py:1277
|
||||||
@@ -8229,16 +8217,14 @@ msgid "Gift card currency"
|
|||||||
msgstr "Devise de la carte-cadeau"
|
msgstr "Devise de la carte-cadeau"
|
||||||
|
|
||||||
#: pretix/base/settings.py:277
|
#: pretix/base/settings.py:277
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "Automatically create a new gift card if a previously unknown chip is seen"
|
|
||||||
msgid "Automatically create a new gift card if a new chip is encoded"
|
msgid "Automatically create a new gift card if a new chip is encoded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Créer automatiquement une nouvelle carte-cadeau si une puce inconnue est vue"
|
"Créer automatiquement une nouvelle carte-cadeau si une nouvelle puce est "
|
||||||
|
"encodée"
|
||||||
|
|
||||||
#: pretix/base/settings.py:299
|
#: pretix/base/settings.py:299
|
||||||
msgid "Use UID protection feature of NFC chip"
|
msgid "Use UID protection feature of NFC chip"
|
||||||
msgstr ""
|
msgstr "Utiliser la fonction de protection UID de la puce NFC"
|
||||||
|
|
||||||
#: pretix/base/settings.py:313
|
#: pretix/base/settings.py:313
|
||||||
msgid "Maximum number of items per order"
|
msgid "Maximum number of items per order"
|
||||||
@@ -8753,10 +8739,8 @@ msgstr ""
|
|||||||
"commandés par d'autres personnes."
|
"commandés par d'autres personnes."
|
||||||
|
|
||||||
#: pretix/base/settings.py:942
|
#: pretix/base/settings.py:942
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Expiration date"
|
|
||||||
msgid "Expiration delay"
|
msgid "Expiration delay"
|
||||||
msgstr "Date d'expiration"
|
msgstr "Délai d’expiration"
|
||||||
|
|
||||||
#: pretix/base/settings.py:943
|
#: pretix/base/settings.py:943
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -8766,6 +8750,11 @@ msgid ""
|
|||||||
"beyond the \"last date of payments\" configured above, which is always "
|
"beyond the \"last date of payments\" configured above, which is always "
|
||||||
"enforced."
|
"enforced."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"La commande n’expirera réellement que ce nombre de jours après la date d’"
|
||||||
|
"expiration communiquée au client. Si vous sélectionnez « Ne terminez les "
|
||||||
|
"conditions de paiement que les jours de semaine » ci-dessus, cela sera "
|
||||||
|
"également respecté. Cependant, cela ne retardera pas au-delà de la « "
|
||||||
|
"dernière date de paiement » configurée ci-dessus, qui est toujours appliquée."
|
||||||
|
|
||||||
#: pretix/base/settings.py:964
|
#: pretix/base/settings.py:964
|
||||||
msgid "Hide \"payment pending\" state on customer-facing pages"
|
msgid "Hide \"payment pending\" state on customer-facing pages"
|
||||||
@@ -10186,25 +10175,12 @@ msgstr ""
|
|||||||
"Votre équipe {event}"
|
"Votre équipe {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2349
|
#: pretix/base/settings.py:2349
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "Payment received for your order: {code}"
|
|
||||||
msgid "Payment failed for your order: {code}"
|
msgid "Payment failed for your order: {code}"
|
||||||
msgstr "Paiement reçu pour votre commande : {code}"
|
msgstr "Paiement échoué pour votre commande : {code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2353
|
#: pretix/base/settings.py:2353
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "we did not yet receive a full payment for your order for {event}.\n"
|
|
||||||
#| "Please keep in mind that we only guarantee your order if we receive\n"
|
|
||||||
#| "your payment before {expire_date}.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the payment information and the status of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10222,18 +10198,18 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Bonjour\n"
|
"Bonjour\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Nous n’avons pas encore reçu le paiement intégral de votre commande de "
|
"Votre tentative de paiement pour votre commande pour {event} a échoué.\n"
|
||||||
"{event}.\n"
|
|
||||||
"Veuillez garder à l’esprit que nous ne garantissons votre commande que si "
|
|
||||||
"nous recevons\n"
|
|
||||||
"votre paiement avant {expire_date}.\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"Vous pouvez consulter les informations de paiement et l’état de votre "
|
"Votre commande est toujours valide et vous pouvez essayer de payer à nouveau "
|
||||||
"commande à l’adresse\n"
|
"en utilisant le même mode de paiement ou un mode de paiement différent. "
|
||||||
|
"Veuillez effectuer votre paiement avant {expire_date}.\n"
|
||||||
|
"\n"
|
||||||
|
"Vous pouvez réessayer le paiement et consulter l’état de votre commande à l’"
|
||||||
|
"adresse\n"
|
||||||
"{url}\n"
|
"{url}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Sinceres salutations \n"
|
"Sinceres salutations \n"
|
||||||
"Votre {event} équipe"
|
"Votre équipe {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2367
|
#: pretix/base/settings.py:2367
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -11154,7 +11130,7 @@ msgstr "Degré (après le nom)"
|
|||||||
#: pretix/base/settings.py:3577
|
#: pretix/base/settings.py:3577
|
||||||
msgctxt "person_name_sample"
|
msgctxt "person_name_sample"
|
||||||
msgid "MA"
|
msgid "MA"
|
||||||
msgstr ""
|
msgstr "MA"
|
||||||
|
|
||||||
#: pretix/base/settings.py:3684 pretix/control/forms/event.py:217
|
#: pretix/base/settings.py:3684 pretix/control/forms/event.py:217
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -11195,7 +11171,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/settings.py:3720
|
#: pretix/base/settings.py:3720
|
||||||
msgid "This needs to be disabled if other NFC-based types are active."
|
msgid "This needs to be disabled if other NFC-based types are active."
|
||||||
msgstr ""
|
msgstr "Cela doit être désactivé si d’autres types NFC sont actifs."
|
||||||
|
|
||||||
#: pretix/base/shredder.py:74 pretix/base/shredder.py:77
|
#: pretix/base/shredder.py:74 pretix/base/shredder.py:77
|
||||||
msgid "Your event needs to be over to use this feature."
|
msgid "Your event needs to be over to use this feature."
|
||||||
@@ -14920,10 +14896,8 @@ msgid "The medium has been connected to a new ticket."
|
|||||||
msgstr "Le média a été connecté à un nouveau ticket."
|
msgstr "Le média a été connecté à un nouveau ticket."
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:371
|
#: pretix/control/logdisplay.py:371
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The medium has been connected to a new ticket."
|
|
||||||
msgid "The medium has been connected to a new gift card."
|
msgid "The medium has been connected to a new gift card."
|
||||||
msgstr "Le média a été connecté à un nouveau ticket."
|
msgstr "Le média a été connecté à une nouvelle carte cadeau."
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:372 pretix/control/logdisplay.py:413
|
#: pretix/control/logdisplay.py:372 pretix/control/logdisplay.py:413
|
||||||
msgid "Sending of an email has failed."
|
msgid "Sending of an email has failed."
|
||||||
@@ -15193,12 +15167,9 @@ msgstr ""
|
|||||||
"l'utilisateur."
|
"l'utilisateur."
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:436
|
#: pretix/control/logdisplay.py:436
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "An email has been sent to notify the user that payment has been received."
|
|
||||||
msgid "An email has been sent to notify the user that the payment failed."
|
msgid "An email has been sent to notify the user that the payment failed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Un mail a été envoyé pour informer l'utilisateur que le paiement a été reçu."
|
"Un mail a été envoyé pour informer l'utilisateur que le paiement a échoué."
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:437
|
#: pretix/control/logdisplay.py:437
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -18023,7 +17994,7 @@ msgstr "Contenu de l' e-mail"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:90
|
#: pretix/control/templates/pretixcontrol/event/mail.html:90
|
||||||
msgid "Placed order"
|
msgid "Placed order"
|
||||||
msgstr "Ordre placé"
|
msgstr "Commande placée"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:93
|
#: pretix/control/templates/pretixcontrol/event/mail.html:93
|
||||||
msgid "Paid order"
|
msgid "Paid order"
|
||||||
@@ -18044,10 +18015,8 @@ msgid "Payment reminder"
|
|||||||
msgstr "Rappel de paiement"
|
msgstr "Rappel de paiement"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:108
|
#: pretix/control/templates/pretixcontrol/event/mail.html:108
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment fee"
|
|
||||||
msgid "Payment failed"
|
msgid "Payment failed"
|
||||||
msgstr "Frais de paiement"
|
msgstr "Paiement échoué"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:111
|
#: pretix/control/templates/pretixcontrol/event/mail.html:111
|
||||||
msgid "Waiting list notification"
|
msgid "Waiting list notification"
|
||||||
@@ -18102,8 +18071,6 @@ msgid "Deadlines"
|
|||||||
msgstr "Échéances"
|
msgstr "Échéances"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/payment.html:68
|
#: pretix/control/templates/pretixcontrol/event/payment.html:68
|
||||||
#, fuzzy
|
|
||||||
#| msgid "days"
|
|
||||||
msgctxt "unit"
|
msgctxt "unit"
|
||||||
msgid "days"
|
msgid "days"
|
||||||
msgstr "jours"
|
msgstr "jours"
|
||||||
@@ -22176,6 +22143,10 @@ msgid ""
|
|||||||
"made by NXP. This provides a higher level of security than other approaches, "
|
"made by NXP. This provides a higher level of security than other approaches, "
|
||||||
"but requires all chips to be encoded prior to use."
|
"but requires all chips to be encoded prior to use."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ce type de support ne fonctionne qu’avec des puces NFC du type Mifare "
|
||||||
|
"Ultralight AES fabriquées par NXP. Cela fournit un niveau de sécurité plus "
|
||||||
|
"élevé que les autres approches, mais nécessite que toutes les puces soient "
|
||||||
|
"encodées avant utilisation."
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:64
|
#: pretix/control/templates/pretixcontrol/organizers/export.html:64
|
||||||
msgid "Run export now and download result"
|
msgid "Run export now and download result"
|
||||||
@@ -24255,19 +24226,15 @@ msgid ""
|
|||||||
"For safety reasons, the waiting list does not run if the quota is set to "
|
"For safety reasons, the waiting list does not run if the quota is set to "
|
||||||
"unlimited."
|
"unlimited."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Pour des raisons de sécurité, la liste d’attente ne fonctionne pas si le "
|
||||||
|
"quota est fixé sur illimité."
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:219
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:219
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Quota name"
|
|
||||||
msgid "Quota unlimited"
|
msgid "Quota unlimited"
|
||||||
msgstr "Nom du quota"
|
msgstr "Quota illimité"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:225
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:225
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "\n"
|
|
||||||
#| " Waiting, product %(num)sx available\n"
|
|
||||||
#| " "
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" Waiting, product %(num)sx "
|
" Waiting, product %(num)sx "
|
||||||
@@ -24275,8 +24242,9 @@ msgid ""
|
|||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
" En attente, produit %(num)sx disponible\n"
|
" En attente, produit %(num)sx "
|
||||||
" "
|
"disponible\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:231
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:231
|
||||||
msgid "Waiting, product unavailable"
|
msgid "Waiting, product unavailable"
|
||||||
@@ -25047,13 +25015,6 @@ msgid "The selected product has been deactivated."
|
|||||||
msgstr "Le produit sélectionné a été désactivé."
|
msgstr "Le produit sélectionné a été désactivé."
|
||||||
|
|
||||||
#: pretix/control/views/mailsetup.py:195
|
#: pretix/control/views/mailsetup.py:195
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "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 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."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"We could not find an SPF record set for the domain you are trying to use. "
|
"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 "
|
"This means that there is a very high change most of the emails will be "
|
||||||
@@ -25062,11 +25023,11 @@ msgid ""
|
|||||||
"registered your domain with."
|
"registered your domain with."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Nous n’avons pas pu trouver de jeu d’enregistrements SPF pour le domaine que "
|
"Nous n’avons pas pu trouver de jeu d’enregistrements SPF pour le domaine que "
|
||||||
"vous essayez d’utiliser. Vous pouvez toujours continuer, mais cela "
|
"vous essayez d’utiliser. Cela signifie qu’il y a un changement très élevé, "
|
||||||
"augmentera les chances que les e-mails soient envoyés au spam ou rejetés. "
|
"la plupart des e-mails seront rejetés ou marqués comme spam. Nous vous "
|
||||||
"Nous vous recommandons vivement de définir un enregistrement SPF sur le "
|
"recommandons vivement de définir un enregistrement SPF sur le domaine. Vous "
|
||||||
"domaine. Vous pouvez le faire via les paramètres DNS du fournisseur auprès "
|
"pouvez le faire via les paramètres DNS du fournisseur auprès duquel vous "
|
||||||
"duquel vous avez enregistré votre domaine."
|
"avez enregistré votre domaine."
|
||||||
|
|
||||||
#: pretix/control/views/mailsetup.py:202
|
#: pretix/control/views/mailsetup.py:202
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -28160,9 +28121,6 @@ msgid "Restrict to event dates starting before"
|
|||||||
msgstr "Limiter aux dates d’événements commençant avant"
|
msgstr "Limiter aux dates d’événements commençant avant"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:170
|
#: pretix/plugins/sendmail/forms.py:170
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Send to"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Send to"
|
msgid "Send to"
|
||||||
msgstr "Envoyer à"
|
msgstr "Envoyer à"
|
||||||
@@ -28177,9 +28135,6 @@ msgid "Filter check-in status"
|
|||||||
msgstr "Filtrer le statut d'enregistrement"
|
msgstr "Filtrer le statut d'enregistrement"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:189
|
#: pretix/plugins/sendmail/forms.py:189
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to recipients without check-in"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to recipients without check-in"
|
msgid "Restrict to recipients without check-in"
|
||||||
msgstr "Restreindre aux destinataires sans enregistrement"
|
msgstr "Restreindre aux destinataires sans enregistrement"
|
||||||
@@ -28233,17 +28188,11 @@ msgid "pending with payment overdue"
|
|||||||
msgstr "en attente avec retard"
|
msgstr "en attente avec retard"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:258
|
#: pretix/plugins/sendmail/forms.py:258
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to orders with status"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to orders with status"
|
msgid "Restrict to orders with status"
|
||||||
msgstr "Restreindre aux commandes avec statut"
|
msgstr "Restreindre aux commandes avec statut"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:283 pretix/plugins/sendmail/forms.py:287
|
#: pretix/plugins/sendmail/forms.py:283 pretix/plugins/sendmail/forms.py:287
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to recipients with check-in on list"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to recipients with check-in on list"
|
msgid "Restrict to recipients with check-in on list"
|
||||||
msgstr "Restreindre aux destinataires avec enregistrement sur la liste"
|
msgstr "Restreindre aux destinataires avec enregistrement sur la liste"
|
||||||
@@ -28314,9 +28263,6 @@ msgid "Limit products"
|
|||||||
msgstr "Limiter les produits"
|
msgstr "Limiter les produits"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/models.py:218
|
#: pretix/plugins/sendmail/models.py:218
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to orders with status"
|
|
||||||
msgid "Restrict to orders with status"
|
msgid "Restrict to orders with status"
|
||||||
msgstr "Restreindre aux commandes avec statut"
|
msgstr "Restreindre aux commandes avec statut"
|
||||||
|
|
||||||
@@ -28880,7 +28826,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:296
|
#: pretix/plugins/stripe/payment.py:296
|
||||||
msgid "Check for Apple Pay/Google Pay"
|
msgid "Check for Apple Pay/Google Pay"
|
||||||
msgstr ""
|
msgstr "Vérifier Apple Pay/Google Pay"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:300
|
#: pretix/plugins/stripe/payment.py:300
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -28890,6 +28836,11 @@ msgid ""
|
|||||||
"take into consideration if Google Pay/Apple Pay has been disabled in the "
|
"take into consideration if Google Pay/Apple Pay has been disabled in the "
|
||||||
"Stripe Dashboard."
|
"Stripe Dashboard."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"pretix tentera de vérifier si le navigateur Web du client prend en charge "
|
||||||
|
"les méthodes de paiement basées sur le portefeuille comme Apple Pay ou "
|
||||||
|
"Google Pay et les affichera bien en évidence avec le mode de paiement par "
|
||||||
|
"carte de crédit. Cette détection ne prend pas en compte si Google Pay/Apple "
|
||||||
|
"Pay a été désactivé dans le tableau de bord Stripe."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:309
|
#: pretix/plugins/stripe/payment.py:309
|
||||||
msgid "Statement descriptor postfix"
|
msgid "Statement descriptor postfix"
|
||||||
@@ -28939,38 +28890,32 @@ msgid "Bancontact"
|
|||||||
msgstr "Bancontact"
|
msgstr "Bancontact"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:357
|
#: pretix/plugins/stripe/payment.py:357
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Disable SEPA Direct Debit"
|
|
||||||
msgid "SEPA Direct Debit"
|
msgid "SEPA Direct Debit"
|
||||||
msgstr "Désactiver le prélèvement SEPA"
|
msgstr "Prélèvement SEPA"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:362
|
#: pretix/plugins/stripe/payment.py:362
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "Despite the name, Sofort payments via Stripe are <strong>not</strong> "
|
|
||||||
#| "processed instantly but might take up to <strong>14 days</strong> to be "
|
|
||||||
#| "confirmed in some cases. Please only activate this payment method if your "
|
|
||||||
#| "payment term allows for this lag."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"SEPA Direct Debit payments via Stripe are <strong>not</strong> processed "
|
"SEPA Direct Debit payments via Stripe are <strong>not</strong> processed "
|
||||||
"instantly but might take up to <strong>14 days</strong> to be confirmed in "
|
"instantly but might take up to <strong>14 days</strong> to be confirmed in "
|
||||||
"some cases. Please only activate this payment method if your payment term "
|
"some cases. Please only activate this payment method if your payment term "
|
||||||
"allows for this lag."
|
"allows for this lag."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Malgré leur nom, les paiements Sofort via Stripe <strong>ne sont pas</"
|
"Les paiements par prélèvement SEPA via Stripe ne sont <strong>pas</strong> "
|
||||||
"strong> traités instantanément, mais peuvent prendre jusqu’à <strong>14 "
|
"traités instantanément, mais peuvent prendre jusqu’à <strong>14 jours</"
|
||||||
"jours</strong> pour être confirmés dans certains cas. Veuillez n’activer ce "
|
"strong> pour être confirmés dans certains cas. Veuillez n’activer ce mode de "
|
||||||
"mode de paiement que si votre délai de paiement le permet."
|
"paiement que si votre délai de paiement le permet."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:370
|
#: pretix/plugins/stripe/payment.py:370
|
||||||
msgid "SEPA Creditor Mandate Name"
|
msgid "SEPA Creditor Mandate Name"
|
||||||
msgstr ""
|
msgstr "Nom du mandat du créancier SEPA"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:372
|
#: pretix/plugins/stripe/payment.py:372
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please provide your SEPA Creditor Mandate Name, that will be displayed to "
|
"Please provide your SEPA Creditor Mandate Name, that will be displayed to "
|
||||||
"the user."
|
"the user."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Veuillez fournir votre nom de mandat de créancier SEPA, qui sera affiché à "
|
||||||
|
"l’utilisateur."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:383
|
#: pretix/plugins/stripe/payment.py:383
|
||||||
msgid "SOFORT"
|
msgid "SOFORT"
|
||||||
@@ -29081,44 +29026,32 @@ msgid "Credit card"
|
|||||||
msgstr "Carte de crédit"
|
msgstr "Carte de crédit"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1157
|
#: pretix/plugins/stripe/payment.py:1157
|
||||||
#, fuzzy
|
|
||||||
#| msgid "EPS via Stripe"
|
|
||||||
msgid "SEPA Debit via Stripe"
|
msgid "SEPA Debit via Stripe"
|
||||||
msgstr "EPS via Stripe"
|
msgstr "Prélèvement SEPA via Stripe"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1158
|
#: pretix/plugins/stripe/payment.py:1158
|
||||||
msgid "SEPA Debit"
|
msgid "SEPA Debit"
|
||||||
msgstr ""
|
msgstr "Débit SEPA"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1197
|
#: pretix/plugins/stripe/payment.py:1197
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Name"
|
msgid "Account Holder Name"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Titulaire du compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1202
|
#: pretix/plugins/stripe/payment.py:1202
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Street"
|
msgid "Account Holder Street"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Rue du titulaire du compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1214
|
#: pretix/plugins/stripe/payment.py:1214
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Postal Code"
|
msgid "Account Holder Postal Code"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Code postal du titulaire du compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1226
|
#: pretix/plugins/stripe/payment.py:1226
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder City"
|
msgid "Account Holder City"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Ville du titulaire du compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1238
|
#: pretix/plugins/stripe/payment.py:1238
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Country"
|
msgid "Account Holder Country"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Pays du titulaire du compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1282
|
#: pretix/plugins/stripe/payment.py:1282
|
||||||
msgid "giropay via Stripe"
|
msgid "giropay via Stripe"
|
||||||
@@ -29289,22 +29222,18 @@ msgid "Card type"
|
|||||||
msgstr "Type de carte"
|
msgstr "Type de carte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:14
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:14
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The total amount will be withdrawn from your credit card."
|
|
||||||
msgid "The total amount will be withdrawn from your bank account."
|
msgid "The total amount will be withdrawn from your bank account."
|
||||||
msgstr "Le montant total sera prélevé sur votre carte de crédit."
|
msgstr "Le montant total sera prélevé sur votre compte bancaire."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:18
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:18
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:20
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:20
|
||||||
msgid "Banking Institution"
|
msgid "Banking Institution"
|
||||||
msgstr ""
|
msgstr "Établissement bancaire"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:20
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:20
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:22
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:22
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account number"
|
msgid "Account number"
|
||||||
msgstr "Titulaire du compte"
|
msgstr "Numéro de compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:24
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:24
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:4
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:4
|
||||||
@@ -29353,28 +29282,20 @@ msgstr ""
|
|||||||
"serveurs."
|
"serveurs."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:10
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:10
|
||||||
#, fuzzy
|
|
||||||
#| msgid "For a credit card payment, please turn on JavaScript."
|
|
||||||
msgid "For a SEPA Debit payment, please turn on JavaScript."
|
msgid "For a SEPA Debit payment, please turn on JavaScript."
|
||||||
msgstr "Pour un paiement par carte de crédit, veuillez activer JavaScript."
|
msgstr "Pour un paiement par prélèvement SEPA, veuillez activer JavaScript."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:16
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:16
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "You already entered a card number that we will use to charge the payment "
|
|
||||||
#| "amount."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"You already entered a bank account that we will use to charge the payment "
|
"You already entered a bank account that we will use to charge the payment "
|
||||||
"amount."
|
"amount."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous avez déjà entré un numéro de carte que nous utiliserons pour débiter le "
|
"Vous avez déjà saisi un compte bancaire que nous utiliserons pour débiter le "
|
||||||
"montant du paiement."
|
"montant du paiement."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:27
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:27
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Use a different card"
|
|
||||||
msgid "Use a different account"
|
msgid "Use a different account"
|
||||||
msgstr "Utiliser une autre carte"
|
msgstr "Utiliser un autre compte"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:51
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:51
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -29390,6 +29311,18 @@ msgid ""
|
|||||||
"statement that you can obtain from your bank. You agree to receive "
|
"statement that you can obtain from your bank. You agree to receive "
|
||||||
"notifications for future debits up to 2 days before they occur."
|
"notifications for future debits up to 2 days before they occur."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"En fournissant vos informations de paiement et en confirmant ce paiement, "
|
||||||
|
"vous autorisez (A) %(sepa_creditor_name)s et Stripe, notre prestataire de "
|
||||||
|
"services de paiement et/ou PPRO, son prestataire de services local, à "
|
||||||
|
"envoyer des instructions à votre banque pour débiter votre compte et (B) "
|
||||||
|
"votre banque à débiter votre compte conformément à ces instructions. Dans le "
|
||||||
|
"cadre de vos droits, vous avez droit à un remboursement de votre banque "
|
||||||
|
"selon les termes et conditions de votre accord avec votre banque. Un "
|
||||||
|
"remboursement doit être demandé dans un délai de 8 semaines à compter de la "
|
||||||
|
"date à laquelle votre compte a été débité. Vos droits sont expliqués dans "
|
||||||
|
"une déclaration que vous pouvez obtenir auprès de votre banque. Vous "
|
||||||
|
"acceptez de recevoir des notifications pour les débits futurs jusqu’à 2 "
|
||||||
|
"jours avant qu’ils ne se produisent."
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html:6
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html:6
|
||||||
msgid "Charge ID"
|
msgid "Charge ID"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: French\n"
|
"Project-Id-Version: French\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-21 11:46+0000\n"
|
"POT-Creation-Date: 2023-07-21 11:46+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-19 17:00+0000\n"
|
"PO-Revision-Date: 2023-08-02 02:00+0000\n"
|
||||||
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
|
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
|
||||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
"fr/>\n"
|
"fr/>\n"
|
||||||
@@ -16,7 +16,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -63,7 +63,7 @@ msgstr "iDEAL"
|
|||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||||
msgid "SEPA Direct Debit"
|
msgid "SEPA Direct Debit"
|
||||||
msgstr "Débit direct SEPA"
|
msgstr "Prélèvement SEPA"
|
||||||
|
|
||||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
||||||
msgid "Bancontact"
|
msgid "Bancontact"
|
||||||
@@ -679,10 +679,8 @@ msgid "Your local time:"
|
|||||||
msgstr "Votre heure locale :"
|
msgstr "Votre heure locale :"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Apple Pay"
|
|
||||||
msgid "Google Pay"
|
msgid "Google Pay"
|
||||||
msgstr "Apple Pay"
|
msgstr "Google Pay"
|
||||||
|
|
||||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||||
msgctxt "widget"
|
msgctxt "widget"
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-16 22:00+0000\n"
|
"PO-Revision-Date: 2023-08-25 04:00+0000\n"
|
||||||
"Last-Translator: Freek Engelbarts <freekengelbarts@gmail.com>\n"
|
"Last-Translator: Alain <alain@waag.org>\n"
|
||||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
|
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
|
||||||
">\n"
|
"\n"
|
||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:78
|
#: pretix/_base_settings.py:78
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -411,7 +411,7 @@ msgstr "Bestelling is verlopen"
|
|||||||
|
|
||||||
#: pretix/api/webhooks.py:234
|
#: pretix/api/webhooks.py:234
|
||||||
msgid "Order expiry date changed"
|
msgid "Order expiry date changed"
|
||||||
msgstr "Verloopdatum aangepast."
|
msgstr "Verloopdatum aangepast"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:238 pretix/base/notifications.py:269
|
#: pretix/api/webhooks.py:238 pretix/base/notifications.py:269
|
||||||
msgid "Order information changed"
|
msgid "Order information changed"
|
||||||
@@ -534,10 +534,8 @@ msgid "Waiting list entry deleted"
|
|||||||
msgstr "Wachtlijstitem verwijderd"
|
msgstr "Wachtlijstitem verwijderd"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:351
|
#: pretix/api/webhooks.py:351
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Waiting list entries"
|
|
||||||
msgid "Waiting list entry received voucher"
|
msgid "Waiting list entry received voucher"
|
||||||
msgstr "Wachtlijstitems"
|
msgstr "Wachtlijstitem heeft voucher ontvangen"
|
||||||
|
|
||||||
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
||||||
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
||||||
@@ -553,11 +551,11 @@ msgstr "Dit veld is verplicht."
|
|||||||
|
|
||||||
#: pretix/base/addressvalidation.py:213
|
#: pretix/base/addressvalidation.py:213
|
||||||
msgid "Enter a postal code in the format XXX."
|
msgid "Enter a postal code in the format XXX."
|
||||||
msgstr "Voer een postcode in in het formaat XXX."
|
msgstr "Postcode in het formaat XXX invoeren."
|
||||||
|
|
||||||
#: pretix/base/addressvalidation.py:222 pretix/base/addressvalidation.py:224
|
#: pretix/base/addressvalidation.py:222 pretix/base/addressvalidation.py:224
|
||||||
msgid "Enter a postal code in the format XXXX."
|
msgid "Enter a postal code in the format XXXX."
|
||||||
msgstr "Voer een postcode in in het format XXXX."
|
msgstr "Postcode in het format XXXX invoeren."
|
||||||
|
|
||||||
#: pretix/base/auth.py:143
|
#: pretix/base/auth.py:143
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -2311,7 +2309,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:887
|
#: pretix/base/exporters/orderlist.py:887
|
||||||
msgid "Converted from legacy version"
|
msgid "Converted from legacy version"
|
||||||
msgstr ""
|
msgstr "Vanuit oudere versie geconverteerd"
|
||||||
|
|
||||||
#: pretix/base/exporters/orderlist.py:949
|
#: pretix/base/exporters/orderlist.py:949
|
||||||
msgid "Payments and refunds"
|
msgid "Payments and refunds"
|
||||||
@@ -4380,7 +4378,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/items.py:662
|
#: pretix/base/models/items.py:662
|
||||||
msgid "Reusable media type"
|
msgid "Reusable media type"
|
||||||
msgstr ""
|
msgstr "Mediatype"
|
||||||
|
|
||||||
#: pretix/base/models/items.py:664
|
#: pretix/base/models/items.py:664
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -6146,7 +6144,7 @@ msgstr "Vul een geldige taalcode in."
|
|||||||
#: pretix/base/orderimport.py:669 pretix/base/orderimport.py:692
|
#: pretix/base/orderimport.py:669 pretix/base/orderimport.py:692
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not parse {value} as a date and time."
|
msgid "Could not parse {value} as a date and time."
|
||||||
msgstr ""
|
msgstr "Kon {value} niet als datum en tijd herkennen."
|
||||||
|
|
||||||
#: pretix/base/orderimport.py:711
|
#: pretix/base/orderimport.py:711
|
||||||
msgid "Please enter a valid sales channel."
|
msgid "Please enter a valid sales channel."
|
||||||
@@ -6847,7 +6845,7 @@ msgstr "Geldig tot"
|
|||||||
|
|
||||||
#: pretix/base/pdf.py:457
|
#: pretix/base/pdf.py:457
|
||||||
msgid "Reusable Medium ID"
|
msgid "Reusable Medium ID"
|
||||||
msgstr ""
|
msgstr "Media-ID"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:462
|
#: pretix/base/pdf.py:462
|
||||||
msgid "Seat: Full name"
|
msgid "Seat: Full name"
|
||||||
@@ -7047,38 +7045,30 @@ msgstr ""
|
|||||||
"door u gekozen hoeveelheid. Zie hieronder voor de details."
|
"door u gekozen hoeveelheid. Zie hieronder voor de details."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:114
|
#: pretix/base/services/cart.py:114
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid "You cannot select more than %s items per order."
|
|
||||||
msgid "You cannot select more than %s item per order."
|
msgid "You cannot select more than %s item per order."
|
||||||
msgid_plural "You cannot select more than %s items per order."
|
msgid_plural "You cannot select more than %s items per order."
|
||||||
msgstr[0] "U kunt niet meer dan %s items per bestelling kiezen."
|
msgstr[0] "U kunt niet meer dan %s item per bestelling kiezen."
|
||||||
msgstr[1] "U kunt niet meer dan %s items per bestelling kiezen."
|
msgstr[1] "U kunt niet meer dan %s items per bestelling kiezen."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:118 pretix/base/services/orders.py:1468
|
#: pretix/base/services/cart.py:118 pretix/base/services/orders.py:1468
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "You cannot select more than %(max)s items of the product %(product)s."
|
|
||||||
msgid "You cannot select more than %(max)s item of the product %(product)s."
|
msgid "You cannot select more than %(max)s item of the product %(product)s."
|
||||||
msgid_plural ""
|
msgid_plural ""
|
||||||
"You cannot select more than %(max)s items of the product %(product)s."
|
"You cannot select more than %(max)s items of the product %(product)s."
|
||||||
msgstr[0] "U kunt niet meer dan %(max)s items van product %(product)s kiezen."
|
msgstr[0] "U kunt niet meer dan %(max)s item van product %(product)s kiezen."
|
||||||
msgstr[1] "U kunt niet meer dan %(max)s items van product %(product)s kiezen."
|
msgstr[1] "U kunt niet meer dan %(max)s items van product %(product)s kiezen."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:123 pretix/base/services/orders.py:1473
|
#: pretix/base/services/cart.py:123 pretix/base/services/orders.py:1473
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "You need to select at least %(min)s items of the product %(product)s."
|
|
||||||
msgid "You need to select at least %(min)s item of the product %(product)s."
|
msgid "You need to select at least %(min)s item of the product %(product)s."
|
||||||
msgid_plural ""
|
msgid_plural ""
|
||||||
"You need to select at least %(min)s items of the product %(product)s."
|
"You need to select at least %(min)s items of the product %(product)s."
|
||||||
msgstr[0] "U moet ten minste %(min)s items van product %(product)s kiezen."
|
msgstr[0] "U moet ten minste %(min)s item van product %(product)s kiezen."
|
||||||
msgstr[1] "U moet ten minste %(min)s items van product %(product)s kiezen."
|
msgstr[1] "U moet ten minste %(min)s items van product %(product)s kiezen."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:128
|
#: pretix/base/services/cart.py:128
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "We removed %(product)s from your cart as you can not buy less than "
|
|
||||||
#| "%(min)s items of it."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"We removed %(product)s from your cart as you can not buy less than %(min)s "
|
"We removed %(product)s from your cart as you can not buy less than %(min)s "
|
||||||
"item of it."
|
"item of it."
|
||||||
@@ -7087,10 +7077,10 @@ msgid_plural ""
|
|||||||
"items of it."
|
"items of it."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
"We hebben %(product)s uit uw winkelwagen verwijderd, omdat u niet minder dan "
|
"We hebben %(product)s uit uw winkelwagen verwijderd, omdat u niet minder dan "
|
||||||
"%(min)s ervan kunt kopen."
|
"%(min)s item ervan kunt kopen."
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
"We hebben %(product)s uit uw winkelwagen verwijderd, omdat u niet minder dan "
|
"We hebben %(product)s uit uw winkelwagen verwijderd, omdat u niet minder dan "
|
||||||
"%(min)s ervan kunt kopen."
|
"%(min)s items ervan kunt kopen."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:132 pretix/base/services/orders.py:146
|
#: pretix/base/services/cart.py:132 pretix/base/services/orders.py:146
|
||||||
#: pretix/presale/templates/pretixpresale/event/index.html:157
|
#: pretix/presale/templates/pretixpresale/event/index.html:157
|
||||||
@@ -7252,10 +7242,7 @@ msgid "You can not select two variations of the same add-on product."
|
|||||||
msgstr "U kunt niet twee varianten van hetzelfde add-on-product selecteren."
|
msgstr "U kunt niet twee varianten van hetzelfde add-on-product selecteren."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:185 pretix/base/services/orders.py:184
|
#: pretix/base/services/cart.py:185 pretix/base/services/orders.py:184
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "You can select at most %(max)s add-ons from the category %(cat)s for the "
|
|
||||||
#| "product %(base)s."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can select at most %(max)s add-on from the category %(cat)s for the "
|
"You can select at most %(max)s add-on from the category %(cat)s for the "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
@@ -7263,17 +7250,14 @@ msgid_plural ""
|
|||||||
"You can select at most %(max)s add-ons from the category %(cat)s for the "
|
"You can select at most %(max)s add-ons from the category %(cat)s for the "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
"U kunt maximaal %(max)s add-ons van de categorie %(cat)s selecteren voor het "
|
"U kunt maximaal %(max)s add-on van de categorie %(cat)s selecteren voor het "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
"U kunt maximaal %(max)s add-ons van de categorie %(cat)s selecteren voor het "
|
"U kunt maximaal %(max)s add-ons van de categorie %(cat)s selecteren voor het "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:190 pretix/base/services/orders.py:189
|
#: pretix/base/services/cart.py:190 pretix/base/services/orders.py:189
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "You need to select at least %(min)s add-ons from the category %(cat)s for "
|
|
||||||
#| "the product %(base)s."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"You need to select at least %(min)s add-on from the category %(cat)s for the "
|
"You need to select at least %(min)s add-on from the category %(cat)s for the "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
@@ -7281,7 +7265,7 @@ msgid_plural ""
|
|||||||
"You need to select at least %(min)s add-ons from the category %(cat)s for "
|
"You need to select at least %(min)s add-ons from the category %(cat)s for "
|
||||||
"the product %(base)s."
|
"the product %(base)s."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
"U moet minimaal %(min)s add-ons van de categorie %(cat)s selecteren voor het "
|
"U moet minimaal %(min)s add-on van de categorie %(cat)s selecteren voor het "
|
||||||
"product %(base)s."
|
"product %(base)s."
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
"U moet minimaal %(min)s add-ons van de categorie %(cat)s selecteren voor het "
|
"U moet minimaal %(min)s add-ons van de categorie %(cat)s selecteren voor het "
|
||||||
@@ -7446,16 +7430,14 @@ msgid "This ticket has been blocked."
|
|||||||
msgstr "Dit ticket werd reeds eenmaal gebruikt."
|
msgstr "Dit ticket werd reeds eenmaal gebruikt."
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:781 pretix/base/services/checkin.py:785
|
#: pretix/base/services/checkin.py:781 pretix/base/services/checkin.py:785
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "Only allowed after {datetime}"
|
|
||||||
msgid "This ticket is only valid after {datetime}."
|
msgid "This ticket is only valid after {datetime}."
|
||||||
msgstr "Alleen toegestaan vanaf {datetime}"
|
msgstr "Dit ticket is geldig vanaf {datetime}."
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:795 pretix/base/services/checkin.py:799
|
#: pretix/base/services/checkin.py:795 pretix/base/services/checkin.py:799
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "This ticket has already been redeemed."
|
|
||||||
msgid "This ticket was only valid before {datetime}."
|
msgid "This ticket was only valid before {datetime}."
|
||||||
msgstr "Dit ticket is al gebruikt."
|
msgstr "Dit ticket was geldig vòòr {datetime}."
|
||||||
|
|
||||||
#: pretix/base/services/checkin.py:830
|
#: pretix/base/services/checkin.py:830
|
||||||
msgid "This order position has an invalid product for this check-in list."
|
msgid "This order position has an invalid product for this check-in list."
|
||||||
@@ -7516,14 +7498,14 @@ msgid "Export failed"
|
|||||||
msgstr "Geëxporteerde bestanden"
|
msgstr "Geëxporteerde bestanden"
|
||||||
|
|
||||||
#: pretix/base/services/export.py:206
|
#: pretix/base/services/export.py:206
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Permission denied"
|
|
||||||
msgid "Permission denied."
|
msgid "Permission denied."
|
||||||
msgstr "Geen toestemming"
|
msgstr "Geen toestemming."
|
||||||
|
|
||||||
#: pretix/base/services/export.py:221
|
#: pretix/base/services/export.py:221
|
||||||
msgid "Your exported data exceeded the size limit for scheduled exports."
|
msgid "Your exported data exceeded the size limit for scheduled exports."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"De door u geëxporteerde data overschrijdt de grootte-limiet voor geplande "
|
||||||
|
"exports."
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:103
|
#: pretix/base/services/invoices.py:103
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -7548,11 +7530,10 @@ msgstr ""
|
|||||||
"{country}"
|
"{country}"
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:220 pretix/base/services/invoices.py:257
|
#: pretix/base/services/invoices.py:220 pretix/base/services/invoices.py:257
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "Event location"
|
|
||||||
msgctxt "invoice"
|
msgctxt "invoice"
|
||||||
msgid "Event location: {location}"
|
msgid "Event location: {location}"
|
||||||
msgstr "Evenementlocatie"
|
msgstr "Evenementlocatie: {location}"
|
||||||
|
|
||||||
#: pretix/base/services/invoices.py:236
|
#: pretix/base/services/invoices.py:236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -7766,10 +7747,7 @@ msgid "Your cart is empty."
|
|||||||
msgstr "Uw winkelwagen is leeg."
|
msgstr "Uw winkelwagen is leeg."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:138
|
#: pretix/base/services/orders.py:138
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "You cannot select more than %(max)s items of the product %(product)s. We "
|
|
||||||
#| "removed the surplus items from your cart."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"You cannot select more than %(max)s item of the product %(product)s. We "
|
"You cannot select more than %(max)s item of the product %(product)s. We "
|
||||||
"removed the surplus items from your cart."
|
"removed the surplus items from your cart."
|
||||||
@@ -7777,11 +7755,11 @@ msgid_plural ""
|
|||||||
"You cannot select more than %(max)s items of the product %(product)s. We "
|
"You cannot select more than %(max)s items of the product %(product)s. We "
|
||||||
"removed the surplus items from your cart."
|
"removed the surplus items from your cart."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
"U kunt niet meer dan %(max)s kopieën van het product %(product)s kiezen. We "
|
"U kunt van het product %(product)s niet meer dan %(max)s per bestelling "
|
||||||
"hebben het overschot uit uw winkelwagen verwijderd."
|
"kiezen. We hebben de overtallige producten uit uw winkelwagen verwijderd."
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
"U kunt niet meer dan %(max)s kopieën van het product %(product)s kiezen. We "
|
"U kunt van het product %(product)s niet meer dan %(max)s per bestelling "
|
||||||
"hebben het overschot uit uw winkelwagen verwijderd."
|
"kiezen. We hebben de overtallige producten uit uw winkelwagen verwijderd."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:147
|
#: pretix/base/services/orders.py:147
|
||||||
msgid "The booking period has ended."
|
msgid "The booking period has ended."
|
||||||
@@ -7831,10 +7809,9 @@ msgstr ""
|
|||||||
"niet geldig voor dit item. We hebben dit item uit uw winkelwagen verwijderd."
|
"niet geldig voor dit item. We hebben dit item uit uw winkelwagen verwijderd."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:168
|
#: pretix/base/services/orders.py:168
|
||||||
#, fuzzy
|
|
||||||
#| msgid "You need a valid voucher code to order this product."
|
|
||||||
msgid "You need a valid voucher code to order one of the products."
|
msgid "You need a valid voucher code to order one of the products."
|
||||||
msgstr "U heeft een geldige vouchercode nodig om dit product te bestellen."
|
msgstr ""
|
||||||
|
"U heeft een geldige vouchercode nodig om een van de producten te bestellen."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:170
|
#: pretix/base/services/orders.py:170
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -7873,10 +7850,8 @@ msgstr ""
|
|||||||
"is besteld."
|
"is besteld."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:210
|
#: pretix/base/services/orders.py:210
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The order has been canceled."
|
|
||||||
msgid "The order was not canceled."
|
msgid "The order was not canceled."
|
||||||
msgstr "De bestelling is geannuleerd."
|
msgstr "De bestelling is niet geannuleerd."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:265 pretix/control/forms/orders.py:120
|
#: pretix/base/services/orders.py:265 pretix/control/forms/orders.py:120
|
||||||
msgid "The new expiry date needs to be in the future."
|
msgid "The new expiry date needs to be in the future."
|
||||||
@@ -7912,10 +7887,8 @@ msgstr ""
|
|||||||
"bestelling is betaald."
|
"bestelling is betaald."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:918
|
#: pretix/base/services/orders.py:918
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This payment method does not support automatic refunds."
|
|
||||||
msgid "The selected payment methods do not cover the total balance."
|
msgid "The selected payment methods do not cover the total balance."
|
||||||
msgstr "Deze betalingsmethode ondersteunt geen automatische terugbetalingen."
|
msgstr "Deze betalingsmethode dekt het volledige bedrag niet."
|
||||||
|
|
||||||
#: pretix/base/services/orders.py:990
|
#: pretix/base/services/orders.py:990
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -8070,10 +8043,8 @@ msgid "Something happened in your event after the export, please try again."
|
|||||||
msgstr "Er is iets gebeurd in uw evenement na de export, probeer het opnieuw."
|
msgstr "Er is iets gebeurd in uw evenement na de export, probeer het opnieuw."
|
||||||
|
|
||||||
#: pretix/base/services/shredder.py:177
|
#: pretix/base/services/shredder.py:177
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment completed."
|
|
||||||
msgid "Data shredding completed"
|
msgid "Data shredding completed"
|
||||||
msgstr "Betaling voltooid."
|
msgstr "Verwijderen van data voltooid."
|
||||||
|
|
||||||
#: pretix/base/services/stats.py:210
|
#: pretix/base/services/stats.py:210
|
||||||
msgid "Uncategorized"
|
msgid "Uncategorized"
|
||||||
@@ -10100,19 +10071,7 @@ msgid "Your order is pending payment: {code}"
|
|||||||
msgstr "Uw bestelling wacht op betaling: {code}"
|
msgstr "Uw bestelling wacht op betaling: {code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2316
|
#: pretix/base/settings.py:2316
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "we did not yet receive a full payment for your order for {event}.\n"
|
|
||||||
#| "Please keep in mind that we only guarantee your order if we receive\n"
|
|
||||||
#| "your payment before {expire_date}.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the payment information and the status of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10135,7 +10094,7 @@ msgstr ""
|
|||||||
"U kunt de betalingsinformatie en de status van uw bestelling inzien op\n"
|
"U kunt de betalingsinformatie en de status van uw bestelling inzien op\n"
|
||||||
"{url}.\n"
|
"{url}.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Met vriendelijke groet,\n"
|
"Met vriendelijke groet, \n"
|
||||||
"De organisatoren van {event}"
|
"De organisatoren van {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2329
|
#: pretix/base/settings.py:2329
|
||||||
@@ -10145,19 +10104,7 @@ msgid "Incomplete payment received: {code}"
|
|||||||
msgstr "Betaling ontvangen voor uw bestelling: {code}"
|
msgstr "Betaling ontvangen voor uw bestelling: {code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2333
|
#: pretix/base/settings.py:2333
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "we did not yet receive a full payment for your order for {event}.\n"
|
|
||||||
#| "Please keep in mind that we only guarantee your order if we receive\n"
|
|
||||||
#| "your payment before {expire_date}.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the payment information and the status of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10175,10 +10122,11 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Hallo,\n"
|
"Hallo,\n"
|
||||||
"\n"
|
"\n"
|
||||||
"We hebben nog geen volledige betaling ontvangen voor uw bestelling voor "
|
"We hebben een betaling ontvangen voor {event}\n"
|
||||||
"{event}.\n"
|
"\n"
|
||||||
"We kunnen uw bestelling alleen garanderen als we uw betaling ontvangen\n"
|
"Helaas is het ontvangen bedrag minder dan het volledige verschuldigde "
|
||||||
"voor {expire_date}.\n"
|
"bedrag. Graag nog het bedrag van **{pending_sum}** voldoen om de bestelling "
|
||||||
|
"te voltooien.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"U kunt de betalingsinformatie en de status van uw bestelling inzien op\n"
|
"U kunt de betalingsinformatie en de status van uw bestelling inzien op\n"
|
||||||
"{url}.\n"
|
"{url}.\n"
|
||||||
@@ -10367,17 +10315,7 @@ msgstr ""
|
|||||||
"Organisatie van {event}"
|
"Organisatie van {event}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2446 pretix/base/settings.py:2483
|
#: pretix/base/settings.py:2446 pretix/base/settings.py:2483
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello {attendee_name},\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "a ticket for {event} has been ordered for you.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the details and status of your ticket here:\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -10389,9 +10327,9 @@ msgid ""
|
|||||||
"Best regards, \n"
|
"Best regards, \n"
|
||||||
"Your {event} team"
|
"Your {event} team"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Beste {attendee_name},\n"
|
"Beste,\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Er is een ticket voor {event} voor u besteld.\n"
|
"Uw ticket voor {event} is geaccordeerd.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"U kunt de details en status van uw ticket hier bekijken:\n"
|
"U kunt de details en status van uw ticket hier bekijken:\n"
|
||||||
"{url}\n"
|
"{url}\n"
|
||||||
@@ -17295,10 +17233,8 @@ msgid "Valid check-in"
|
|||||||
msgstr "Alle check-ins"
|
msgstr "Alle check-ins"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/simulator.html:67
|
#: pretix/control/templates/pretixcontrol/checkin/simulator.html:67
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Additional information"
|
|
||||||
msgid "Additional information required"
|
msgid "Additional information required"
|
||||||
msgstr "Extra informatie"
|
msgstr "Extra informatie vereist"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/simulator.html:69
|
#: pretix/control/templates/pretixcontrol/checkin/simulator.html:69
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-21 11:46+0000\n"
|
"POT-Creation-Date: 2023-07-21 11:46+0000\n"
|
||||||
"PO-Revision-Date: 2021-10-29 02:00+0000\n"
|
"PO-Revision-Date: 2023-08-24 04:00+0000\n"
|
||||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
"Last-Translator: Alain <alain@waag.org>\n"
|
||||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
"nl/>\n"
|
"nl/>\n"
|
||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
@@ -16,7 +16,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.8\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||||
@@ -252,7 +252,7 @@ msgstr "Dit ticket is nog niet betaald. Wilt u toch doorgaan?"
|
|||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||||
msgid "Additional information required"
|
msgid "Additional information required"
|
||||||
msgstr "Extra informatie nodig"
|
msgstr "Extra informatie vereist"
|
||||||
|
|
||||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||||
msgid "Valid ticket"
|
msgid "Valid ticket"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-07-16 22:00+0000\n"
|
"PO-Revision-Date: 2023-08-24 04:00+0000\n"
|
||||||
"Last-Translator: Freek Engelbarts <freekengelbarts@gmail.com>\n"
|
"Last-Translator: Alain <alain@waag.org>\n"
|
||||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||||
"pretix/nl_Informal/>\n"
|
"pretix/nl_Informal/>\n"
|
||||||
"Language: nl_Informal\n"
|
"Language: nl_Informal\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:78
|
#: pretix/_base_settings.py:78
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -555,10 +555,8 @@ msgid "Waiting list entry deleted"
|
|||||||
msgstr "Wachtlijstitem"
|
msgstr "Wachtlijstitem"
|
||||||
|
|
||||||
#: pretix/api/webhooks.py:351
|
#: pretix/api/webhooks.py:351
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Waiting list entries"
|
|
||||||
msgid "Waiting list entry received voucher"
|
msgid "Waiting list entry received voucher"
|
||||||
msgstr "Wachtlijstitems"
|
msgstr "Wachtlijstitem heeft voucher ontvangen"
|
||||||
|
|
||||||
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
||||||
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
"POT-Creation-Date: 2023-07-27 11:49+0000\n"
|
||||||
"PO-Revision-Date: 2023-06-28 06:00+0000\n"
|
"PO-Revision-Date: 2023-08-30 07:00+0000\n"
|
||||||
"Last-Translator: Yucheng Lin <yuchenglinedu@gmail.com>\n"
|
"Last-Translator: Ash So <ashs@vankaifong.com>\n"
|
||||||
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
|
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/zh_Hant/>\n"
|
"pretix/pretix/zh_Hant/>\n"
|
||||||
"Language: zh_Hant\n"
|
"Language: zh_Hant\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 4.18.2\n"
|
||||||
|
|
||||||
#: pretix/_base_settings.py:78
|
#: pretix/_base_settings.py:78
|
||||||
msgid "English"
|
msgid "English"
|
||||||
@@ -5876,24 +5876,18 @@ msgid "Ambiguous option selected."
|
|||||||
msgstr "選擇不明確的選項。"
|
msgstr "選擇不明確的選項。"
|
||||||
|
|
||||||
#: pretix/base/orderimport.py:845
|
#: pretix/base/orderimport.py:845
|
||||||
#, fuzzy
|
|
||||||
#| msgid "No matching seat was found."
|
|
||||||
msgid "No matching customer was found."
|
msgid "No matching customer was found."
|
||||||
msgstr "未找到符合的座位。"
|
msgstr "未找到符合的客戶。"
|
||||||
|
|
||||||
#: pretix/base/payment.py:86
|
#: pretix/base/payment.py:86
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Apply"
|
|
||||||
msgctxt "payment"
|
msgctxt "payment"
|
||||||
msgid "Apple Pay"
|
msgid "Apple Pay"
|
||||||
msgstr "應用"
|
msgstr "Apple Pay"
|
||||||
|
|
||||||
#: pretix/base/payment.py:87
|
#: pretix/base/payment.py:87
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Android (Google Play)"
|
|
||||||
msgctxt "payment"
|
msgctxt "payment"
|
||||||
msgid "Google Pay"
|
msgid "Google Pay"
|
||||||
msgstr "安卓(Google Play)"
|
msgstr "安卓(Google Pay)"
|
||||||
|
|
||||||
#: pretix/base/payment.py:256
|
#: pretix/base/payment.py:256
|
||||||
#: pretix/presale/templates/pretixpresale/event/order.html:115
|
#: pretix/presale/templates/pretixpresale/event/order.html:115
|
||||||
@@ -6424,16 +6418,12 @@ msgid "List of Add-Ons"
|
|||||||
msgstr "附加組件清單"
|
msgstr "附加組件清單"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:364
|
#: pretix/base/pdf.py:364
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "Add-on 1\n"
|
|
||||||
#| "Add-on 2"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Add-on 1\n"
|
"Add-on 1\n"
|
||||||
"2x Add-on 2"
|
"2x Add-on 2"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"附加1\n"
|
"附加1\n"
|
||||||
"附加2"
|
"2x附加2"
|
||||||
|
|
||||||
#: pretix/base/pdf.py:370 pretix/control/forms/filter.py:1275
|
#: pretix/base/pdf.py:370 pretix/control/forms/filter.py:1275
|
||||||
#: pretix/control/forms/filter.py:1277
|
#: pretix/control/forms/filter.py:1277
|
||||||
@@ -9410,25 +9400,12 @@ msgstr ""
|
|||||||
"你的{event} 團隊"
|
"你的{event} 團隊"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2349
|
#: pretix/base/settings.py:2349
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "Payment received for your order: {code}"
|
|
||||||
msgid "Payment failed for your order: {code}"
|
msgid "Payment failed for your order: {code}"
|
||||||
msgstr "收到的訂單付款:{code}"
|
msgstr "訂單付款失敗:{code}"
|
||||||
|
|
||||||
#: pretix/base/settings.py:2353
|
#: pretix/base/settings.py:2353
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid ""
|
|
||||||
#| "Hello,\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "we did not yet receive a full payment for your order for {event}.\n"
|
|
||||||
#| "Please keep in mind that we only guarantee your order if we receive\n"
|
|
||||||
#| "your payment before {expire_date}.\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "You can view the payment information and the status of your order at\n"
|
|
||||||
#| "{url}\n"
|
|
||||||
#| "\n"
|
|
||||||
#| "Best regards, \n"
|
|
||||||
#| "Your {event} team"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hello,\n"
|
"Hello,\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -9446,11 +9423,12 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"你好\n"
|
"你好\n"
|
||||||
"\n"
|
"\n"
|
||||||
"我們尚未收到你的 {event} 訂單的全額付款。\n"
|
"你在 {event} 的訂單付款未能成功。\n"
|
||||||
"請記住,我們僅在收到時保證你的訂單\n"
|
|
||||||
"你在 {expire_date} 之前的付款。\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"你可以在以下位置查看付款資訊與訂單狀態:\n"
|
"您的訂單仍然有效,您可以嘗試使用相同或不同的付款方式再次進行支付,唯請在 "
|
||||||
|
"{expire_date} 前完成付款程序。\n"
|
||||||
|
"\n"
|
||||||
|
"您可以重新嘗試付款,並在以下網址檢視您的訂單狀態:\n"
|
||||||
"{url}\n"
|
"{url}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"敬此\n"
|
"敬此\n"
|
||||||
@@ -13568,7 +13546,7 @@ msgstr "已為位置 #{posid} 生成一個新密鑰。"
|
|||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The validity start date for position #{posid} has been changed to {value}."
|
"The validity start date for position #{posid} has been changed to {value}."
|
||||||
msgstr "位置 #{posid} 的有效開始日期已更改為{value}"
|
msgstr "位置 #{posid} 的有效開始日期已更改為{value}。"
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:171
|
#: pretix/control/logdisplay.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -13846,10 +13824,8 @@ msgid "The medium has been connected to a new ticket."
|
|||||||
msgstr "媒體已連接到新票證。"
|
msgstr "媒體已連接到新票證。"
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:371
|
#: pretix/control/logdisplay.py:371
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The medium has been connected to a new ticket."
|
|
||||||
msgid "The medium has been connected to a new gift card."
|
msgid "The medium has been connected to a new gift card."
|
||||||
msgstr "媒體已連接到新票證。"
|
msgstr "媒體已連接到新的禮品卡。"
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:372 pretix/control/logdisplay.py:413
|
#: pretix/control/logdisplay.py:372 pretix/control/logdisplay.py:413
|
||||||
msgid "Sending of an email has failed."
|
msgid "Sending of an email has failed."
|
||||||
@@ -14085,11 +14061,8 @@ msgid ""
|
|||||||
msgstr "包含訂單詳細資訊頁面連結的電子郵件已重新發送給使用者。"
|
msgstr "包含訂單詳細資訊頁面連結的電子郵件已重新發送給使用者。"
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:436
|
#: pretix/control/logdisplay.py:436
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "An email has been sent to notify the user that payment has been received."
|
|
||||||
msgid "An email has been sent to notify the user that the payment failed."
|
msgid "An email has been sent to notify the user that the payment failed."
|
||||||
msgstr "已發送一封電子郵件,通知使用者已收到付款。"
|
msgstr "已發送一封電子郵件通知使用者未能成功付款。"
|
||||||
|
|
||||||
#: pretix/control/logdisplay.py:437
|
#: pretix/control/logdisplay.py:437
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -16788,10 +16761,8 @@ msgid "Payment reminder"
|
|||||||
msgstr "付款提醒"
|
msgstr "付款提醒"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:108
|
#: pretix/control/templates/pretixcontrol/event/mail.html:108
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment fee"
|
|
||||||
msgid "Payment failed"
|
msgid "Payment failed"
|
||||||
msgstr "支付費用"
|
msgstr "支付失敗"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/mail.html:111
|
#: pretix/control/templates/pretixcontrol/event/mail.html:111
|
||||||
msgid "Waiting list notification"
|
msgid "Waiting list notification"
|
||||||
@@ -16845,8 +16816,6 @@ msgid "Deadlines"
|
|||||||
msgstr "期限"
|
msgstr "期限"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/event/payment.html:68
|
#: pretix/control/templates/pretixcontrol/event/payment.html:68
|
||||||
#, fuzzy
|
|
||||||
#| msgid "days"
|
|
||||||
msgctxt "unit"
|
msgctxt "unit"
|
||||||
msgid "days"
|
msgid "days"
|
||||||
msgstr "日"
|
msgstr "日"
|
||||||
@@ -21981,11 +21950,11 @@ msgstr "兩步驟狀態"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:44
|
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:44
|
||||||
msgid "Two-factor authentication is currently enabled."
|
msgid "Two-factor authentication is currently enabled."
|
||||||
msgstr "兩步驟驗證目前啟用"
|
msgstr "兩步驟驗證目前啟用。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:60
|
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:60
|
||||||
msgid "Two-factor authentication is currently disabled."
|
msgid "Two-factor authentication is currently disabled."
|
||||||
msgstr "兩步驟驗證目前停用"
|
msgstr "兩步驟驗證目前停用。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:63
|
#: pretix/control/templates/pretixcontrol/user/2fa_main.html:63
|
||||||
msgid "To enable it, you need to configure at least one device below."
|
msgid "To enable it, you need to configure at least one device below."
|
||||||
@@ -22529,20 +22498,14 @@ msgstr "此條目的優先順序已修改。此數字越高,此人將越早獲
|
|||||||
msgid ""
|
msgid ""
|
||||||
"For safety reasons, the waiting list does not run if the quota is set to "
|
"For safety reasons, the waiting list does not run if the quota is set to "
|
||||||
"unlimited."
|
"unlimited."
|
||||||
msgstr ""
|
msgstr "出於安全考慮,如果額度設定為無限制,將不設等候名單。"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:219
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:219
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Quota name"
|
|
||||||
msgid "Quota unlimited"
|
msgid "Quota unlimited"
|
||||||
msgstr "額度名稱"
|
msgstr "無限額度"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:225
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:225
|
||||||
#, fuzzy, python-format
|
#, python-format
|
||||||
#| msgid ""
|
|
||||||
#| "\n"
|
|
||||||
#| " Waiting, product %(num)sx available\n"
|
|
||||||
#| " "
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" Waiting, product %(num)sx "
|
" Waiting, product %(num)sx "
|
||||||
@@ -22550,8 +22513,8 @@ msgid ""
|
|||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
" 等待中,產品 %(num)s可用\n"
|
" 等待中,產品 %(num)s可用\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:231
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:231
|
||||||
msgid "Waiting, product unavailable"
|
msgid "Waiting, product unavailable"
|
||||||
@@ -23678,7 +23641,7 @@ msgstr "訂單已更改,使用者已收到通知。"
|
|||||||
#: pretix/control/views/orders.py:1828 pretix/control/views/orders.py:1962
|
#: pretix/control/views/orders.py:1828 pretix/control/views/orders.py:1962
|
||||||
#: pretix/control/views/orders.py:1999 pretix/presale/views/order.py:1538
|
#: pretix/control/views/orders.py:1999 pretix/presale/views/order.py:1538
|
||||||
msgid "The order has been changed."
|
msgid "The order has been changed."
|
||||||
msgstr "訂單順序已更改"
|
msgstr "訂單順序已更改。"
|
||||||
|
|
||||||
#: pretix/control/views/orders.py:1855 pretix/presale/checkoutflow.py:881
|
#: pretix/control/views/orders.py:1855 pretix/presale/checkoutflow.py:881
|
||||||
#: pretix/presale/views/order.py:799
|
#: pretix/presale/views/order.py:799
|
||||||
@@ -25274,7 +25237,7 @@ msgstr "值機清單 (PDF)"
|
|||||||
#: pretix/plugins/checkinlists/exporters.py:648
|
#: pretix/plugins/checkinlists/exporters.py:648
|
||||||
msgctxt "export_category"
|
msgctxt "export_category"
|
||||||
msgid "Check-in"
|
msgid "Check-in"
|
||||||
msgstr "Check-in"
|
msgstr "簽到"
|
||||||
|
|
||||||
#: pretix/plugins/checkinlists/exporters.py:286
|
#: pretix/plugins/checkinlists/exporters.py:286
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -26107,9 +26070,6 @@ msgid "Restrict to event dates starting before"
|
|||||||
msgstr "限制為早於之前開始的活動日期"
|
msgstr "限制為早於之前開始的活動日期"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:170
|
#: pretix/plugins/sendmail/forms.py:170
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Send to"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Send to"
|
msgid "Send to"
|
||||||
msgstr "傳送到"
|
msgstr "傳送到"
|
||||||
@@ -26124,9 +26084,6 @@ msgid "Filter check-in status"
|
|||||||
msgstr "篩選簽到狀態"
|
msgstr "篩選簽到狀態"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:189
|
#: pretix/plugins/sendmail/forms.py:189
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to recipients without check-in"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to recipients without check-in"
|
msgid "Restrict to recipients without check-in"
|
||||||
msgstr "僅限未簽到的收件者"
|
msgstr "僅限未簽到的收件者"
|
||||||
@@ -26176,17 +26133,11 @@ msgid "pending with payment overdue"
|
|||||||
msgstr "待處理,付款逾期"
|
msgstr "待處理,付款逾期"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:258
|
#: pretix/plugins/sendmail/forms.py:258
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to orders with status"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to orders with status"
|
msgid "Restrict to orders with status"
|
||||||
msgstr "限制為具有狀態的訂單"
|
msgstr "僅限具有狀態的訂單"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/forms.py:283 pretix/plugins/sendmail/forms.py:287
|
#: pretix/plugins/sendmail/forms.py:283 pretix/plugins/sendmail/forms.py:287
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to recipients with check-in on list"
|
|
||||||
msgctxt "sendmail_form"
|
msgctxt "sendmail_form"
|
||||||
msgid "Restrict to recipients with check-in on list"
|
msgid "Restrict to recipients with check-in on list"
|
||||||
msgstr "僅限在清單中簽到的收件者"
|
msgstr "僅限在清單中簽到的收件者"
|
||||||
@@ -26257,11 +26208,8 @@ msgid "Limit products"
|
|||||||
msgstr "限制商品"
|
msgstr "限制商品"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/models.py:218
|
#: pretix/plugins/sendmail/models.py:218
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "sendmail_from"
|
|
||||||
#| msgid "Restrict to orders with status"
|
|
||||||
msgid "Restrict to orders with status"
|
msgid "Restrict to orders with status"
|
||||||
msgstr "限制為具有狀態的訂單"
|
msgstr "僅限具有狀態的訂單"
|
||||||
|
|
||||||
#: pretix/plugins/sendmail/models.py:228
|
#: pretix/plugins/sendmail/models.py:228
|
||||||
msgid "Send date"
|
msgid "Send date"
|
||||||
@@ -26840,10 +26788,8 @@ msgid "Bancontact"
|
|||||||
msgstr "Bancontact"
|
msgstr "Bancontact"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:357
|
#: pretix/plugins/stripe/payment.py:357
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Disable SEPA Direct Debit"
|
|
||||||
msgid "SEPA Direct Debit"
|
msgid "SEPA Direct Debit"
|
||||||
msgstr "禁用 SEPA 直接扣款"
|
msgstr "SEPA 直接扣款"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:362
|
#: pretix/plugins/stripe/payment.py:362
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -26975,26 +26921,20 @@ msgid "Credit card"
|
|||||||
msgstr "信用卡"
|
msgstr "信用卡"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1157
|
#: pretix/plugins/stripe/payment.py:1157
|
||||||
#, fuzzy
|
|
||||||
#| msgid "EPS via Stripe"
|
|
||||||
msgid "SEPA Debit via Stripe"
|
msgid "SEPA Debit via Stripe"
|
||||||
msgstr "EPS透過Stripe"
|
msgstr "透過Stripe進行SEPA直接扣款"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1158
|
#: pretix/plugins/stripe/payment.py:1158
|
||||||
msgid "SEPA Debit"
|
msgid "SEPA Debit"
|
||||||
msgstr ""
|
msgstr "SEPA扣款"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1197
|
#: pretix/plugins/stripe/payment.py:1197
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Name"
|
msgid "Account Holder Name"
|
||||||
msgstr "帳戶持有人"
|
msgstr "帳戶持有人名稱"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1202
|
#: pretix/plugins/stripe/payment.py:1202
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Street"
|
msgid "Account Holder Street"
|
||||||
msgstr "帳戶持有人"
|
msgstr "帳戶持有人街道"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1214
|
#: pretix/plugins/stripe/payment.py:1214
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -27003,16 +26943,12 @@ msgid "Account Holder Postal Code"
|
|||||||
msgstr "帳戶持有人"
|
msgstr "帳戶持有人"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1226
|
#: pretix/plugins/stripe/payment.py:1226
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder City"
|
msgid "Account Holder City"
|
||||||
msgstr "帳戶持有人"
|
msgstr "帳戶持有人城市"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1238
|
#: pretix/plugins/stripe/payment.py:1238
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account Holder Country"
|
msgid "Account Holder Country"
|
||||||
msgstr "帳戶持有人"
|
msgstr "帳戶持有人國家"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/payment.py:1282
|
#: pretix/plugins/stripe/payment.py:1282
|
||||||
msgid "giropay via Stripe"
|
msgid "giropay via Stripe"
|
||||||
@@ -27183,10 +27119,8 @@ msgid "Card type"
|
|||||||
msgstr "卡片類型"
|
msgstr "卡片類型"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:14
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:14
|
||||||
#, fuzzy
|
|
||||||
#| msgid "The total amount will be withdrawn from your credit card."
|
|
||||||
msgid "The total amount will be withdrawn from your bank account."
|
msgid "The total amount will be withdrawn from your bank account."
|
||||||
msgstr "總金額將從你的信用卡中提取。"
|
msgstr "總金額將從你的銀行戶口中提取。"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:18
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:18
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:20
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:20
|
||||||
@@ -27195,10 +27129,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:20
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:20
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:22
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:22
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Account holder"
|
|
||||||
msgid "Account number"
|
msgid "Account number"
|
||||||
msgstr "帳戶持有人"
|
msgstr "帳戶號碼"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:24
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:24
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:4
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_simple.html:4
|
||||||
@@ -27246,20 +27178,14 @@ msgid "For a SEPA Debit payment, please turn on JavaScript."
|
|||||||
msgstr "對於信用卡付款,請打開JavaScript。"
|
msgstr "對於信用卡付款,請打開JavaScript。"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:16
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:16
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "You already entered a card number that we will use to charge the payment "
|
|
||||||
#| "amount."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"You already entered a bank account that we will use to charge the payment "
|
"You already entered a bank account that we will use to charge the payment "
|
||||||
"amount."
|
"amount."
|
||||||
msgstr "你已經輸入了一個卡號,我們將使用該卡號來收取付款金額。"
|
msgstr "您已經輸入了一個我們將用來扣除支付金額的銀行帳戶。"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:27
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:27
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Use a different card"
|
|
||||||
msgid "Use a different account"
|
msgid "Use a different account"
|
||||||
msgstr "使用不同卡片"
|
msgstr "使用不同帳戶"
|
||||||
|
|
||||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:51
|
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:51
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -27539,7 +27465,7 @@ msgstr "網上check-in"
|
|||||||
|
|
||||||
#: pretix/plugins/webcheckin/templates/pretixplugins/webcheckin/index.html:10
|
#: pretix/plugins/webcheckin/templates/pretixplugins/webcheckin/index.html:10
|
||||||
msgid "Check-in"
|
msgid "Check-in"
|
||||||
msgstr "Check-in"
|
msgstr "簽到"
|
||||||
|
|
||||||
#: pretix/presale/checkoutflow.py:107
|
#: pretix/presale/checkoutflow.py:107
|
||||||
msgctxt "checkoutflow"
|
msgctxt "checkoutflow"
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ def get_event_domain(event, fallback=False, return_info=False):
|
|||||||
|
|
||||||
def get_organizer_domain(organizer):
|
def get_organizer_domain(organizer):
|
||||||
assert isinstance(organizer, Organizer)
|
assert isinstance(organizer, Organizer)
|
||||||
|
if not organizer.pk:
|
||||||
|
return None
|
||||||
domain = getattr(organizer, '_cached_domain', None) or organizer.cache.get('domain')
|
domain = getattr(organizer, '_cached_domain', None) or organizer.cache.get('domain')
|
||||||
if domain is None:
|
if domain is None:
|
||||||
domains = organizer.domains.filter(event__isnull=True)
|
domains = organizer.domains.filter(event__isnull=True)
|
||||||
@@ -126,7 +128,7 @@ def eventreverse(obj, name, kwargs=None):
|
|||||||
:param kwargs: A dictionary of additional keyword arguments that should be used. You do not
|
:param kwargs: A dictionary of additional keyword arguments that should be used. You do not
|
||||||
need to provide the organizer or event slug here, it will be added automatically as
|
need to provide the organizer or event slug here, it will be added automatically as
|
||||||
needed.
|
needed.
|
||||||
:returns: An absolute URL (including scheme and host) as a string
|
:returns: An absolute or relative URL as a string
|
||||||
"""
|
"""
|
||||||
from pretix.multidomain import (
|
from pretix.multidomain import (
|
||||||
event_domain_urlconf, maindomain_urlconf, organizer_domain_urlconf,
|
event_domain_urlconf, maindomain_urlconf, organizer_domain_urlconf,
|
||||||
@@ -175,6 +177,17 @@ def eventreverse(obj, name, kwargs=None):
|
|||||||
|
|
||||||
|
|
||||||
def build_absolute_uri(obj, urlname, kwargs=None):
|
def build_absolute_uri(obj, urlname, kwargs=None):
|
||||||
|
"""
|
||||||
|
Works similar to ``eventreverse`` but always returns an absolute URL.
|
||||||
|
|
||||||
|
:param obj: An ``Event`` or ``Organizer`` object
|
||||||
|
:param name: The name of the URL route
|
||||||
|
:type name: str
|
||||||
|
:param kwargs: A dictionary of additional keyword arguments that should be used. You do not
|
||||||
|
need to provide the organizer or event slug here, it will be added automatically as
|
||||||
|
needed.
|
||||||
|
:returns: An absolute URL (including scheme and host) as a string
|
||||||
|
"""
|
||||||
reversedurl = eventreverse(obj, urlname, kwargs)
|
reversedurl = eventreverse(obj, urlname, kwargs)
|
||||||
if '://' in reversedurl:
|
if '://' in reversedurl:
|
||||||
return reversedurl
|
return reversedurl
|
||||||
|
|||||||
@@ -535,9 +535,11 @@ class BankTransfer(BasePaymentProvider):
|
|||||||
'eu_barcodes': self.event.currency == 'EUR',
|
'eu_barcodes': self.event.currency == 'EUR',
|
||||||
'pending_description': self.settings.get('pending_description', as_type=LazyI18nString),
|
'pending_description': self.settings.get('pending_description', as_type=LazyI18nString),
|
||||||
'details': self.settings.get('bank_details', as_type=LazyI18nString),
|
'details': self.settings.get('bank_details', as_type=LazyI18nString),
|
||||||
|
'has_invoices': payment.order.invoices.exists(),
|
||||||
|
'invoice_email_enabled': self.settings.get('invoice_email', as_type=bool),
|
||||||
}
|
}
|
||||||
ctx['any_barcodes'] = ctx['swiss_qrbill'] or ctx['eu_barcodes']
|
ctx['any_barcodes'] = ctx['swiss_qrbill'] or ctx['eu_barcodes']
|
||||||
return template.render(ctx)
|
return template.render(ctx, request=request)
|
||||||
|
|
||||||
def payment_control_render(self, request: HttpRequest, payment: OrderPayment) -> str:
|
def payment_control_render(self, request: HttpRequest, payment: OrderPayment) -> str:
|
||||||
warning = None
|
warning = None
|
||||||
|
|||||||
+7
-7
@@ -11,10 +11,10 @@
|
|||||||
<span class="icon icon-upload"></span> {% trans "Continue" %}
|
<span class="icon icon-upload"></span> {% trans "Continue" %}
|
||||||
</button>
|
</button>
|
||||||
<div class="flipped-scroll-wrapper clearfix">
|
<div class="flipped-scroll-wrapper clearfix">
|
||||||
<table class="table table-condensed flipped-scroll-inner">
|
<table class="table table-condensed table-th-sticky-horizontal flipped-scroll-inner">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Date" %}</th>
|
<th scope="row">{% trans "Date" %}</th>
|
||||||
{% for col in rows.0 %}
|
{% for col in rows.0 %}
|
||||||
<th>
|
<th>
|
||||||
<input type="radio" name="date" value="{{ forloop.counter0 }}"/>
|
<input type="radio" name="date" value="{{ forloop.counter0 }}"/>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Amount" %}</th>
|
<th scope="row">{% trans "Amount" %}</th>
|
||||||
{% for col in rows.0 %}
|
{% for col in rows.0 %}
|
||||||
<th>
|
<th>
|
||||||
<input type="radio" name="amount" value="{{ forloop.counter0 }}" required="required"/>
|
<input type="radio" name="amount" value="{{ forloop.counter0 }}" required="required"/>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Reference" %}</th>
|
<th scope="row">{% trans "Reference" %}</th>
|
||||||
{% for col in rows.0 %}
|
{% for col in rows.0 %}
|
||||||
<th>
|
<th>
|
||||||
<input type="checkbox" name="reference" value="{{ forloop.counter0 }}"/>
|
<input type="checkbox" name="reference" value="{{ forloop.counter0 }}"/>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Payer" %}</th>
|
<th scope="row">{% trans "Payer" %}</th>
|
||||||
{% for col in rows.0 %}
|
{% for col in rows.0 %}
|
||||||
<th>
|
<th>
|
||||||
<input type="checkbox" name="payer" value="{{ forloop.counter0 }}"/>
|
<input type="checkbox" name="payer" value="{{ forloop.counter0 }}"/>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th scope="row">
|
||||||
{% trans "IBAN" %}
|
{% trans "IBAN" %}
|
||||||
<label for="id_iban_clear">
|
<label for="id_iban_clear">
|
||||||
<span class="btn btn-default btn-sm fa fa-close"></span>
|
<span class="btn btn-default btn-sm fa fa-close"></span>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th scope="row">
|
||||||
{% trans "BIC" %}
|
{% trans "BIC" %}
|
||||||
<label for="id_bic_clear">
|
<label for="id_bic_clear">
|
||||||
<span class="btn btn-default btn-sm fa fa-close"></span>
|
<span class="btn btn-default btn-sm fa fa-close"></span>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
{% load money %}
|
{% load money %}
|
||||||
{% load unidecode %}
|
{% load unidecode %}
|
||||||
{% load rich_text %}
|
{% load rich_text %}
|
||||||
|
{% load eventurl %}
|
||||||
|
|
||||||
{% if pending_description %}
|
{% if pending_description %}
|
||||||
{{ pending_description|rich_text }}
|
{{ pending_description|rich_text }}
|
||||||
@@ -103,3 +104,28 @@ SCT
|
|||||||
{% if swiss_qrbill %}
|
{% if swiss_qrbill %}
|
||||||
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
|
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if invoice_email_enabled and has_invoices %}
|
||||||
|
<form method="post" action="{% eventurl event "plugins:banktransfer:mail_invoice" order=order.code secret=order.secret %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
To send the invoice directly to your accounting department, please enter their email address:
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9 col-xs-12">
|
||||||
|
<label for="mail_invoice_email" class="sr-only">{% trans "Invoice recipient email" %}:</label>
|
||||||
|
<input type="email" name="email" id="mail_invoice_email" class="form-control" value="" required
|
||||||
|
placeholder="{% trans "Email address" %}" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-xs-12">
|
||||||
|
<button class="btn btn-default btn-block">
|
||||||
|
<span class="fa fa-envelope-o" aria-hidden="true"></span>
|
||||||
|
{% trans "Send invoice via email" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
@@ -19,13 +19,19 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from django.urls import re_path
|
from django.urls import include, re_path
|
||||||
|
|
||||||
from pretix.api.urls import orga_router
|
from pretix.api.urls import orga_router
|
||||||
from pretix.plugins.banktransfer.api import BankImportJobViewSet
|
from pretix.plugins.banktransfer.api import BankImportJobViewSet
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
event_patterns = [
|
||||||
|
re_path(r'^banktransfer/', include([
|
||||||
|
re_path(r'^(?P<order>[^/][^w]+)/(?P<secret>[A-Za-z0-9]+)/mail-invoice/$', views.SendInvoiceMailView.as_view(), name='mail_invoice'),
|
||||||
|
])),
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/import/',
|
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/import/',
|
||||||
views.OrganizerImportView.as_view(),
|
views.OrganizerImportView.as_view(),
|
||||||
|
|||||||
@@ -44,14 +44,18 @@ from typing import Set
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import validate_email
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, Q, QuerySet
|
from django.db.models import Count, Q, QuerySet
|
||||||
from django.http import FileResponse, JsonResponse
|
from django.http import FileResponse, Http404, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
from django.views.generic import DetailView, FormView, ListView, View
|
from django.views.generic import DetailView, FormView, ListView, View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from localflavor.generic.forms import BICFormField, IBANFormField
|
from localflavor.generic.forms import BICFormField, IBANFormField
|
||||||
@@ -75,6 +79,8 @@ from pretix.plugins.banktransfer.refund_export import (
|
|||||||
build_sepa_xml, get_refund_export_csv,
|
build_sepa_xml, get_refund_export_csv,
|
||||||
)
|
)
|
||||||
from pretix.plugins.banktransfer.tasks import process_banktransfers
|
from pretix.plugins.banktransfer.tasks import process_banktransfers
|
||||||
|
from pretix.presale.views import EventViewMixin
|
||||||
|
from pretix.presale.views.order import OrderDetailMixin
|
||||||
|
|
||||||
logger = logging.getLogger('pretix.plugins.banktransfer')
|
logger = logging.getLogger('pretix.plugins.banktransfer')
|
||||||
|
|
||||||
@@ -886,3 +892,36 @@ class OrganizerSepaXMLExportView(OrganizerPermissionRequiredMixin, OrganizerDeta
|
|||||||
organizer=self.request.organizer,
|
organizer=self.request.organizer,
|
||||||
pk=self.kwargs.get('id')
|
pk=self.kwargs.get('id')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||||
|
class SendInvoiceMailView(EventViewMixin, OrderDetailMixin, View):
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if not self.order:
|
||||||
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||||
|
try:
|
||||||
|
validate_email(request.POST['email'])
|
||||||
|
except ValidationError:
|
||||||
|
messages.error(request, _('Please enter a valid email address.'))
|
||||||
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
|
last_payment = self.order.payments.last()
|
||||||
|
if (not last_payment
|
||||||
|
or last_payment.provider != BankTransfer.identifier
|
||||||
|
or last_payment.state != OrderPayment.PAYMENT_STATE_CREATED):
|
||||||
|
messages.error(request, _('No pending bank transfer payment found. Maybe the order has been paid already?'))
|
||||||
|
return redirect(self.get_order_url())
|
||||||
|
if not last_payment.payment_provider.settings.get('invoice_email', as_type=bool):
|
||||||
|
messages.error(request, _('Sending invoices via email is disabled by the event organizer.'))
|
||||||
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
|
last_invoice = self.order.invoices.last()
|
||||||
|
if not last_invoice:
|
||||||
|
messages.error(request, _('No invoice found, please request an invoice first.'))
|
||||||
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
|
provider = last_payment.payment_provider
|
||||||
|
provider.send_invoice_to_alternate_email(self.order, last_invoice, request.POST['email'])
|
||||||
|
|
||||||
|
messages.success(request, _('Sending the latest invoice via e-mail to {email}.').format(email=request.POST['email']))
|
||||||
|
return redirect(self.get_order_url())
|
||||||
|
|||||||
@@ -607,6 +607,12 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
response = self.client.execute(req)
|
response = self.client.execute(req)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.exception('PayPal OrdersGetRequest: {}'.format(str(e)))
|
logger.exception('PayPal OrdersGetRequest: {}'.format(str(e)))
|
||||||
|
payment.fail(info={
|
||||||
|
"error": {
|
||||||
|
"name": "IOError",
|
||||||
|
"message": str(e),
|
||||||
|
}
|
||||||
|
})
|
||||||
raise PaymentException(_('We had trouble communicating with PayPal'))
|
raise PaymentException(_('We had trouble communicating with PayPal'))
|
||||||
else:
|
else:
|
||||||
pp_captured_order = response.result
|
pp_captured_order = response.result
|
||||||
@@ -615,9 +621,15 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=pp_captured_order.id)
|
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=pp_captured_order.id)
|
||||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||||
pass
|
pass
|
||||||
if str(pp_captured_order.purchase_units[0].amount.value) != str(payment.amount) or \
|
if Decimal(pp_captured_order.purchase_units[0].amount.value) != payment.amount or \
|
||||||
pp_captured_order.purchase_units[0].amount.currency_code != self.event.currency:
|
pp_captured_order.purchase_units[0].amount.currency_code != self.event.currency:
|
||||||
logger.error('Value mismatch: Payment %s vs paypal trans %s' % (payment.id, str(pp_captured_order.dict())))
|
logger.error('Value mismatch: Payment %s vs paypal trans %s' % (payment.id, str(pp_captured_order.dict())))
|
||||||
|
payment.fail(info={
|
||||||
|
"error": {
|
||||||
|
"name": "ValidationError",
|
||||||
|
"message": "Value mismatch",
|
||||||
|
}
|
||||||
|
})
|
||||||
raise PaymentException(_('We were unable to process your payment. See below for details on how to '
|
raise PaymentException(_('We were unable to process your payment. See below for details on how to '
|
||||||
'proceed.'))
|
'proceed.'))
|
||||||
|
|
||||||
@@ -660,6 +672,12 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
self.client.execute(patchreq)
|
self.client.execute(patchreq)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
messages.error(request, _('We had trouble communicating with PayPal'))
|
messages.error(request, _('We had trouble communicating with PayPal'))
|
||||||
|
payment.fail(info={
|
||||||
|
"error": {
|
||||||
|
"name": "IOError",
|
||||||
|
"message": str(e),
|
||||||
|
}
|
||||||
|
})
|
||||||
logger.exception('PayPal OrdersPatchRequest: {}'.format(str(e)))
|
logger.exception('PayPal OrdersPatchRequest: {}'.format(str(e)))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class RuleSerializer(I18nAwareModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rule
|
model = Rule
|
||||||
fields = ['id', 'subject', 'template', 'all_products', 'limit_products', 'restrict_to_status',
|
fields = ['id', 'subject', 'template', 'all_products', 'limit_products', 'restrict_to_status',
|
||||||
'send_date', 'send_offset_days', 'send_offset_time', 'date_is_absolute',
|
'checked_in_status', 'send_date', 'send_offset_days', 'send_offset_time', 'date_is_absolute',
|
||||||
'offset_to_event_end', 'offset_is_after', 'send_to', 'enabled']
|
'offset_to_event_end', 'offset_is_after', 'send_to', 'enabled']
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
@@ -88,6 +88,10 @@ class RuleSerializer(I18nAwareModelSerializer):
|
|||||||
]:
|
]:
|
||||||
raise ValidationError(f'status {s} not allowed: restrict_to_status may only include valid states')
|
raise ValidationError(f'status {s} not allowed: restrict_to_status may only include valid states')
|
||||||
|
|
||||||
|
if full_data.get('checked_in_status') == "":
|
||||||
|
# even though "blank" is not allowed on this field, "" gets accepted without this check
|
||||||
|
raise ValidationError('empty string not allowed: use null to disable check-in based filtering')
|
||||||
|
|
||||||
return full_data
|
return full_data
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
|
|||||||
fields = ['subject', 'template', 'attach_ical',
|
fields = ['subject', 'template', 'attach_ical',
|
||||||
'send_date', 'send_offset_days', 'send_offset_time',
|
'send_date', 'send_offset_days', 'send_offset_time',
|
||||||
'all_products', 'limit_products', 'restrict_to_status',
|
'all_products', 'limit_products', 'restrict_to_status',
|
||||||
'send_to', 'enabled']
|
'checked_in_status', 'send_to', 'enabled']
|
||||||
|
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'subevent': SafeModelMultipleChoiceField,
|
'subevent': SafeModelMultipleChoiceField,
|
||||||
@@ -337,6 +337,7 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
|
|||||||
'data-inverse-dependency': '#id_all_products'},
|
'data-inverse-dependency': '#id_all_products'},
|
||||||
),
|
),
|
||||||
'send_to': forms.RadioSelect,
|
'send_to': forms.RadioSelect,
|
||||||
|
'checked_in_status': forms.RadioSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.19 on 2023-08-09 11:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sendmail', '0004_rule_restrict_to_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rule',
|
||||||
|
name='checked_in_status',
|
||||||
|
field=models.CharField(max_length=10, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -34,7 +34,8 @@ from i18nfield.fields import I18nCharField, I18nTextField
|
|||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent, fields,
|
Checkin, Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent,
|
||||||
|
fields,
|
||||||
)
|
)
|
||||||
from pretix.base.models.base import LoggingMixin
|
from pretix.base.models.base import LoggingMixin
|
||||||
from pretix.base.services.mail import SendMailException
|
from pretix.base.services.mail import SendMailException
|
||||||
@@ -112,19 +113,30 @@ class ScheduledMail(models.Model):
|
|||||||
e = self.event
|
e = self.event
|
||||||
|
|
||||||
orders = e.orders.all()
|
orders = e.orders.all()
|
||||||
limit_products = self.rule.limit_products.values_list('pk', flat=True) if not self.rule.all_products else None
|
|
||||||
|
filter_orders_by_op = False
|
||||||
|
op_qs = OrderPosition.objects.filter(
|
||||||
|
order__event=self.event,
|
||||||
|
canceled=False,
|
||||||
|
)
|
||||||
|
|
||||||
if self.subevent:
|
if self.subevent:
|
||||||
orders = orders.filter(
|
filter_orders_by_op = True
|
||||||
Exists(OrderPosition.objects.filter(order=OuterRef('pk'), subevent=self.subevent))
|
op_qs = op_qs.filter(subevent=self.subevent)
|
||||||
)
|
|
||||||
elif e.has_subevents:
|
elif e.has_subevents:
|
||||||
return # This rule should not even exist
|
return # This rule should not even exist
|
||||||
|
|
||||||
if not self.rule.all_products:
|
if not self.rule.all_products:
|
||||||
orders = orders.filter(
|
filter_orders_by_op = True
|
||||||
Exists(OrderPosition.objects.filter(order=OuterRef('pk'), item_id__in=limit_products))
|
limit_products = self.rule.limit_products.values_list('pk', flat=True)
|
||||||
)
|
op_qs = op_qs.filter(item_id__in=limit_products)
|
||||||
|
|
||||||
|
if self.rule.checked_in_status == "no_checkin":
|
||||||
|
filter_orders_by_op = True
|
||||||
|
op_qs = op_qs.filter(~Exists(Checkin.objects.filter(position_id=OuterRef('pk'))))
|
||||||
|
elif self.rule.checked_in_status == "checked_in":
|
||||||
|
filter_orders_by_op = True
|
||||||
|
op_qs = op_qs.filter(Exists(Checkin.objects.filter(position_id=OuterRef('pk'))))
|
||||||
|
|
||||||
status_q = Q(status__in=self.rule.restrict_to_status)
|
status_q = Q(status__in=self.rule.restrict_to_status)
|
||||||
if 'n__pending_approval' in self.rule.restrict_to_status:
|
if 'n__pending_approval' in self.rule.restrict_to_status:
|
||||||
@@ -142,6 +154,8 @@ class ScheduledMail(models.Model):
|
|||||||
pk__gt=self.last_successful_order_id
|
pk__gt=self.last_successful_order_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filter_orders_by_op:
|
||||||
|
orders = orders.filter(pk__in=op_qs.values_list('order_id', flat=True))
|
||||||
orders = orders.filter(
|
orders = orders.filter(
|
||||||
status_q,
|
status_q,
|
||||||
).order_by('pk').select_related('invoice_address').prefetch_related('positions')
|
).order_by('pk').select_related('invoice_address').prefetch_related('positions')
|
||||||
@@ -205,6 +219,12 @@ class Rule(models.Model, LoggingMixin):
|
|||||||
(BOTH, _('Both (all order contact addresses and all attendee email addresses)'))
|
(BOTH, _('Both (all order contact addresses and all attendee email addresses)'))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CHECK_IN_STATUS_CHOICES = [
|
||||||
|
(None, _("Everyone")),
|
||||||
|
("checked_in", _("Anyone who is or was checked in")),
|
||||||
|
("no_checkin", _("Anyone who never checked in before"))
|
||||||
|
]
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='sendmail_rules')
|
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='sendmail_rules')
|
||||||
|
|
||||||
@@ -219,6 +239,15 @@ class Rule(models.Model, LoggingMixin):
|
|||||||
default=['p', 'n__valid_if_pending'],
|
default=['p', 'n__valid_if_pending'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
checked_in_status = models.CharField(
|
||||||
|
verbose_name=_("Restrict to check-in status"),
|
||||||
|
default=None,
|
||||||
|
choices=CHECK_IN_STATUS_CHOICES,
|
||||||
|
max_length=10,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
attach_ical = models.BooleanField(
|
attach_ical = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Attach calendar files"),
|
verbose_name=_("Attach calendar files"),
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
<legend>{% trans "Recipients" %}</legend>
|
<legend>{% trans "Recipients" %}</legend>
|
||||||
{% bootstrap_field form.send_to layout='control' %}
|
{% bootstrap_field form.send_to layout='control' %}
|
||||||
{% bootstrap_field form.restrict_to_status layout='control' %}
|
{% bootstrap_field form.restrict_to_status layout='control' %}
|
||||||
|
{% bootstrap_field form.checked_in_status layout='control' %}
|
||||||
|
<hr>
|
||||||
{% bootstrap_field form.all_products layout='control' %}
|
{% bootstrap_field form.all_products layout='control' %}
|
||||||
{% bootstrap_field form.limit_products layout='horizontal' %}
|
{% bootstrap_field form.limit_products layout='horizontal' %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
<legend>{% trans "Recipients" %}</legend>
|
<legend>{% trans "Recipients" %}</legend>
|
||||||
{% bootstrap_field form.send_to layout='control' %}
|
{% bootstrap_field form.send_to layout='control' %}
|
||||||
{% bootstrap_field form.restrict_to_status layout='control' %}
|
{% bootstrap_field form.restrict_to_status layout='control' %}
|
||||||
|
{% bootstrap_field form.checked_in_status layout='control' %}
|
||||||
|
<hr>
|
||||||
{% bootstrap_field form.all_products layout='control' %}
|
{% bootstrap_field form.all_products layout='control' %}
|
||||||
{% bootstrap_field form.limit_products layout='horizontal' %}
|
{% bootstrap_field form.limit_products layout='horizontal' %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -39,10 +39,13 @@ from decimal import Decimal
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.cache import caches
|
||||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||||
from django.core.signing import BadSignature, loads
|
from django.core.signing import BadSignature, loads
|
||||||
from django.core.validators import EmailValidator
|
from django.core.validators import EmailValidator
|
||||||
from django.db.models import F, Q
|
from django.db import models
|
||||||
|
from django.db.models import Count, F, Q, Sum
|
||||||
|
from django.db.models.functions import Cast
|
||||||
from django.http import HttpResponseNotAllowed, JsonResponse
|
from django.http import HttpResponseNotAllowed, JsonResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
@@ -62,12 +65,14 @@ from pretix.base.services.cart import (
|
|||||||
)
|
)
|
||||||
from pretix.base.services.memberships import validate_memberships_in_order
|
from pretix.base.services.memberships import validate_memberships_in_order
|
||||||
from pretix.base.services.orders import perform_order
|
from pretix.base.services.orders import perform_order
|
||||||
|
from pretix.base.services.tasks import EventTask
|
||||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||||
from pretix.base.signals import validate_cart_addons
|
from pretix.base.signals import validate_cart_addons
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
from pretix.base.templatetags.phone_format import phone_format
|
from pretix.base.templatetags.phone_format import phone_format
|
||||||
from pretix.base.templatetags.rich_text import rich_text_snippet
|
from pretix.base.templatetags.rich_text import rich_text_snippet
|
||||||
from pretix.base.views.tasks import AsyncAction
|
from pretix.base.views.tasks import AsyncAction
|
||||||
|
from pretix.celery_app import app
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
from pretix.presale.forms.checkout import (
|
from pretix.presale.forms.checkout import (
|
||||||
ContactForm, InvoiceAddressForm, InvoiceNameForm, MembershipForm,
|
ContactForm, InvoiceAddressForm, InvoiceNameForm, MembershipForm,
|
||||||
@@ -802,7 +807,9 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
@cached_property
|
@cached_property
|
||||||
def invoice_form(self):
|
def invoice_form(self):
|
||||||
wd = self.cart_session.get('widget_data', {})
|
wd = self.cart_session.get('widget_data', {})
|
||||||
if not self.invoice_address.pk:
|
if self.invoice_address.pk:
|
||||||
|
wd_initial = {}
|
||||||
|
elif wd:
|
||||||
wd_initial = {
|
wd_initial = {
|
||||||
'name_parts': {
|
'name_parts': {
|
||||||
k[21:].replace('-', '_'): v
|
k[21:].replace('-', '_'): v
|
||||||
@@ -817,7 +824,9 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
'country': wd.get('invoice-address-country', ''),
|
'country': wd.get('invoice-address-country', ''),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
wd_initial = {}
|
wd_initial = {
|
||||||
|
'is_business': self._get_is_business_heuristic(),
|
||||||
|
}
|
||||||
initial = dict(wd_initial)
|
initial = dict(wd_initial)
|
||||||
|
|
||||||
if self.cart_customer:
|
if self.cart_customer:
|
||||||
@@ -1026,6 +1035,25 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
ctx['cart_session'] = self.cart_session
|
ctx['cart_session'] = self.cart_session
|
||||||
ctx['invoice_address_asked'] = self.address_asked
|
ctx['invoice_address_asked'] = self.address_asked
|
||||||
|
|
||||||
|
def reduce_initial(v):
|
||||||
|
if isinstance(v, dict):
|
||||||
|
# try to flatten objects such as name_parts to a single string to determine whether they have any value set
|
||||||
|
return ''.join([v for k, v in v.items() if not k.startswith('_') and v])
|
||||||
|
else:
|
||||||
|
return v
|
||||||
|
|
||||||
|
def is_form_filled(form, ignore_keys=()):
|
||||||
|
return any([reduce_initial(v) for k, v in form.initial.items() if k not in ignore_keys])
|
||||||
|
|
||||||
|
ctx['invoice_address_open'] = (
|
||||||
|
self.request.event.settings.invoice_address_required or
|
||||||
|
self.request.event.settings.invoice_name_required or
|
||||||
|
'invoice' in self.request.GET or
|
||||||
|
# Checking for self.invoice_address.pk is not enough as when an invoice_address has been added and later edited to be empty, it’s not None.
|
||||||
|
# So check initial values as invoice_form can receive pre-filled values from invoice_address, widget-data or overwrites from plug-ins.
|
||||||
|
is_form_filled(self.invoice_form, ignore_keys=('is_business', 'country'))
|
||||||
|
)
|
||||||
|
|
||||||
if self.cart_customer:
|
if self.cart_customer:
|
||||||
if self.address_asked:
|
if self.address_asked:
|
||||||
addresses = self.cart_customer.stored_addresses.all()
|
addresses = self.cart_customer.stored_addresses.all()
|
||||||
@@ -1114,6 +1142,31 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
ctx['profiles_data'] = profiles_list
|
ctx['profiles_data'] = profiles_list
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def _get_is_business_heuristic(self):
|
||||||
|
key = 'checkout_heuristic_is_business:' + str(self.event.pk)
|
||||||
|
cached_result = caches['default'].get(key)
|
||||||
|
if cached_result is None:
|
||||||
|
if caches['default'].add(key, False, timeout=10): # return False while query is running
|
||||||
|
QuestionsStep._update_is_business_heuristic.apply_async(args=(self.event.pk,))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return cached_result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@app.task(base=EventTask)
|
||||||
|
def _update_is_business_heuristic(event):
|
||||||
|
result = InvoiceAddress.objects.filter(order__event=event).aggregate(
|
||||||
|
total=Count('*'), business=Sum(Cast('is_business', output_field=models.IntegerField())))
|
||||||
|
if result['total'] < 100:
|
||||||
|
result = InvoiceAddress.objects.filter(order__event__organizer=event.organizer).aggregate(
|
||||||
|
total=Count('*'), business=Sum(Cast('is_business', output_field=models.IntegerField())))
|
||||||
|
if result['business'] and result['total']:
|
||||||
|
is_business = result['business'] / result['total'] >= 0.6
|
||||||
|
else:
|
||||||
|
is_business = False
|
||||||
|
key = 'checkout_heuristic_is_business:' + str(event.pk)
|
||||||
|
caches['default'].set(key, is_business, timeout=12 * 3600) # 12 hours
|
||||||
|
|
||||||
|
|
||||||
class PaymentStep(CartMixin, TemplateFlowStep):
|
class PaymentStep(CartMixin, TemplateFlowStep):
|
||||||
priority = 200
|
priority = 200
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{% trans "Invoice information" %}
|
{% trans "Invoice information" %}
|
||||||
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1" aria-label="{% trans "Modify invoice information" %}" class="h6">
|
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1#invoice-details" aria-label="{% trans "Modify invoice information" %}" class="h6">
|
||||||
<span class="fa fa-edit" aria-hidden="true"></span>{% trans "Modify" %}
|
<span class="fa fa-edit" aria-hidden="true"></span>{% trans "Modify" %}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{% if invoice_address_asked %}
|
{% if invoice_address_asked %}
|
||||||
<details class="panel panel-default" {% if event.settings.invoice_address_required or event.settings.invoice_name_required %}open{% endif %}>
|
<details class="panel panel-default" {% if invoice_address_open %}open{% endif %} id="invoice-details">
|
||||||
<summary class="panel-heading">
|
<summary class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
<strong>{% trans "Invoice information" %}{% if not event.settings.invoice_address_required and not event.settings.invoice_name_required %}
|
<strong>{% trans "Invoice information" %}{% if not event.settings.invoice_address_required and not event.settings.invoice_name_required %}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load eventurl %}
|
||||||
|
{% if ev.location and show_location %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="fa fa-map-marker fa-fw" aria-hidden="true" title="{% trans "Where does the event happen?" %}"></span>
|
||||||
|
<p><span class="sr-only">{% trans "Where does the event happen?" %}</span>
|
||||||
|
{{ ev.location|linebreaksbr }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ev.settings.show_dates_on_frontpage %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="fa fa-clock-o fa-fw" aria-hidden="true" title="{% trans "When does the event happen?" %}"></span>
|
||||||
|
<p><span class="sr-only">{% trans "When does the event happen?" %}</span>
|
||||||
|
{{ ev.get_date_range_display_as_html }}
|
||||||
|
{% if event.settings.show_times %}
|
||||||
|
<br>
|
||||||
|
<span data-time="{{ ev.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
|
{% with time_human=ev.date_from|date:"TIME_FORMAT" time_24=ev.date_from|time:"H:i" %}
|
||||||
|
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
||||||
|
Begin: {{ time }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
</span>
|
||||||
|
{% if event.settings.show_date_to and ev.date_to %}
|
||||||
|
<br>
|
||||||
|
<span data-time="{{ ev.date_to.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
|
{% with time_human=ev.date_to|date:"TIME_FORMAT" time_24=ev.date_to|time:"H:i" %}
|
||||||
|
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
||||||
|
End: {{ time }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if ev.date_admission %}
|
||||||
|
<br>
|
||||||
|
{% if ev.date_admission|date:"SHORT_DATE_FORMAT" == ev.date_from|date:"SHORT_DATE_FORMAT" %}
|
||||||
|
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
|
{% with time_human=ev.date_admission|date:"TIME_FORMAT" time_24=ev.date_admission|time:"H:i" %}
|
||||||
|
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
||||||
|
Admission: {{ time }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||||
|
{% with datetime_human=ev.date_admission|date:"SHORT_DATETIME_FORMAT" datetime_iso=ev.date_admission|time:"Y-m-d H:i" %}
|
||||||
|
{% blocktrans trimmed with datetime='<time datetime="'|add:datetime_iso|add:'">'|add:datetime_human|add:"</time>"|safe %}
|
||||||
|
Admission: {{ datetime }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% if subevent %}
|
||||||
|
<a href="{% eventurl event "presale:event.ical.download" subevent=subevent.pk %}">
|
||||||
|
{% else %}
|
||||||
|
<a href="{% eventurl event "presale:event.ical.download" %}">
|
||||||
|
{% endif %}
|
||||||
|
{% trans "Add to Calendar" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -162,73 +162,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not cart_namespace or subevent %}
|
{% if not cart_namespace or subevent %}
|
||||||
<div>
|
<div>
|
||||||
{% if ev.location %}
|
{% include "pretixpresale/event/fragment_event_info.html" with event=request.event subevent=subevent ev=ev show_location=True %}
|
||||||
<div class="info-row">
|
|
||||||
<span class="fa fa-map-marker fa-fw" aria-hidden="true" title="{% trans "Where does the event happen?" %}"></span>
|
|
||||||
<p><span class="sr-only">{% trans "Where does the event happen?" %}</span>
|
|
||||||
{{ ev.location|linebreaksbr }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if ev.settings.show_dates_on_frontpage %}
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="fa fa-clock-o fa-fw" aria-hidden="true" title="{% trans "When does the event happen?" %}"></span>
|
|
||||||
<p><span class="sr-only">{% trans "When does the event happen?" %}</span>
|
|
||||||
{{ ev.get_date_range_display_as_html }}
|
|
||||||
{% if event.settings.show_times %}
|
|
||||||
<br>
|
|
||||||
<span data-time="{{ ev.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
|
||||||
{% with time_human=ev.date_from|date:"TIME_FORMAT" time_24=ev.date_from|time:"H:i" %}
|
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
|
||||||
Begin: {{ time }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
</span>
|
|
||||||
{% if event.settings.show_date_to and ev.date_to %}
|
|
||||||
<br>
|
|
||||||
<span data-time="{{ ev.date_to.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
|
||||||
{% with time_human=ev.date_to|date:"TIME_FORMAT" time_24=ev.date_to|time:"H:i" %}
|
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
|
||||||
End: {{ time }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if ev.date_admission %}
|
|
||||||
<br>
|
|
||||||
{% if ev.date_admission|date:"SHORT_DATE_FORMAT" == ev.date_from|date:"SHORT_DATE_FORMAT" %}
|
|
||||||
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
|
||||||
{% with time_human=ev.date_admission|date:"TIME_FORMAT" time_24=ev.date_admission|time:"H:i" %}
|
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
|
||||||
Admission: {{ time }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<span data-time="{{ ev.date_admission.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
|
||||||
{% with datetime_human=ev.date_admission|date:"SHORT_DATETIME_FORMAT" datetime_iso=ev.date_admission|time:"Y-m-d H:i" %}
|
|
||||||
{% blocktrans trimmed with datetime='<time datetime="'|add:datetime_iso|add:'">'|add:datetime_human|add:"</time>"|safe %}
|
|
||||||
Admission: {{ datetime }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if subevent %}
|
|
||||||
<a href="{% eventurl event "presale:event.ical.download" subevent=subevent.pk %}">
|
|
||||||
{% else %}
|
|
||||||
<a href="{% eventurl event "presale:event.ical.download" %}">
|
|
||||||
{% endif %}
|
|
||||||
{% trans "Add to Calendar" %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% eventsignal event "pretix.presale.signals.front_page_top" request=request subevent=subevent %}
|
{% eventsignal event "pretix.presale.signals.front_page_top" request=request subevent=subevent %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -13,63 +13,28 @@
|
|||||||
{% include "pretixpresale/event/fragment_cart_box.html" with open=request.GET.show_cart %}
|
{% include "pretixpresale/event/fragment_cart_box.html" with open=request.GET.show_cart %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2>{% trans "Voucher redemption" %}</h2>
|
|
||||||
|
|
||||||
{% if subevent %}
|
{% if subevent %}
|
||||||
|
<h2>{% trans "Voucher redemption" %}</h2>
|
||||||
{% if request.GET.subevent and subevent.pk|stringformat:"i" != request.GET.subevent %}
|
{% if request.GET.subevent and subevent.pk|stringformat:"i" != request.GET.subevent %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
{% trans "This voucher is valid only for the following specific date and time." %}
|
{% trans "This voucher is valid only for the following specific date and time." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3>{{ subevent.name }}</h3>
|
<h3>{{ subevent.name }}</h3>
|
||||||
{% with ev=subevent %}
|
{% include "pretixpresale/event/fragment_event_info.html" with event=request.event subevent=subevent ev=subevent show_location=True %}
|
||||||
<div class="info-row">
|
{% else %}
|
||||||
<span class="fa fa-clock-o fa-fw" aria-hidden="true"></span>
|
{% if event_logo and event_logo_show_title %}
|
||||||
<p>
|
<h2 class="content-header">
|
||||||
{{ ev.get_date_range_display_as_html }}
|
{{ event.name }}
|
||||||
{% if event.settings.show_times %}
|
{% if request.event.settings.show_dates_on_frontpage %}
|
||||||
<br>
|
<small>{{ event.get_date_range_display_as_html }}</small>
|
||||||
{% with time_human=ev.date_from|date:"TIME_FORMAT" time_24=ev.date_from|time:"H:i" %}
|
{% endif %}
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
</h2>
|
||||||
Begin: {{ time }}
|
{% include "pretixpresale/event/fragment_event_info.html" with event=request.event subevent=None ev=request.event show_location=True %}
|
||||||
{% endblocktrans %}
|
<h3>{% trans "Voucher redemption" %}</h3>
|
||||||
{% endwith %}
|
{% else %}
|
||||||
{% if event.settings.show_date_to and ev.date_to %}
|
<h2>{% trans "Voucher redemption" %}</h2>
|
||||||
<br>
|
{% endif %}
|
||||||
{% with time_human=ev.date_to|date:"TIME_FORMAT" time_24=ev.date_to|time:"H:i" %}
|
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
|
||||||
End: {{ time }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if ev.date_admission %}
|
|
||||||
<br>
|
|
||||||
{% if ev.date_admission|date:"SHORT_DATE_FORMAT" == ev.date_from|date:"SHORT_DATE_FORMAT" %}
|
|
||||||
{% with time_human=ev.date_admission|date:"TIME_FORMAT" time_24=ev.date_admission|time:"H:i" %}
|
|
||||||
{% blocktrans trimmed with time='<time datetime="'|add:time_24|add:'">'|add:time_human|add:"</time>"|safe %}
|
|
||||||
Admission: {{ time }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
{% with datetime_human=ev.date_admission|date:"SHORT_DATETIME_FORMAT" datetime_iso=ev.date_admission|time:"Y-m-d H:i" %}
|
|
||||||
{% blocktrans trimmed with datetime='<time datetime="'|add:datetime_iso|add:'">'|add:datetime_human|add:"</time>"|safe %}
|
|
||||||
Admission: {{ datetime }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if subevent %}
|
|
||||||
<a href="{% eventurl event "presale:event.ical.download" subevent=subevent.pk %}">
|
|
||||||
{% else %}
|
|
||||||
<a href="{% eventurl event "presale:event.ical.download" %}">
|
|
||||||
{% endif %}
|
|
||||||
{% trans "Add to Calendar" %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -496,7 +496,12 @@ class OrderPaymentConfirm(EventViewMixin, OrderDetailMixin, TemplateView):
|
|||||||
ctx['order'] = self.order
|
ctx['order'] = self.order
|
||||||
ctx['payment'] = self.payment
|
ctx['payment'] = self.payment
|
||||||
if 'order' in inspect.signature(self.payment.payment_provider.checkout_confirm_render).parameters:
|
if 'order' in inspect.signature(self.payment.payment_provider.checkout_confirm_render).parameters:
|
||||||
ctx['payment_info'] = self.payment.payment_provider.checkout_confirm_render(self.request, order=self.order)
|
if 'info_data' in inspect.signature(self.payment.payment_provider.checkout_confirm_render).parameters:
|
||||||
|
ctx['payment_info'] = self.payment.payment_provider.checkout_confirm_render(
|
||||||
|
self.request, order=self.order, info_data=self.payment.info_data
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ctx['payment_info'] = self.payment.payment_provider.checkout_confirm_render(self.request, order=self.order)
|
||||||
else:
|
else:
|
||||||
ctx['payment_info'] = self.payment.payment_provider.checkout_confirm_render(self.request)
|
ctx['payment_info'] = self.payment.payment_provider.checkout_confirm_render(self.request)
|
||||||
ctx['payment_provider'] = self.payment.payment_provider
|
ctx['payment_provider'] = self.payment.payment_provider
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user