Compare commits

..

47 Commits

Author SHA1 Message Date
Raphael Michel
8957ed929a Update src/pretix/base/channels.py 2023-11-09 10:09:07 +01:00
Richard Schreiber
a24a36b1e7 sort asc, force web first 2023-11-08 12:29:18 +01:00
Richard Schreiber
13ee2b3c5d Control: sort sales-channels desc 2023-11-08 12:27:26 +01:00
Zona Vip
9b77403796 Translations: Update Spanish
Currently translated at 81.0% (175 of 216 strings)

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

powered by weblate
2023-11-07 15:39:11 +01:00
Zona Vip
c93c2fb6e8 Translations: Update Spanish
Currently translated at 60.1% (3284 of 5461 strings)

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

powered by weblate
2023-11-07 15:39:11 +01:00
Raphael Michel
5800babdab Event list: Add date filter 2023-11-07 09:40:39 +01:00
Raphael Michel
5e3600ac44 Fix missing form option 2023-11-06 18:37:26 +01:00
Raphael Michel
3af2342d7b Replace Item.hidden_if_available with relationship to other Item (#3686)
* draft

* Implementation that is closer to old one

* Fix tests

* Add tests

* Update src/pretix/control/forms/item.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Review notes

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-11-06 13:26:32 +01:00
Raphael Michel
3d68c83907 Improve check-in deletion (#3688)
* Improve check-in deletion

* Add pluralization

* Disable buttons if nothing selected
2023-11-06 10:30:02 +01:00
dependabot[bot]
6366df2c24 Bump vue and vue-template-compiler in /src/pretix/static/npm_dir (#3679)
Bumps [vue](https://github.com/vuejs/core) and [vue-template-compiler](https://github.com/vuejs/vue). These dependencies needed to be updated together.

Updates `vue` from 2.7.14 to 2.7.15
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits)

Updates `vue-template-compiler` from 2.7.14 to 2.7.15
- [Release notes](https://github.com/vuejs/vue/releases)
- [Changelog](https://github.com/vuejs/vue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue/compare/v2.7.14...v2.7.15)

---
updated-dependencies:
- dependency-name: vue
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: vue-template-compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 10:25:13 +01:00
Fast128
05ca336c1b Translations: Update Polish
Currently translated at 15.9% (869 of 5461 strings)

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

powered by weblate
2023-11-06 10:07:40 +01:00
Zona Vip
f643102696 Translations: Update Spanish
Currently translated at 60.0% (3277 of 5461 strings)

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

powered by weblate
2023-11-06 10:07:40 +01:00
Jāzeps Benjamins Baško
33ef50daea Translations: Update Latvian
Currently translated at 39.4% (2153 of 5461 strings)

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

powered by weblate
2023-11-06 10:07:40 +01:00
Raphael Michel
8071207bf3 Order change: Allow price reduction as long as no refund is required (Z#23135268) (#3689)
* Order change: Allow price reduction as long as no refund is required

* Update src/pretix/base/settings.py

Co-authored-by: Richard Schreiber <schreiber@rami.io>

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2023-11-06 10:07:21 +01:00
Martin Gross
767bb27175 PayPal: Add visibility EventListener for onApprove (Z#23135203) (#3687) 2023-11-03 15:18:53 +01:00
Raphael Michel
b7f240faf0 Emphasize risk of deleting check-in lists 2023-11-03 13:46:35 +01:00
Raphael Michel
e0e2b2d7f7 Allow hidden payment methods on payment method change (#3682)
* Allow hidden payment methods on payment method change

* Save hashes to meta data
2023-11-03 13:42:34 +01:00
Raphael Michel
10b515f1d1 Fix incorrect import 2023-11-03 13:18:36 +01:00
dependabot[bot]
d81c05c444 Bump @rollup/plugin-node-resolve from 15.2.1 to 15.2.3 in /src/pretix/static/npm_dir (#3680)
Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 15.2.1 to 15.2.3.
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/node-resolve-v15.2.3/packages/node-resolve)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-node-resolve"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-03 13:12:57 +01:00
Raphael Michel
bc0a205f03 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5461 of 5461 strings)

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

powered by weblate
2023-11-03 13:06:29 +01:00
Raphael Michel
0e53ddc83b Translations: Update German
Currently translated at 100.0% (5461 of 5461 strings)

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

powered by weblate
2023-11-03 13:06:29 +01:00
Raphael Michel
0400b577bb Badges: Create templates for common paper sizes (#3660)
* Badges: Create templates for common paper sizes

* Add more sizes

* format lazy
2023-11-03 12:37:20 +01:00
Raphael Michel
ec2085f125 Docs: Fix change version number 2023-11-02 21:32:28 +01:00
Raphael Michel
6430427e3a Docs: Fix change version number 2023-11-02 21:32:18 +01:00
Raphael Michel
38a1b6a417 Ignore deprecation warning in compressor 2023-11-02 19:57:23 +01:00
dependabot[bot]
764d7a2f1c Bump @rollup/plugin-babel from 6.0.3 to 6.0.4 in /src/pretix/static/npm_dir (#3677)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 19:40:43 +01:00
dependabot[bot]
e65564234f Bump @babel/core from 7.23.0 to 7.23.2 in /src/pretix/static/npm_dir (#3678)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 19:40:35 +01:00
Raphael Michel
1ec18eb44a Payment method change: Hide explanation if there are no fees 2023-11-02 14:33:26 +01:00
Raphael Michel
4c51c28d7a Order change: Emphasize warning on payment state (Z#23135268) 2023-11-02 14:23:42 +01:00
fyksen
eaa134089e Translations: Update Norwegian Bokmål
Currently translated at 99.5% (215 of 216 strings)

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

powered by weblate
2023-11-02 14:07:01 +01:00
fyksen
a015c9ca2a Translations: Update Norwegian Bokmål
Currently translated at 94.7% (5173 of 5461 strings)

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

powered by weblate
2023-11-02 14:07:01 +01:00
Raphael Michel
d8ca865fc6 Email: Fix color for organizer-level emails 2023-11-02 11:29:27 +01:00
Raphael Michel
987b02d733 Add Norwegian Bokmål language 2023-11-02 10:14:58 +01:00
fyksen
3dd8d4349d Translations: Update Norwegian Bokmål
Currently translated at 93.9% (203 of 216 strings)

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

powered by weblate
2023-11-02 10:13:55 +01:00
fyksen
fd43908e4c Translations: Update Norwegian Bokmål
Currently translated at 6.3% (346 of 5461 strings)

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

powered by weblate
2023-11-02 10:13:55 +01:00
fyksen
257ed8ebc3 Translations: Update Norwegian Bokmål
Currently translated at 6.2% (339 of 5461 strings)

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

powered by weblate
2023-11-02 10:13:55 +01:00
Jāzeps Benjamins Baško
dfd8bf1c0f Translations: Update Latvian
Currently translated at 38.8% (2124 of 5461 strings)

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

powered by weblate
2023-11-02 10:13:55 +01:00
Raphael Michel
0f709c2275 Device API: Fix RSA encryption crash (PRETIXEU-8Y2) 2023-10-31 10:23:14 +01:00
Raphael Michel
3b64e6046c API: Add endpoints for scheduled exports (#3659)
* API: Add endpoints for scheduled exports

* ADd note to docs
2023-10-27 17:15:53 +02:00
Raphael Michel
26cbc24a10 Waiting list: Use a deterministic order 2023-10-27 17:01:31 +02:00
Martin Gross
33a5479809 Add KulturPass documentation (#3671)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Raphael Michel <michel@rami.io>
2023-10-27 15:32:07 +02:00
Raphael Michel
000c64755d Free price: Allow to suggest a different price than the minimum (#3666)
* Free price: Allow to suggest a different price than the minimum

* Full implementation

* Widget tests

* Add min values to titles
2023-10-27 13:36:01 +02:00
Raphael Michel
b32249d48b Make redirections after voucher redemption more consistent 2023-10-27 11:47:22 +02:00
Raphael Michel
c325cc1120 Fix crash in gift card detail view (PRETIXEU-97N) 2023-10-27 10:46:05 +02:00
Raphael Michel
8d2791b32e Fix crash in event creation form (PRETIXEU-97T) 2023-10-27 10:43:46 +02:00
Raphael Michel
466fc15382 Scheduled exports: Fix handling of datetime fields 2023-10-27 10:39:46 +02:00
Raphael Michel
86cf3be225 Bump version to 2023.10.0.dev0 2023-10-27 10:05:08 +02:00
76 changed files with 9716 additions and 4586 deletions

View File

@@ -1,5 +1,7 @@
.. spelling:word-list:: checkin
.. _rest-exporters:
Data exporters
==============

View File

@@ -40,6 +40,7 @@ at :ref:`plugin-docs`.
webhooks
seatingplans
exporters
scheduled_exports
shredders
sendmail_rules
billing_invoices

View File

@@ -18,6 +18,8 @@ default_price money (string) The price set d
price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price`` (read-only).
free_price_suggestion money (string) A suggested price, used as a default value if
``Item.free_price`` is set (or ``null``).
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
active boolean If ``false``, this variation will not be sold or shown.
@@ -53,6 +55,10 @@ meta_data object Values set for
The ``meta_data`` and ``checkin_attention`` attributes have been added.
.. versionchanged:: 2023.10
The ``free_price_suggestion`` attribute has been added.
Endpoints
---------
@@ -103,6 +109,7 @@ Endpoints
"default_price": "223.00",
"price": 223.0,
"original_price": null,
"free_price_suggestion": null,
"meta_data": {}
},
{
@@ -116,10 +123,16 @@ Endpoints
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
"available_until": null,
"hide_without_voucher": false,
"description": {},
"position": 1,
"default_price": null,
"price": 15.0,
"default_price": "223.00",
"price": 223.0,
"original_price": null,
"free_price_suggestion": null,
"meta_data": {}
}
]
@@ -163,6 +176,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -231,6 +245,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -291,6 +306,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": false,
"checkin_attention": false,
"require_approval": false,

View File

@@ -29,6 +29,8 @@ free_price boolean If ``true``,
they buy the product (however, the price can't be set
lower than the price defined by ``default_price`` or
otherwise).
free_price_suggestion money (string) A suggested price, used as a default value if
``free_price`` is set (or ``null``).
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
set through ``tax_rule``).
tax_rule integer The internal ID of the applied tax rule (or ``null``).
@@ -50,9 +52,12 @@ available_from datetime The first dat
(or ``null``).
available_until datetime The last date time at which this item can be bought
(or ``null``).
hidden_if_available integer The internal ID of a quota object, or ``null``. If
hidden_if_available integer **DEPRECATED** The internal ID of a quota object, or ``null``. If
set, this item won't be shown publicly as long as this
quota is available.
hidden_if_item_available integer The internal ID of a different item, or ``null``. If
set, this item won't be shown publicly as long as this
other item is available.
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
@@ -123,6 +128,8 @@ variations list of objects A list with o
├ price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price``.
├ free_price_suggestion money (string) A suggested price, used as a default value if
``free_price`` is set (or ``null``).
├ original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown.
@@ -196,6 +203,15 @@ meta_data object Values set fo
The ``media_policy`` and ``media_type`` attributes have been added.
.. versionchanged:: 2023.10
The ``free_price_suggestion`` and ``variations[x].free_price_suggestion`` attributes have been added.
.. versionchanged:: 2023.10
The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been
deprecated.
Notes
-----
@@ -246,6 +262,7 @@ Endpoints
"active": true,
"description": null,
"free_price": false,
"free_price_suggestion": null,
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
@@ -259,6 +276,7 @@ Endpoints
"available_from": null,
"available_until": null,
"hidden_if_available": null,
"hidden_if_item_available": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -291,6 +309,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -309,6 +328,7 @@ Endpoints
"default_price": null,
"price": "23.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -377,6 +397,7 @@ Endpoints
"active": true,
"description": null,
"free_price": false,
"free_price_suggestion": null,
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
@@ -390,6 +411,7 @@ Endpoints
"available_from": null,
"available_until": null,
"hidden_if_available": null,
"hidden_if_item_available": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -422,6 +444,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -440,6 +463,7 @@ Endpoints
"default_price": null,
"price": "23.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -489,6 +513,7 @@ Endpoints
"active": true,
"description": null,
"free_price": false,
"free_price_suggestion": null,
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
@@ -502,6 +527,7 @@ Endpoints
"available_from": null,
"available_until": null,
"hidden_if_available": null,
"hidden_if_item_available": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -533,6 +559,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -551,6 +578,7 @@ Endpoints
"default_price": null,
"price": "23.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -588,6 +616,7 @@ Endpoints
"active": true,
"description": null,
"free_price": false,
"free_price_suggestion": null,
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
@@ -601,6 +630,7 @@ Endpoints
"available_from": null,
"available_until": null,
"hidden_if_available": null,
"hidden_if_item_available": null,
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -633,6 +663,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -651,6 +682,7 @@ Endpoints
"default_price": null,
"price": "23.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -719,6 +751,7 @@ Endpoints
"active": true,
"description": null,
"free_price": false,
"free_price_suggestion": null,
"tax_rate": "0.00",
"tax_rule": 1,
"admission": false,
@@ -732,6 +765,7 @@ Endpoints
"available_from": null,
"available_until": null,
"hidden_if_available": null,
"hidden_if_item_available": null,
"require_voucher": false,
"hide_without_voucher": false,
"generate_tickets": null,
@@ -764,6 +798,7 @@ Endpoints
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,
@@ -782,6 +817,7 @@ Endpoints
"default_price": null,
"price": "23.00",
"original_price": null,
"free_price_suggestion": null,
"active": true,
"checkin_attention": false,
"require_approval": false,

View File

@@ -348,7 +348,7 @@ Endpoints
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/ HTTP/1.1
PATCH /api/v1/organizers/bigevents/events/sampleconf/questions/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
@@ -415,7 +415,7 @@ Endpoints
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the question to modify
:statuscode 200: no error
:statuscode 400: The item could not be modified due to invalid submitted data
:statuscode 400: The question could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
@@ -427,7 +427,7 @@ Endpoints
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/ HTTP/1.1
DELETE /api/v1/organizers/bigevents/events/sampleconf/questions/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
@@ -440,7 +440,7 @@ Endpoints
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the item to delete
:param id: The ``id`` field of the question to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.

View File

@@ -0,0 +1,556 @@
.. spelling:word-list:: checkin
Scheduled data exports
======================
pretix and it's plugins include a number of data exporters that allow you to bulk download various data from pretix in
different formats. You should read :ref:`rest-exporters` first to get an understanding of the basic mechanism.
Exports can be scheduled to be sent at specific times automatically, both on organizer level and event level.
Scheduled export resource
-------------------------
The scheduled export contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the schedule
owner string Email address of the user who created this schedule (read-only).
This address will always receive the export and the export
will only contain data that this user has permission
to access at the time of the export. **We consider this
field experimental, it's behaviour might change in the future.
Note that the email address of a user can change at any time.**
export_identifier string Identifier of the export to run, see :ref:`rest-exporters`
export_form_data object Input data for the export, format depends on the export,
see :ref:`rest-exporters` for more details.
locale string Language to run the export in
mail_additional_recipients string Email addresses to receive the export, comma-separated (or empty string)
mail_additional_recipients_cc string Email addresses to receive the export in copy, comma-separated (or empty string)
mail_additional_recipients_bcc string Email addresses to receive the exportin blind copy, comma-separated (or empty string)
mail_subject string Subject to use for the email (currently no variables supported)
mail_template string Text to use for the email (currently no variables supported)
schedule_rrule string Recurrence specification to determine the **days** this
schedule runs on in ``RRULE`` syntax following `RFC 5545`_
with some restrictions. Only one rule is allowed, only
one occurrence per day is allowed, and some features
are not supported (``BYMONTHDAY``, ``BYYEARDAY``,
``BYEASTER``, ``BYWEEKNO``).
schedule_rrule_time time Time of day to run this on on the specified days.
Will be interpreted as local time of the event for event-level
exports. For organizer-level exports, the timezone is given
in the field ``timezone``. The export will never run **before**
this time but it **may** run **later**.
timezone string Time zone to interpret the schedule in (only for organizer-level exports)
schedule_next_run datetime Next planned execution (read-only, computed by server)
error_counter integer Number of consecutive times this export failed (read-only).
After a number of failures (currently 5), the schedule no
longer is executed. Changing parameters resets the value.
===================================== ========================== =======================================================
Special notes on permissions
----------------------------
Permission handling for scheduled exports is more complex than for most other objects. The reason for this is that
there are two levels of access control involved here: First, you need permission to access or change the configuration
of the scheduled exports in the moment you are doing it. Second, you **continuously** need permission to access the
**data** that is exported as part of the schedule. For this reason, scheduled exports always need one user account
to be their **owner**.
Therefore, scheduled exports **must** be created by an API client using :ref:`OAuth authentication <rest-oauth>`.
It is impossible to create a scheduled export using token authentication. After the export is created, it can also be
modified using token authentication.
A user or token with the "can change settings" permission for a given organizer or event can see and change
**all** scheduled exports created for the respective organizer or event, regardless of who created them.
A user without this permission can only see **their own** scheduled exports.
A token without this permission can not see scheduled exports as all.
Endpoints for event exports
---------------------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/scheduled_exports/
Returns a list of all scheduled exports the client has access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/scheduled_exports/ 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": [
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"error_counter": 0
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``export_identifier``, and ``schedule_next_run``.
Default: ``id``
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event 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.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/scheduled_exports/(id)/
Returns information on one scheduled export, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/scheduled_exports/1/ 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
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the scheduled export 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.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/scheduled_exports/
Schedule a new export.
.. note:: See above for special notes on permissions.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/scheduled_exports/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer of the event to create an item for
:param event: The ``slug`` field of the event to create an item for
:statuscode 201: no error
:statuscode 400: The item could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/scheduled_exports/(id)/
Update a scheduled export. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/scheduled_exports/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"export_form_data": {"_format": "xlsx", "date_range": "week_this"},
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_this"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the export to modify
:statuscode 200: no error
:statuscode 400: The export could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/scheduled_exports/(id)/
Delete a scheduled export.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/scheduled_exports/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the export to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
Endpoints for organizer exports
---------------------------
.. http:get:: /api/v1/organizers/(organizer)/scheduled_exports/
Returns a list of all scheduled exports the client has access to.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/scheduled_exports/ 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": [
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"timezone": "Europe/Berlin",
"error_counter": 0
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``export_identifier``, and ``schedule_next_run``.
Default: ``id``
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/scheduled_exports/(id)/
Returns information on one scheduled export, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/scheduled_exports/1/ 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
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"timezone": "Europe/Berlin",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer to fetch
:param id: The ``id`` field of the scheduled export to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/scheduled_exports/
Schedule a new export.
.. note:: See above for special notes on permissions.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/scheduled_exports/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"timezone": "Europe/Berlin"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_previous"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"timezone": "Europe/Berlin",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer of the event to create an item for
:statuscode 201: no error
:statuscode 400: The item could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/scheduled_exports/(id)/
Update a scheduled export. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/scheduled_exports/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"export_form_data": {"_format": "xlsx", "date_range": "week_this"},
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"owner": "john@example.com",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "week_this"},
"locale": "en",
"mail_additional_recipients": "mary@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Order list",
"mail_template": "Here is last week's order list\n\nCheers\nJohn",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"schedule_next_run": "2023-10-26T02:00:00Z",
"timezone": "Europe/Berlin",
"error_counter": 0
}
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the export to modify
:statuscode 200: no error
:statuscode 400: The export could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/scheduled_exports/(id)/
Delete a scheduled export.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/scheduled_exports/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the export to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.
.. _RFC 5545: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3

View File

@@ -24,3 +24,4 @@ If you want to **create** a plugin, please go to the
imported_secrets
webinar
presale-saml
kulturpass

193
doc/plugins/kulturpass.rst Normal file
View File

@@ -0,0 +1,193 @@
KulturPass
=========
.. note::
Since the KulturPass is specific to event organizers within Germany, the following page is also only provided in
German. Should you require assistance with the KulturPass and do not speak this language, please feel free reach
out to support@pretix.eu.
Einführung
----------
Der `KulturPass`_ ist ein Angebot der Bundesregierung für alle, die im laufenden Jahr ihren 18. Geburtstag feiern.
Sie erhalten ab ihrem 18. Geburtstag ein Budget von 200 Euro, das sie für Eintrittskarten, Bücher, CDs, Platten und
vieles andere einsetzen können. So wird Kultur vor Ort noch einfacher erlebbar. Gleichzeitig stärkt das die Nachfrage
bei den Anbietenden.
Da pretix ein Ticketing-System ist, stellen wir ausschließlich einen automatisierten Prozess für den Verkauf von
Eintrittskarten über den KulturPass-Marktplatz bereit.
Registrierung und Einrichtung
-----------------------------
Um als Unternehmen oder Kultureinrichtung Angebote auf dem KulturPass-Marktplatz anbieten zu können, ist zunächst eine
Registerung und die Einrichtung eines "Shops" sowie der dazugehörigen Angebote notwendig.
1. Registrierung
Registrieren Sie sich zunächst unter https://www.kulturpass.de/anbietende/layer als Anbieter. Im Zuge der
Registrierung beantworten Sie einige Fragen zu Ihrem Unternehmen/Ihrer Kultureinrichtung, hinterlegen Ihre
E-Mail-Adresse und beantworten Fragen zu Ihren Angebotsformen sowie Finanzierung Ihrer Einrichtung.
2. Anlegen eines KulturPass Shops
Nach Ihrer Registrierung müssen Sie der Weitergabe Ihrer Daten an die technische Platform hinter dem KulturPass,
Mirakl, zustimmen. Hier benennen Sie auch Ihren Shop.
3. Identifizierung mit ELSTER-Zertifikat
Als nächsten Schritt müssen Sie Ihr Unternehmen oder Ihre Einrichtung mit Hilfe eines sog. ELSTER-Zertifikates
identifizieren. Dieses Zertifikat nutzen Sie auch bereits jetzt schon, wenn Sie auf elektronischem Wege mit der
Finanzverwaltung kommunizieren.
4. Ersteinrichtung in pretix
Hinterlegen Sie nun die ID-Nummer Ihres KulturPass Marktplatz-Shops sowie einen API-Key in den
`Einstellungen Ihres Veranstalterkontos`_ (Veranstalter-Konto -> Einstellungen -> KulturPass). Diese Daten müssen
Sie nur einmalig für alle Ihre Veranstaltungen angeben.
Im `KulturPass-Backend`_ finden Sie die benötigten Informationen indem Sie auf das Benutzer-Symbol in der oberen,
rechten Ecke klicken, "Profil" und dann "API Schlüssel" auswählen bzw. indem Sie auf "Einstellungen" in der
Navigation links und dann "Shop" auswählen.
.. note::
Zu jedem Zeitpunkt kann nur ein Hintergrundsystem mit dem KulturPass-System verbunden sein. Werden
unterschiedliche Systeme oder gar mehrere pretix-Veranstalterkonten mit dem gleichen KulturPass-System verbunden,
können keine Bestellungen mehr verarbeitet werden und Angebote nicht automatisiert an den KulturPass-Marktplatz
übermittelt werden. Eingehende Bestellungen von Jugendlichen werden in diesem Fall automatisch abgelehnt, da diese
nicht eindeutig zugeordnet werden können. Ebenso überschreibt die Bereitstellung der Angebote eines Systems die
Angebote eines anderen Systems.
Wenn Sie mehrere Systeme haben, die den KulturPass-Marktplatz bedienen sollen, wenden Sie sich bitte an den
KulturPass-Support, um sich einen weiteren Shop einrichten zu lassen.
5. Aktivierung der KulturPass-Erweiterungen
Alle Veranstaltungen, die Sie über den KulturPass anbieten möchten, benötigen die `KulturPass-Erweiterung`_.
Aktivieren Sie diese bitte in jeder relevanten Veranstaltung über Einstellungen -> Erweiterungen -> Tab
"Integrationen" -> KulturPass.
6. Konfiguration der Artikel
Nachdem die KulturPass-Erweiterung aktiviert wurde, müssen Sie sich entscheiden, welche Produkte Sie über den
KulturPass-Marktplatz anbieten möchten. In der Bearbeitungs-Ansicht des jeweiligen Produktes finden Sie hierzu im
Tab "Zusätzliche Einstellungen" eine Checkbox "Das Produkt kann mit dem KulturPass erworben werden".
.. note::
Die Eigenschaft, dass ein Produkt durch den KulturPass-Marktplatz erworben werden kann, kann für beliebig viele
Produkte aktiviert werden. Auf Grund der Funktionsweise des KulturPasses sollten Sie jedoch gerade bei vielen
Artikeln mit unterschiedlich hohen Preisen darauf achten, dass die Preisspanne nicht zu hoch ausfällt.
Aktivieren Sie die Option für drei Produkte für 1, 10 und 100 Euro, so wird Ihr Angebot im KulturPass-Marktplatz
für 100 Euro gelistet werden. Dies bedeutet im Umkehrschluss auch, dass das KulturPass-Guthaben eines Jugendlichen
auch mindestens 100 Euro betragen muss, damit er Ihr Angebot in Anspruch nehmen kann - auch wenn die betroffene
Person lediglich das 1 Euro-Angebot wahrnehmen möchte. Erst mit dem 100 Euro KulturPass-Einlösecode wählt die
kaufende Person in Ihrem pretix-Shop aus, welches Produkt erworben werden soll. Ein Restguthaben wird nach dem Kauf
automatisch zurückerstattet und dem KulturPass-Konto wieder gutgeschrieben.
7. Konfiguration des Marktplatz-Eintrages
Je nach dem, ob es sich bei Ihrer Veranstaltung um eine Einzelveranstaltung oder eine Veranstaltungsreihe handelt,
müssen Sie die folgende Einstellung einmalig oder pro Veranstaltungstermin vornehmen.
Einzelveranstaltungen konfigurieren Sie über den Menüpunkt "KulturPass" in den Einstellungen Ihrer Veranstaltung;
Veranstaltungsreihen beim Anlegen oder Editieren eines jeden einzelnen Termins am Ende der Seite.
Um eine Veranstaltung oder einen Veranstaltungstermin im KulturPass-Marktplatz anzubieten, aktivieren Sie zunächst
die Option "Diese Veranstaltung via KulturPass anbieten". Geben Sie im folgenden die benötigten Informationen an.
Bitte beachten Sie, dass Sie bei den Angaben präzise Titel und Beschreibungen verwenden, da der KulturPass-
Marktplatz ausschließlich die Informationen aus diesem Bereich verwendet. Etwaige andere Informationen die Sie
bspw. in den "Text auf Startseite"-Felder eingeben haben, erreichen das KulturPass-System nicht.
.. note::
Gerade bei Veranstaltungsreihen nutzen viele pretix-Veranstalter gerne verkürzte Termin-Namen. Ein Schwimmbad würde
beispielsweise Ihre Veranstaltungsreihe "Freibad Musterstadt" und die einzelnen Termine nur "Schwimmen" nennen.
Während dies im pretix-Shop in einem gemeinsamen Kontext wunderbar funktioniert, würde eine Veranstaltung mit dem
Titel "Schwimmen" im KulturPass-Marktplatz Informationen vermissen lassen. Wählen Sie daher für das Eingabefeld
"Veranstaltungstitel" in der KulturPass-Konfiguration einen sprechenden Wert.
8. Übermittlung der Angebote
Sobald Sie Ihre ersten Veranstaltungen konfiguriert und live geschaltet haben, übermittelt pretix automatisch in
regelmäßigen Abständen alle von Ihnen angebotenen Veranstaltungen an das KulturPass System (Mirakl). Bitte beachten
Sie jedoch, dass der Import der Produkte und Angebote einige Zeit in Anspruch nehmen kann. Zum einen müssen
Angebote initial händisch von den Betreibern der KulturPass-Platform freigegeben werden, zum anderen muss auch eine
Synchronisation zwischen dem Hintergrundsystem und der KulturPass-App erfolgen. Auf die Dauer dieser Prozesse hat
pretix keinen Einfluss.
9. Freischalten des Marktplatz-Shops
Nachdem pretix erstmalig Angebote an das KulturPass-System übermittelt hat, müssen Sie Ihren Shop KulturPass-Shop
einmalig freischalten. Loggen Sie sich hierzu in das `KulturPass-Backend`_ ein.
Verwalten von KulturPass-Bestellungen
-------------------------------------
Durch die Nutzung der pretix-Integration mit dem KulturPass-System müssen Sie sich - bis auf die Kennzeichnung von
Produkten, die per KulturPass erworben werden dürfen, sowie die Bereitstellung von Veranstaltungs-Informationen für den
KulturPass-Marktplatz - um nichts kümmern: pretix übermittelt automatisch Ihre Veranstaltungen, wickelt die Einlösung
der Tickets ab und führt die Abrechnung mit dem Hintergrund-System durch.
Für Ihre Kunden verhält sich der KulturPass wie eine Zahlungsmethode im Bestellprozess und wird dort neben Ihren
anderen Zahlungsmethoden mit angeboten.
Die Gelder für mit dem KulturPass bezahlte Tickets erhalten Sie in Form einer Sammel-Überweisung von der Stiftung
Digitale Chancen auf das von Ihnen beim KulturPass Onboarding angegeben Bankkonto.
In Ihrem `KulturPass-Backend`_ können Sie über den Menüpunkt "Buchhaltung" Ihre bereits erfolgten und kommenden
Auszahlungen betrachten.
.. note::
Es ist von äußerster Wichtigkeit, dass Sie weder die eingehenden Bestellungen noch die Produkte und Angebote im
KulturPass-Backend händisch bearbeiten - auch wenn dies möglich wäre.
Bei händischen Änderungen riskieren Sie, dass die Datenbasis zwischen pretix und dem KulturPass-System divergiert
und es zu fehlerhaften Buchungen kommt. Wann immer möglich, sollten Sie Korrekturbuchungen und Änderungen
ausschließlich über pretix vornehmen.
Sollte eine händische Änderung/Korrektur notwendig werden, wenden Sie sich bitte an den pretix-Support, damit wir
die Auswirkungen evaluieren und vorab mit Ihnen besprechen können!
Erstattungen für Stornos und Absagen können Sie wie gehabt über das pretix-Backend vornehmen. Der jeweilige Betrag wird
dem KulturPass-Konto dann automatisch gutgeschrieben.
Da nach Ausgabe eines KulturPass Einlöse-Codes dieser vom Kunden jederzeit oder vom System bei
Nicht-(Komplett)Einlösung binnen 48 Stunden storniert werden kann, kann das im KulturPass-Backend angezeigte,
auszuzahlende Guthaben fluktuieren. Da in der Regel Auszahlungen frühestens 48 Stunden nach der Aufgabe einer
KulturPass-Bestellungen erfolgen, sollte Ihr Guthaben in der Regel nicht ins Negative gehen.
Ablauf für Kunden
-----------------
Ihre Kunden erhalten - nachdem sie sich ein eigenes Konto in der KulturPass-App angelegt und sich mit ihrem
elektronischen Personalausweis identifiziert haben - ein Guthaben von 200 Euro, welches für Leistungen aus dem
KulturPass-Marktplatz eingelöst werden kann.
Im Falle von Veranstaltungen, die per pretix verkauft werden, wählt der Kunde ein Angebot aus und erhält im folgenden
binnen kurzer Zeit (ca. 10-20 Minuten) einen Code und einen Link, um diesen einzulösen. Der Link bringt den Kunden direkt auf die Seite der
betreffenden pretix-Veranstaltung. Hier wird der Kunde darauf hingewiesen, für welche Produkte der Code genutzt werden
kann.
Im Bezahlschritt des Verkaufsprozesses wird dem Kunden vorgeschlagen, seinen KulturPass Einlösecode nun zu nutzen, um
die gewünschte Leistung zu erhalten.
Wurde ein Artikel gewählt, welcher günstiger als der Wert des Einlösecodes war, wird das Restguthaben automatisch auf
das KulturPass-Konto erstattet.
Wurden hingegen mehrere Artikel in den Warenkorb gelegt, so kann die Differenz mit einem anderen, regulären
Zahlungsmittel erfolgen.
Einlösecodes, die vom Kunden nicht binnen 48 Stunden eingelöst werden, werden automatisch storniert und dem
KulturPass-Konto wieder gutgeschrieben. Dieser Mechanismus greift auch, wenn eine Veranstaltung mittlerweile
ausverkauft ist und daher der Einlösecode nicht mehr Nutzbar ist.
Unterstützung
-------------
Weitergehende Informationen zum KulturPass finden Sie auch auf der `Webseite des KulturPasses`_, sowie im
`KulturPass Serviceportal`_.
.. _KulturPass: https://www.kulturpass.de/
.. _Einstellungen Ihres Veranstalterkontos: https://pretix.eu/control/organizer/-/settings/kulturpass
.. _KulturPass-Erweiterung: https://pretix.eu/control/event/-/-/settings/plugins#tab-0-2-open
.. _KulturPass-Backend: https://kulturpass-de.mirakl.net/
.. _Webseite des KulturPasses: https://www.kulturpass.de/
.. _KulturPass Serviceportal: https://service.kulturpass.de/help/

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2023.9.0"
__version__ = "2023.10.0.dev0"

View File

@@ -92,6 +92,7 @@ ALL_LANGUAGES = [
('id', _('Indonesian')),
('it', _('Italian')),
('lv', _('Latvian')),
('nb-no', _('Norwegian Bokmål')),
('pl', _('Polish')),
('pt-pt', _('Portuguese (Portugal)')),
('pt-br', _('Portuguese (Brazil)')),

View File

@@ -20,11 +20,14 @@
# <https://www.gnu.org/licenses/>.
#
from django import forms
from django.conf import settings
from django.http import QueryDict
from pytz import common_timezones
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.base.exporter import OrganizerLevelExportMixin
from pretix.base.models import ScheduledEventExport, ScheduledOrganizerExport
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
@@ -197,3 +200,92 @@ class JobRunSerializer(serializers.Serializer):
raise ValidationError(self.errors)
return not bool(self._errors)
class ScheduledExportSerializer(serializers.ModelSerializer):
schedule_next_run = serializers.DateTimeField(read_only=True)
export_identifier = serializers.ChoiceField(choices=[])
locale = serializers.ChoiceField(choices=settings.LANGUAGES, default='en')
owner = serializers.SlugRelatedField(slug_field='email', read_only=True)
error_counter = serializers.IntegerField(read_only=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['export_identifier'].choices = [(e, e) for e in self.context['exporters']]
def validate(self, attrs):
if attrs.get("export_form_data"):
identifier = attrs.get('export_identifier', self.instance.export_identifier if self.instance else None)
exporter = self.context['exporters'].get(identifier)
if exporter:
try:
JobRunSerializer(exporter=exporter).to_internal_value(attrs["export_form_data"])
except ValidationError as e:
raise ValidationError({"export_form_data": e.detail})
else:
raise ValidationError({"export_identifier": ["Unknown exporter."]})
return attrs
def validate_mail_additional_recipients(self, value):
d = value.replace(' ', '')
if len(d.split(',')) > 25:
raise ValidationError('Please enter less than 25 recipients.')
return d
def validate_mail_additional_recipients_cc(self, value):
d = value.replace(' ', '')
if len(d.split(',')) > 25:
raise ValidationError('Please enter less than 25 recipients.')
return d
def validate_mail_additional_recipients_bcc(self, value):
d = value.replace(' ', '')
if len(d.split(',')) > 25:
raise ValidationError('Please enter less than 25 recipients.')
return d
class ScheduledEventExportSerializer(ScheduledExportSerializer):
class Meta:
model = ScheduledEventExport
fields = [
'id',
'owner',
'export_identifier',
'export_form_data',
'locale',
'mail_additional_recipients',
'mail_additional_recipients_cc',
'mail_additional_recipients_bcc',
'mail_subject',
'mail_template',
'schedule_rrule',
'schedule_rrule_time',
'schedule_next_run',
'error_counter',
]
class ScheduledOrganizerExportSerializer(ScheduledExportSerializer):
timezone = serializers.ChoiceField(default=settings.TIME_ZONE, choices=[(a, a) for a in common_timezones])
class Meta:
model = ScheduledOrganizerExport
fields = [
'id',
'owner',
'export_identifier',
'export_form_data',
'locale',
'mail_additional_recipients',
'mail_additional_recipients_cc',
'mail_additional_recipients_bcc',
'mail_subject',
'mail_template',
'schedule_rrule',
'schedule_rrule_time',
'schedule_next_run',
'timezone',
'error_counter',
]

View File

@@ -59,7 +59,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval',
'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
@@ -83,7 +83,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval',
'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden',
'checkin_attention', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data')
@@ -234,12 +234,13 @@ class ItemSerializer(I18nAwareModelSerializer):
class Meta:
model = Item
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission', 'personalized',
'position', 'picture', 'available_from', 'available_until',
'default_price', 'free_price', 'free_price_suggestion', 'tax_rate', 'tax_rule', 'admission',
'personalized', 'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist',
'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',
'grant_membership_duration_months', 'validity_mode', 'validity_fixed_from', 'validity_fixed_until',

View File

@@ -63,6 +63,7 @@ orga_router.register(r'teams', organizer.TeamViewSet)
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'scheduled_exports', exporters.ScheduledOrganizerExportViewSet)
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
team_router = routers.DefaultRouter()
@@ -88,6 +89,7 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet)
event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet)
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')
event_router.register(r'shredders', shredders.EventShreddersViewSet, basename='shredders')
event_router.register(r'item_meta_properties', event.ItemMetaPropertiesViewSet)

View File

@@ -89,6 +89,8 @@ class UpdateRequestSerializer(serializers.Serializer):
class RSAEncryptedField(serializers.Field):
def to_representation(self, value):
if isinstance(value, memoryview):
value = value.tobytes()
public_key = load_pem_public_key(
self.context['device'].rsa_pubkey.encode(), Backend()
)

View File

@@ -29,14 +29,20 @@ from django.utils.functional import cached_property
from django.utils.timezone import now
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from rest_framework.reverse import reverse
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.exporters import (
ExporterSerializer, JobRunSerializer,
ExporterSerializer, JobRunSerializer, ScheduledEventExportSerializer,
ScheduledOrganizerExportSerializer,
)
from pretix.base.exporter import OrganizerLevelExportMixin
from pretix.base.models import CachedFile, Device, Event, TeamAPIToken
from pretix.base.models import (
CachedFile, Device, Event, ScheduledEventExport, ScheduledOrganizerExport,
TeamAPIToken,
)
from pretix.base.services.export import export, multiexport
from pretix.base.signals import (
register_data_exporters, register_multievent_data_exporters,
@@ -199,3 +205,152 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
'provider': instance.identifier,
'form_data': data
})
class ScheduledExportersViewSet(viewsets.ModelViewSet):
filter_backends = (TotalOrderingFilter,)
ordering = ('id',)
ordering_fields = ('id', 'export_identifier', 'schedule_next_run')
class ScheduledEventExportViewSet(ScheduledExportersViewSet):
serializer_class = ScheduledEventExportSerializer
queryset = ScheduledEventExport.objects.none()
permission = 'can_view_orders'
def get_queryset(self):
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
if not perm_holder.has_event_permission(self.request.organizer, self.request.event, 'can_change_event_settings',
request=self.request):
if self.request.user.is_authenticated:
qs = self.request.event.scheduled_exports.filter(owner=self.request.user)
else:
raise PermissionDenied('Scheduled exports require either permission to change event settings or '
'user-specific API access.')
else:
qs = self.request.event.scheduled_exports
return qs.select_related("owner")
def perform_create(self, serializer):
if not self.request.user.is_authenticated:
raise PermissionDenied('Creation of exports requires user-specific API access.')
serializer.save(event=self.request.event, owner=self.request.user)
serializer.instance.compute_next_run()
serializer.instance.save(update_fields=["schedule_next_run"])
self.request.event.log_action(
'pretix.event.export.schedule.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['exporters'] = self.exporters
return ctx
@cached_property
def exporters(self):
responses = register_data_exporters.send(self.request.event)
exporters = [response(self.request.event, self.request.organizer) for r, response in responses if response]
return {e.identifier: e for e in exporters}
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.compute_next_run()
serializer.instance.error_counter = 0
serializer.instance.error_last_message = None
serializer.instance.save(update_fields=["schedule_next_run", "error_counter", "error_last_message"])
self.request.event.log_action(
'pretix.event.export.schedule.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_destroy(self, instance):
self.request.event.log_action(
'pretix.event.export.schedule.deleted',
user=self.request.user,
auth=self.request.auth,
)
super().perform_destroy(instance)
class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
serializer_class = ScheduledOrganizerExportSerializer
queryset = ScheduledOrganizerExport.objects.none()
permission = None
def get_queryset(self):
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
if not perm_holder.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings',
request=self.request):
if self.request.user.is_authenticated:
qs = self.request.organizer.scheduled_exports.filter(owner=self.request.user)
else:
raise PermissionDenied('Scheduled exports require either permission to change organizer settings or '
'user-specific API access.')
else:
qs = self.request.organizer.scheduled_exports
return qs.select_related("owner")
def perform_create(self, serializer):
if not self.request.user.is_authenticated:
raise PermissionDenied('Creation of exports requires user-specific API access.')
serializer.save(organizer=self.request.organizer, owner=self.request.user)
serializer.instance.compute_next_run()
serializer.instance.save(update_fields=["schedule_next_run"])
self.request.organizer.log_action(
'pretix.organizer.export.schedule.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
ctx['exporters'] = self.exporters
return ctx
@cached_property
def events(self):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
return self.request.auth.get_events_with_permission('can_view_orders')
elif self.request.user.is_authenticated:
return self.request.user.get_events_with_permission('can_view_orders', self.request).filter(
organizer=self.request.organizer
)
@cached_property
def exporters(self):
responses = register_multievent_data_exporters.send(self.request.organizer)
exporters = [
response(Event.objects.none() if issubclass(response, OrganizerLevelExportMixin) else self.events,
self.request.organizer)
for r, response in responses if response
]
return {e.identifier: e for e in exporters}
def perform_update(self, serializer):
serializer.save(organizer=self.request.organizer)
serializer.instance.compute_next_run()
serializer.instance.error_counter = 0
serializer.instance.error_last_message = None
serializer.instance.save(update_fields=["schedule_next_run", "error_counter", "error_last_message"])
self.request.organizer.log_action(
'pretix.organizer.export.schedule.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_destroy(self, instance):
self.request.organizer.log_action(
'pretix.organizer.export.schedule.deleted',
user=self.request.user,
auth=self.request.auth,
)
super().perform_destroy(instance)

View File

@@ -103,15 +103,17 @@ def get_all_sales_channels():
if _ALL_CHANNELS:
return _ALL_CHANNELS
types = OrderedDict()
channels = []
for recv, ret in register_sales_channels.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.identifier] = r
channels += ret
else:
types[ret.identifier] = ret
_ALL_CHANNELS = types
return types
channels.append(ret)
channels.sort(key=lambda c: c.identifier)
_ALL_CHANNELS = OrderedDict([(c.identifier, c) for c in channels])
if 'web' in _ALL_CHANNELS:
_ALL_CHANNELS.move_to_end('web', last=False)
return _ALL_CHANNELS
class WebshopSalesChannel(SalesChannel):

View File

@@ -149,6 +149,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
}
if self.organizer:
htmlctx['organizer'] = self.organizer
htmlctx['color'] = self.organizer.settings.primary_color
if self.event:
htmlctx['event'] = self.event

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.4 on 2023-10-25 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0247_checkinlist"),
]
operations = [
migrations.AddField(
model_name="item",
name="free_price_suggestion",
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
),
migrations.AddField(
model_name="itemvariation",
name="free_price_suggestion",
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.4 on 2023-10-30 11:50
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0248_item_free_price_suggestion"),
]
operations = [
migrations.AddField(
model_name="item",
name="hidden_if_item_available",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="pretixbase.item",
),
),
]

View File

@@ -857,6 +857,10 @@ class Event(EventMixin, LoggedModel):
v.item = i
v.save(force_insert=True)
for i in self.items.filter(hidden_if_item_available__isnull=False):
i.hidden_if_item_available = item_map[i.hidden_if_item_available_id]
i.save()
for imv in ItemMetaValue.objects.filter(item__event=other):
imv.pk = None
imv.property = item_meta_properties_map[imv.property_id]

View File

@@ -79,7 +79,7 @@ class AbstractScheduledExport(LoggedModel):
)
schedule_rrule = models.TextField(
null=True, blank=True, validators=[RRuleValidator()]
null=True, blank=True, validators=[RRuleValidator(enforce_simple=True)]
)
schedule_rrule_time = models.TimeField(
verbose_name=_("Requested start time"),

View File

@@ -431,6 +431,12 @@ class Item(LoggedModel):
"additional donations for your event. This is currently not supported for products that are "
"bought as an add-on to other products.")
)
free_price_suggestion = models.DecimalField(
verbose_name=_("Suggested price"),
help_text=_("This price will be used as the default value of the input field. The user can choose a lower "
"value, but not lower than the price this product would have without the free price option."),
max_digits=13, decimal_places=2, null=True, blank=True,
)
tax_rule = models.ForeignKey(
'TaxRule',
verbose_name=_('Sales tax'),
@@ -488,13 +494,24 @@ class Item(LoggedModel):
'Quota',
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name=_("Only show after sellout of"),
verbose_name=pgettext_lazy("hidden_if_available_legacy", "Only show after sellout of"),
help_text=_("If you select a quota here, this product will only be shown when that quota is "
"unavailable. If combined with the option to hide sold-out products, this allows you to "
"swap out products for more expensive ones once they are sold out. There might be a short period "
"in which both products are visible while all tickets in the referenced quota are reserved, "
"but not yet sold.")
)
hidden_if_item_available = models.ForeignKey(
'Item',
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name=_("Only show after sellout of"),
help_text=_("If you select a product here, this product will only be shown when that product is "
"sold out. If combined with the option to hide sold-out products, this allows you to "
"swap out products for more expensive ones once the cheaper option is sold out. There might "
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
)
require_voucher = models.BooleanField(
verbose_name=_('This product can only be bought using a voucher.'),
default=False,
@@ -1021,6 +1038,12 @@ class ItemVariation(models.Model):
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
)
free_price_suggestion = models.DecimalField(
verbose_name=_("Suggested price"),
help_text=_("This price will be used as the default value of the input field. The user can choose a lower "
"value, but not lower than the price this product would have without the free price option."),
max_digits=13, decimal_places=2, null=True, blank=True,
)
require_approval = models.BooleanField(
verbose_name=_('Require approval'),
default=False,

View File

@@ -817,7 +817,7 @@ class BasePaymentProvider:
"""
return ""
def order_change_allowed(self, order: Order) -> bool:
def order_change_allowed(self, order: Order, request: HttpRequest=None) -> bool:
"""
Will be called to check whether it is allowed to change the payment method of
an order to this one.
@@ -835,7 +835,12 @@ class BasePaymentProvider:
return False
if self.settings.get('_hidden', as_type=bool):
return False
if request:
hashes = set(request.session.get('pretix_unlock_hashes', [])) | set(order.meta_info_data.get('unlock_hashes', []))
if hashlib.sha256((self.settings._hidden_seed + self.event.slug).encode()).hexdigest() not in hashes:
return False
else:
return False
restricted_countries = self.settings.get('_restricted_countries', as_type=list)
if restricted_countries:

View File

@@ -80,7 +80,7 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
voucher__isnull=True
).select_related('item', 'variation', 'subevent').prefetch_related(
'item__quotas', 'variation__quotas'
).order_by('-priority', 'created')
).order_by('-priority', 'created', 'pk')
if subevent_id and event.has_subevents:
subevent = event.subevents.get(id=subevent_id)

View File

@@ -1677,6 +1677,8 @@ DEFAULTS = {
('gte', _('Only allow changes if the resulting price is higher or equal than the previous price.')),
('gt', _('Only allow changes if the resulting price is higher than the previous price.')),
('eq', _('Only allow changes if the resulting price is equal to the previous price.')),
('gte_paid', _('Allow changes regardless of price, as long as no refund is required (i.e. the resulting '
'price is not lower than what has already been paid).')),
('any', _('Allow changes regardless of price, even if this results in a refund.')),
)
),
@@ -1686,6 +1688,8 @@ DEFAULTS = {
('gte', _('Only allow changes if the resulting price is higher or equal than the previous price.')),
('gt', _('Only allow changes if the resulting price is higher than the previous price.')),
('eq', _('Only allow changes if the resulting price is equal to the previous price.')),
('gte_paid', _('Allow changes regardless of price, as long as no refund is required (i.e. the resulting '
'price is not lower than what has already been paid).')),
('any', _('Allow changes regardless of price, even if this results in a refund.')),
),
widget=forms.RadioSelect,

View File

@@ -19,7 +19,9 @@
# 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/>.
#
from dateutil.rrule import rrulestr
import calendar
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rrulestr
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
@@ -40,7 +42,6 @@ from django.utils.translation import gettext_lazy as _
class BanlistValidator:
banlist = []
def __call__(self, value):
@@ -55,7 +56,6 @@ class BanlistValidator:
@deconstructible
class EventSlugBanlistValidator(BanlistValidator):
banlist = [
'download',
'healthcheck',
@@ -77,7 +77,6 @@ class EventSlugBanlistValidator(BanlistValidator):
@deconstructible
class OrganizerSlugBanlistValidator(BanlistValidator):
banlist = [
'download',
'healthcheck',
@@ -98,7 +97,6 @@ class OrganizerSlugBanlistValidator(BanlistValidator):
@deconstructible
class EmailBanlistValidator(BanlistValidator):
banlist = [
settings.PRETIX_EMAIL_NONE_VALUE,
]
@@ -112,8 +110,45 @@ def multimail_validate(val):
class RRuleValidator:
def __init__(self, enforce_simple=False):
self.enforce_simple = enforce_simple
def __call__(self, value):
try:
rrulestr(value)
parsed = rrulestr(value)
except Exception:
raise ValidationError("Not a valid rrule.")
if self.enforce_simple:
# Validate that only things are used that we can represent in our UI for later editing
if not isinstance(parsed, rrule):
raise ValidationError("Only a single RRULE is allowed, no combination of rules.")
if parsed._freq not in (YEARLY, MONTHLY, WEEKLY, DAILY):
raise ValidationError("Unsupported FREQ value")
if parsed._wkst != calendar.firstweekday():
raise ValidationError("Unsupported WKST value")
if parsed._bysetpos:
if len(parsed._bysetpos) > 1:
raise ValidationError("Only one BYSETPOS value allowed")
if parsed._freq == YEARLY and parsed._bysetpos not in (1, 2, 3, -1):
raise ValidationError("BYSETPOS value not allowed, should be 1, 2, 3 or -1")
elif parsed._freq == MONTHLY and parsed._bysetpos not in (1, 2, 3, -1):
raise ValidationError("BYSETPOS value not allowed, should be 1, 2, 3 or -1")
elif parsed._freq not in (YEARLY, MONTHLY):
raise ValidationError("BYSETPOS not allowed for this FREQ")
if parsed._bymonthday:
raise ValidationError("BYMONTHDAY not supported")
if parsed._byyearday:
raise ValidationError("BYYEARDAY not supported")
if parsed._byeaster:
raise ValidationError("BYEASTER not supported")
if parsed._byweekno:
raise ValidationError("BYWEEKNO not supported")
if len(parsed._byhour) > 1 or set(parsed._byhour) != {parsed._dtstart.hour}:
raise ValidationError("BYHOUR not supported")
if len(parsed._byminute) > 1 or set(parsed._byminute) != {parsed._dtstart.minute}:
raise ValidationError("BYMINUTE not supported")
if len(parsed._bysecond) > 1 or set(parsed._bysecond) != {parsed._dtstart.second}:
raise ValidationError("BYSECOND not supported")

View File

@@ -208,7 +208,7 @@ class EventWizardBasicsForm(I18nModelForm):
del self.fields['team']
else:
self.fields['team'].queryset = self.user.teams.filter(organizer=self.organizer)
if not self.organizer.settings.get("event_team_provisioning", True, as_type=bool):
if self.organizer.pk and not self.organizer.settings.get("event_team_provisioning", True, as_type=bool):
self.fields['team'].required = True
self.fields['team'].empty_label = None
self.fields['team'].initial = 0

View File

@@ -1599,6 +1599,20 @@ class EventFilterForm(FilterForm):
}),
required=False
)
date_from = forms.DateField(
label=_('Date from'),
required=False,
widget=DatePickerWidget({
'placeholder': _('Date from'),
}),
)
date_until = forms.DateField(
label=_('Date until'),
required=False,
widget=DatePickerWidget({
'placeholder': _('Date until'),
}),
)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
@@ -1680,6 +1694,22 @@ class EventFilterForm(FilterForm):
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query)
)
if fdata.get('date_until'):
date_end = make_aware(datetime.combine(
fdata.get('date_until') + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(
Q(date_to__isnull=True, date_from__lt=date_end) |
Q(date_to__isnull=False, date_to__lt=date_end)
)
if fdata.get('date_from'):
date_start = make_aware(datetime.combine(
fdata.get('date_from'),
time(hour=0, minute=0, second=0, microsecond=0)
), get_current_timezone())
qs = qs.filter(date_from__gte=date_start)
filters_by_property_name = {}
for i, p in enumerate(self.meta_properties):
d = fdata.get('meta_{}'.format(p.name))

View File

@@ -45,7 +45,7 @@ from django.db.models import Max
from django.forms.formsets import DELETION_FIELD_NAME
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from django.utils.translation import (
gettext as __, gettext_lazy as _, pgettext_lazy,
@@ -387,6 +387,7 @@ class ItemCreateForm(I18nModelForm):
'allow_waitinglist',
'show_quota_left',
'hidden_if_available',
'hidden_if_item_available',
'require_bundling',
'require_membership',
'grant_membership_type',
@@ -550,19 +551,43 @@ class ItemUpdateForm(I18nModelForm):
widget=forms.CheckboxSelectMultiple
)
change_decimal_field(self.fields['default_price'], self.event.currency)
self.fields['hidden_if_available'].queryset = self.event.quotas.all()
self.fields['hidden_if_available'].widget = Select2(
if self.instance.hidden_if_available_id:
self.fields['hidden_if_available'].queryset = self.event.quotas.all()
self.fields['hidden_if_available'].help_text = format_html(
"<strong>{}</strong> {}",
_("This option is deprecated. For new products, use the newer option below that refers to another "
"product instead of a quota."),
self.fields['hidden_if_available'].help_text
)
self.fields['hidden_if_available'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.items.quotas.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': _('Shown independently of other products')
}
)
self.fields['hidden_if_available'].widget.choices = self.fields['hidden_if_available'].choices
self.fields['hidden_if_available'].required = False
else:
del self.fields['hidden_if_available']
self.fields['hidden_if_item_available'].queryset = self.event.items.exclude(id=self.instance.id)
self.fields['hidden_if_item_available'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.items.quotas.select2', kwargs={
'data-select2-url': reverse('control:event.items.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
}),
'data-placeholder': _('Shown independently of other products')
}
)
self.fields['hidden_if_available'].widget.choices = self.fields['hidden_if_available'].choices
self.fields['hidden_if_available'].required = False
self.fields['hidden_if_item_available'].widget.choices = self.fields['hidden_if_item_available'].choices
self.fields['hidden_if_item_available'].required = False
self.fields['category'].queryset = self.instance.event.categories.all()
self.fields['category'].widget = Select2(
@@ -577,6 +602,8 @@ class ItemUpdateForm(I18nModelForm):
)
self.fields['category'].widget.choices = self.fields['category'].choices
self.fields['free_price_suggestion'].widget.attrs['data-display-dependency'] = '#id_free_price'
qs = self.event.organizer.membership_types.all()
if qs:
self.fields['require_membership_types'].queryset = qs
@@ -664,6 +691,7 @@ class ItemUpdateForm(I18nModelForm):
'picture',
'default_price',
'free_price',
'free_price_suggestion',
'tax_rule',
'available_from',
'available_until',
@@ -680,6 +708,7 @@ class ItemUpdateForm(I18nModelForm):
'require_bundling',
'show_quota_left',
'hidden_if_available',
'hidden_if_item_available',
'issue_giftcard',
'require_membership',
'require_membership_types',
@@ -706,6 +735,7 @@ class ItemUpdateForm(I18nModelForm):
'validity_fixed_from': SplitDateTimeField,
'validity_fixed_until': SplitDateTimeField,
'hidden_if_available': SafeModelChoiceField,
'hidden_if_item_available': SafeModelChoiceField,
'grant_membership_type': SafeModelChoiceField,
'require_membership_types': SafeModelMultipleChoiceField,
}
@@ -797,6 +827,8 @@ class ItemVariationForm(I18nModelForm):
del self.fields['require_membership']
del self.fields['require_membership_types']
self.fields['free_price_suggestion'].widget.attrs['data-display-dependency'] = '#id_free_price'
self.meta_fields = []
meta_defaults = {}
if self.instance.pk:
@@ -829,6 +861,7 @@ class ItemVariationForm(I18nModelForm):
'value',
'active',
'default_price',
'free_price_suggestion',
'original_price',
'description',
'require_approval',

View File

@@ -0,0 +1,31 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Delete check-ins" %}{% endblock %}
{% block inside %}
<h1>{% trans "Delete check-ins" %}</h1>
<form action="{% url "control:event.orders.checkinlists.bulk_action" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}" method="post" class="form-horizontal" data-asynctask>
{% csrf_token %}
{% for k, l in request.POST.lists %}
{% for v in l %}
<input type="hidden" name="{{ k }}" value="{{ v }}">
{% endfor %}
{% endfor %}
<p>
{% blocktrans trimmed count count=cnt %}
Are you sure you want to permanently delete the check-ins of <strong>one ticket</strong>.
{% plural %}
Are you sure you want to permanently delete the check-ins of <strong>{{ count }} tickets</strong>?
{% endblocktrans %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:event.orders.checkinlists" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -205,22 +205,27 @@
</tbody>
</table>
</div>
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save">
<span class="fa fa-sign-in" aria-hidden="true"></span>
{% trans "Check-In selected attendees" %}
</button>
<button type="submit" class="btn btn-default btn-save" name="checkout" value="true">
<span class="fa fa-sign-out" aria-hidden="true"></span>
{% trans "Check-Out selected attendees" %}
</button>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
<button type="submit" class="btn btn-danger btn-save" name="revert" value="true">
<span class="fa fa-trash" aria-hidden="true"></span>
{% trans "Delete all check-ins of selected attendees" %}
</button>
{% endif %}
<div class="batch-select-actions">
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save">
<span class="fa fa-sign-in" aria-hidden="true"></span>
{% trans "Check-In selected attendees" %}
</button>
<button type="submit" class="btn btn-default btn-save" name="checkout" value="true">
<span class="fa fa-sign-out" aria-hidden="true"></span>
{% trans "Check-Out selected attendees" %}
</button>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
<button type="submit" class="btn btn-danger btn-save" name="revert"
formaction="{% url "control:event.orders.checkinlists.bulk_revert" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
data-no-asynctask
value="true">
<span class="fa fa-trash" aria-hidden="true"></span>
{% trans "Delete all check-ins of selected attendees" %}
</button>
{% endif %}
</div>
</form>
{% include "pretixcontrol/pagination.html" %}
{% endif %}

View File

@@ -7,10 +7,10 @@
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans with name=checkinlist.name %}Are you sure you want to delete the check-in list <strong>{{ name }}</strong>?{% endblocktrans %}</p>
{% if checkinlist.checkins.exists > 0 %}
<p>{% blocktrans trimmed with num=checkinlist.checkins.count %}
{% if checkinlist.checkins.exists %}
<div class="alert alert-warning">{% blocktrans trimmed with num=checkinlist.checkins.count %}
This will delete the information of <strong>{{ num }}</strong> check-ins as well.
{% endblocktrans %}</p>
{% endblocktrans %}</div>
{% endif %}
<div class="form-group submit-group">
<a href="{% url "control:event.orders.checkinlists" organizer=request.event.organizer.slug event=request.event.slug %}"
@@ -18,7 +18,11 @@
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
{% if checkinlist.checkins.exists %}
{% trans "Delete list and all check-ins" %}
{% else %}
{% trans "Delete" %}
{% endif %}
</button>
</div>
</form>

View File

@@ -32,6 +32,12 @@
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.date_from %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.date_until %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.organizer %}
</div>

View File

@@ -32,6 +32,14 @@
{% endblocktrans %}
</div>
{% endif %}
{% if not request.event.has_subevents and object.hidden_if_item_available and object.hidden_if_item_available.check_quotas.0 == 100 %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This product is currently not being shown since you configured below that it should only be visible
if a certain other product is already sold out.
{% endblocktrans %}
</div>
{% endif %}
{% block inside %}
{% endblock %}

View File

@@ -72,6 +72,7 @@
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.value layout="control" %}
{% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.free_price_suggestion addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.description layout="control" %}
{% if form.meta_fields %}
@@ -170,6 +171,7 @@
{% bootstrap_field formset.empty_form.active layout="control" %}
{% bootstrap_field formset.empty_form.value layout="control" %}
{% bootstrap_field formset.empty_form.default_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field formset.empty_form.free_price_suggestion addon_after=request.event.currency layout="control" %}
{% bootstrap_field formset.empty_form.original_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field formset.empty_form.description layout="control" %}
{% if formset.empty_form.meta_fields %}

View File

@@ -147,6 +147,7 @@
{% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.tax_rule layout="control" %}
{% bootstrap_field form.free_price layout="control" %}
{% bootstrap_field form.free_price_suggestion addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
</fieldset>
<fieldset>
@@ -168,7 +169,10 @@
{% endif %}
{% bootstrap_field form.allow_cancel layout="control" %}
{% bootstrap_field form.allow_waitinglist layout="control" %}
{% bootstrap_field form.hidden_if_available layout="control" %}
{% if form.hidden_if_available %}
{% bootstrap_field form.hidden_if_available layout="control" %}
{% endif %}
{% bootstrap_field form.hidden_if_item_available layout="control" %}
</fieldset>
{% for v in formsets.values %}
<fieldset>

View File

@@ -30,6 +30,12 @@
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.date_from %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.date_until %}
</div>
{% for mf in meta_fields %}
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field mf %}

View File

@@ -438,6 +438,7 @@ urlpatterns = [
re_path(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'),
re_path(r'^checkinlists/(?P<list>\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'),
re_path(r'^checkinlists/(?P<list>\d+)/simulator$', checkin.CheckInListSimulator.as_view(), name='event.orders.checkinlists.simulator'),
re_path(r'^checkinlists/(?P<list>\d+)/bulk_revert$', checkin.CheckInListBulkRevertConfirmView.as_view(), name='event.orders.checkinlists.bulk_revert'),
re_path(r'^checkinlists/(?P<list>\d+)/bulk_action$', checkin.CheckInListBulkActionView.as_view(), name='event.orders.checkinlists.bulk_action'),
re_path(r'^checkinlists/(?P<list>\d+)/change$', checkin.CheckinListUpdate.as_view(),
name='event.orders.checkinlists.edit'),

View File

@@ -45,7 +45,7 @@ from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.timezone import is_aware, make_aware, now
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView, ListView
from django.views.generic import FormView, ListView, TemplateView
from i18nfield.strings import LazyI18nString
from pretix.api.views.checkin import _redeem_process
@@ -191,9 +191,23 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
return ctx
class CheckInListBulkRevertConfirmView(CheckInListQueryMixin, EventPermissionRequiredMixin, TemplateView):
template_name = "pretixcontrol/checkin/bulk_revert_confirm.html"
def post(self, request, *args, **kwargs):
self.list = get_object_or_404(self.request.event.checkin_lists.all(), pk=kwargs.get("list"))
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
return super().get_context_data(
**kwargs,
cnt=self.get_queryset().count(),
checkinlist=self.list,
)
class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncPostView):
permission = ('can_change_orders', 'can_checkin_orders')
context_object_name = 'device'
def dispatch(self, request, *args, **kwargs):
self.list = get_object_or_404(self.request.event.checkin_lists.all(), pk=kwargs.get("list"))
@@ -216,14 +230,15 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
if op.order.status == Order.STATUS_PAID or (
(self.list.include_pending or op.order.valid_if_pending) and op.order.status == Order.STATUS_PENDING
):
Checkin.objects.filter(position=op, list=self.list).delete()
op.order.log_action('pretix.event.checkin.reverted', data={
'position': op.id,
'positionid': op.positionid,
'list': self.list.pk,
'web': True
}, user=request.user)
op.order.touch()
_, deleted = Checkin.objects.filter(position=op, list=self.list).delete()
if deleted:
op.order.log_action('pretix.event.checkin.reverted', data={
'position': op.id,
'positionid': op.positionid,
'list': self.list.pk,
'web': True
}, user=request.user)
op.order.touch()
return 'reverted', request.POST.get('returnquery')
else:

View File

@@ -39,6 +39,7 @@ from decimal import Decimal
from hashlib import sha1
import bleach
import dateutil
from django import forms
from django.conf import settings
from django.contrib import messages
@@ -1636,7 +1637,10 @@ class ExportMixin:
for k in initial:
if initial[k] and k in test_form.fields:
try:
initial[k] = test_form.fields[k].to_python(initial[k])
if isinstance(test_form.fields[k], forms.SplitDateTimeField):
initial[k] = dateutil.parser.parse(initial[k])
else:
initial[k] = test_form.fields[k].to_python(initial[k])
except Exception:
pass
else:

View File

@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 12:47+0000\n"
"PO-Revision-Date: 2023-10-27 07:35+0000\n"
"PO-Revision-Date: 2023-11-03 04:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
">\n"
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.1\n"
"X-Generator: Weblate 5.1.1\n"
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
#: pretix/_base_settings.py:78
@@ -4255,7 +4255,7 @@ msgstr "Produktbild"
#: pretix/base/models/items.py:491
msgid "Only show after sellout of"
msgstr "Nicht anzeigen, wenn anderes Kontingent verfügbar"
msgstr "Nicht anzeigen, wenn anderes Produkt verfügbar"
#: pretix/base/models/items.py:492
msgid ""
@@ -16562,7 +16562,7 @@ msgstr "Geräte-ID"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:6
msgid "Receipt ID"
msgstr "Transaktionsnummer"
msgstr "Belegnummer"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:11
msgid "ID"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 12:47+0000\n"
"PO-Revision-Date: 2023-10-27 07:35+0000\n"
"PO-Revision-Date: 2023-11-03 04:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.1\n"
"X-Generator: Weblate 5.1.1\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -4252,7 +4252,7 @@ msgstr "Produktbild"
#: pretix/base/models/items.py:491
msgid "Only show after sellout of"
msgstr "Nicht anzeigen, wenn anderes Kontingent verfügbar"
msgstr "Nicht anzeigen, wenn anderes Produkt verfügbar"
#: pretix/base/models/items.py:492
msgid ""
@@ -16535,7 +16535,7 @@ msgstr "Geräte-ID"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:6
msgid "Receipt ID"
msgstr "Transaktionsnummer"
msgstr "Belegnummer"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:11
msgid "ID"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 12:47+0000\n"
"PO-Revision-Date: 2023-09-27 06:59+0000\n"
"Last-Translator: Igor Támara <igor@tamarapatino.org>\n"
"PO-Revision-Date: 2023-11-07 14:00+0000\n"
"Last-Translator: Zona Vip <contacto@zonavip.mx>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.0.2\n"
"X-Generator: Weblate 5.1.1\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -455,7 +455,7 @@ msgstr "Reembolso del pago completado"
#: pretix/api/webhooks.py:281
msgid "Refund of payment canceled"
msgstr "Reembolso del pago anulado."
msgstr "Reembolso del pago anulado"
#: pretix/api/webhooks.py:285
msgid "Refund of payment failed"
@@ -526,7 +526,7 @@ msgstr "La tienda ha sido desconectada"
#: pretix/api/webhooks.py:346
msgid "Test-Mode of shop has been activated"
msgstr "Modo de pruebas de la tienda ha sido activado."
msgstr "Modo de pruebas de la tienda ha sido activado"
#: pretix/api/webhooks.py:350
msgid "Test-Mode of shop has been deactivated"
@@ -1575,7 +1575,7 @@ msgstr "Activo"
#: pretix/control/forms/discounts.py:87 pretix/control/forms/event.py:955
#: pretix/control/forms/item.py:545 pretix/control/forms/item.py:781
msgid "Sales channels"
msgstr "Canales de ventas"
msgstr "Canal de ventas"
#: pretix/base/exporters/items.py:74 pretix/base/models/items.py:420
#: pretix/base/models/items.py:1015
@@ -1984,7 +1984,7 @@ msgstr "Pagado con {method}"
#: pretix/base/exporters/orderlist.py:435
#: pretix/base/exporters/orderlist.py:846
msgid "Fee type"
msgstr "Tipo de tarifa"
msgstr "Tarifa por billete"
#: pretix/base/exporters/orderlist.py:437
#: pretix/base/exporters/orderlist.py:555
@@ -2027,8 +2027,9 @@ msgstr "cancelado"
#: pretix/base/exporters/orderlist.py:539
#: pretix/base/exporters/orderlist.py:839
#: pretix/plugins/checkinlists/exporters.py:733
#, fuzzy
msgid "Position ID"
msgstr "ID de la Posición"
msgstr "Posición"
#: pretix/base/exporters/orderlist.py:547
#: pretix/base/exporters/orderlist.py:850
@@ -2095,7 +2096,7 @@ msgstr "Fecha de inicio"
#: pretix/plugins/checkinlists/exporters.py:493
#: pretix/plugins/checkinlists/exporters.py:678
msgid "End date"
msgstr "Fecha de fin"
msgstr "Fecha final"
#: pretix/base/exporters/orderlist.py:551
#: pretix/base/exporters/orderlist.py:843 pretix/base/models/items.py:674
@@ -3010,9 +3011,9 @@ msgid "Invalid placeholder(s): %(value)s"
msgstr "Persona(s) interesada(s) inválida(s): %(value)s"
#: pretix/base/forms/widgets.py:67
#, fuzzy, python-format
#, python-format
msgid "Sample: %s"
msgstr "Ciudad de ejemplo"
msgstr "Ciudad de ejemplo: %s"
#: pretix/base/forms/widgets.py:70
#, fuzzy, python-brace-format
@@ -3476,14 +3477,12 @@ msgid "Server error"
msgstr "Error interno del servidor"
#: pretix/base/models/checkin.py:367
#, fuzzy
msgid "Ticket blocked"
msgstr "Código de ticket"
msgstr "Ticket bloqueado"
#: pretix/base/models/checkin.py:368
#, fuzzy
msgid "Ticket not valid at this time"
msgstr "Este recibo no es válido para esta fecha de evento."
msgstr "Ticket no válido en este momento"
#: pretix/base/models/customers.py:55
#, fuzzy
@@ -5210,7 +5209,7 @@ msgstr "Localización"
#: pretix/base/models/orders.py:228 pretix/control/forms/filter.py:560
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:57
msgid "Total amount"
msgstr "Monto total"
msgstr "Cantidad total"
#: pretix/base/models/orders.py:232 pretix/base/models/vouchers.py:292
msgid ""
@@ -5463,8 +5462,9 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:19
#: pretix/control/templates/pretixcontrol/organizers/index.html:6
#: pretix/control/templates/pretixcontrol/organizers/index.html:8
#, fuzzy
msgid "Organizers"
msgstr "Organizadores"
msgstr "Cuenta del organizador"
#: pretix/base/models/organizer.py:262
#: pretix/control/templates/pretixcontrol/organizers/teams.html:35
@@ -6283,10 +6283,11 @@ msgstr "No se encontraron respuestas coincidentes."
#: pretix/base/orderimport.py:740 pretix/base/services/cart.py:202
#: pretix/base/services/orderimport.py:158
#, fuzzy
msgid ""
"The seat you selected has already been taken. Please select a different seat."
msgstr "Esta URL semántica ya está en uso. Por favor, elija una diferente."
msgstr ""
"El asiento que has seleccionado ya está ocupado. Por favor seleccione un "
"asiento diferente."
#: pretix/base/orderimport.py:743 pretix/base/services/cart.py:199
#, fuzzy
@@ -7429,9 +7430,8 @@ msgstr ""
"complemento de otro proyecto."
#: pretix/base/services/cart.py:200
#, fuzzy
msgid "Please select a valid seat."
msgstr "Por favor, seleccione una cuota."
msgstr "Por favor seleccione un asiento válido."
#: pretix/base/services/cart.py:201
#, fuzzy
@@ -10909,7 +10909,7 @@ msgstr "Srx."
#: pretix/base/settings.py:3515 pretix/base/settings.py:3536
#: pretix/base/settings.py:3558
msgid "Given name"
msgstr "Nombre dado"
msgstr "Nombre"
#: pretix/base/settings.py:3357 pretix/base/settings.py:3370
#: pretix/base/settings.py:3386 pretix/base/settings.py:3402
@@ -21649,9 +21649,8 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:87
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:116
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:171
#, fuzzy
msgid "Details"
msgstr "Detalles del pedido"
msgstr "Detalles"
#: pretix/control/templates/pretixcontrol/organizers/customer.html:53
#, fuzzy
@@ -22775,7 +22774,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:141
#: pretix/control/templates/pretixcontrol/pdf/index.html:173
msgid "Loading…"
msgstr "Cargando…"
msgstr "Cargado…"
#: pretix/control/templates/pretixcontrol/pdf/index.html:144
msgid "Start editing"
@@ -26958,9 +26957,8 @@ msgid "Search text"
msgstr "Buscar"
#: pretix/plugins/banktransfer/views.py:356
#, fuzzy
msgid "min"
msgstr "Pie de imprenta"
msgstr "Información de contacto"
#: pretix/plugins/banktransfer/views.py:357
#, fuzzy
@@ -29940,7 +29938,7 @@ msgstr "Contactar con el organizador del evento"
#: pretix/presale/templates/pretixpresale/fragment_modals.html:129
#: pretix/presale/templates/pretixpresale/organizers/base.html:91
msgid "Privacy policy"
msgstr ""
msgstr "Aviso de Privacidad"
#: pretix/presale/templates/pretixpresale/event/base.html:176
#: pretix/presale/templates/pretixpresale/organizers/base.html:94
@@ -29951,7 +29949,7 @@ msgstr "Configuración de la factura"
#: pretix/presale/templates/pretixpresale/event/base.html:179
#: pretix/presale/templates/pretixpresale/organizers/base.html:97
msgid "Imprint"
msgstr "Pie de imprenta"
msgstr "Información de contacto"
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:10
msgid ""
@@ -30007,9 +30005,8 @@ msgid "Cart expired"
msgstr "El carrito de compras ha expirado"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:37
#, fuzzy
msgid "Show full cart"
msgstr "Mostrar información"
msgstr "Mostrar carrito completo"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:49
#: pretix/presale/templates/pretixpresale/event/index.html:78

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-18 07:26+0000\n"
"PO-Revision-Date: 2021-11-25 21:00+0000\n"
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n"
"PO-Revision-Date: 2023-11-07 14:00+0000\n"
"Last-Translator: Zona Vip <contacto@zonavip.mx>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
"pretix-js/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.8\n"
"X-Generator: Weblate 5.1.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -52,7 +52,7 @@ msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:39
msgid "Credit Card"
msgstr ""
msgstr "Tarjeta de crédito"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
msgid "PayPal Pay Later"
@@ -288,16 +288,12 @@ msgid "Ticket code revoked/changed"
msgstr "Código de ticket revocado/cambiado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket blocked"
msgstr "Ticket por pagar"
msgstr "Ticket bloqueado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket not valid at this time"
msgstr "Ticket por pagar"
msgstr "Ticket no válido en este momento"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
msgid "Order canceled"
@@ -427,11 +423,11 @@ msgstr "Ver variaciones del producto"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:101
msgid "Gate"
msgstr ""
msgstr "Puerta"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:105
msgid "Current date and time"
msgstr "Fecha y hora"
msgstr "Fecha y hora actual"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:109
msgid "Current day of the week (1 = Monday, 7 = Sunday)"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 12:47+0000\n"
"PO-Revision-Date: 2022-09-15 21:00+0000\n"
"Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n"
"PO-Revision-Date: 2023-11-03 22:00+0000\n"
"Last-Translator: Jāzeps Benjamins Baško <jazeps.basko@gmail.com>\n"
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix/"
"lv/>\n"
"Language: lv\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= "
"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 4.14\n"
"X-Generator: Weblate 5.1.1\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -42,11 +42,11 @@ msgstr "Ķīniešu (vienkāršota)"
#: pretix/_base_settings.py:83
msgid "Chinese (traditional)"
msgstr ""
msgstr "Ķīniešu (tradicionālā)"
#: pretix/_base_settings.py:84
msgid "Czech"
msgstr ""
msgstr "Čehu"
#: pretix/_base_settings.py:85
msgid "Danish"
@@ -78,7 +78,7 @@ msgstr "Grieķu"
#: pretix/_base_settings.py:92
msgid "Indonesian"
msgstr ""
msgstr "Indonēziešu"
#: pretix/_base_settings.py:93
msgid "Italian"
@@ -102,7 +102,7 @@ msgstr "Portugāļu (Brazīlija)"
#: pretix/_base_settings.py:98
msgid "Romanian"
msgstr ""
msgstr "Rumāņu"
#: pretix/_base_settings.py:99
msgid "Russian"
@@ -118,7 +118,7 @@ msgstr "Turku"
#: pretix/_base_settings.py:102
msgid "Ukrainian"
msgstr ""
msgstr "Ukraiņu"
#: pretix/api/auth/devicesecurity.py:31
msgid ""
@@ -300,10 +300,9 @@ msgid ""
msgstr ""
#: pretix/api/serializers/order.py:79
#, fuzzy, python-brace-format
#| msgid "Your input was not valid."
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr "Jūsu ievadītais teksts nav derīgs."
msgstr "Jūsu ievadītais teksts \"{input}\" nav derīgs."
#: pretix/api/serializers/order.py:1297 pretix/api/views/cart.py:220
#: pretix/base/services/orders.py:1495
@@ -668,10 +667,9 @@ msgid "Incompatible SSO provider: \"{error}\"."
msgstr ""
#: pretix/base/customersso/oidc.py:109
#, fuzzy, python-brace-format
#| msgid "Your event registration: {code}"
#, python-brace-format
msgid "You are not requesting \"{scope}\"."
msgstr "Jūsu pasākuma reģistrācija: {code}"
msgstr "Jūs nepieprasāt \"{scope}\""
#: pretix/base/customersso/oidc.py:115
#, python-brace-format
@@ -1032,10 +1030,8 @@ msgstr "Pasākuma biļete {event}-{code}"
#: pretix/plugins/reports/exporters.py:434
#: pretix/plugins/reports/exporters.py:671
#: pretix/plugins/ticketoutputpdf/exporters.py:85
#, fuzzy
#| msgid "Date and time"
msgid "Date range"
msgstr "Datums un laiks"
msgstr "Datumu apgabals"
#: pretix/base/exporters/dekodi.py:223 pretix/base/exporters/invoices.py:77
#, fuzzy
@@ -2687,7 +2683,7 @@ msgstr ""
#: pretix/plugins/reports/accountingreport.py:104
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:67
msgid "All"
msgstr "Visi"
msgstr "Viss"
#: pretix/base/exporters/orderlist.py:1258 pretix/control/forms/filter.py:1317
msgid "Live"
@@ -3035,10 +3031,9 @@ msgid "Invalid placeholder(s): %(value)s"
msgstr "Nederīgs (-i) vietturis (-i): %(value)s"
#: pretix/base/forms/widgets.py:67
#, fuzzy, python-format
#| msgid "Sample city"
#, python-format
msgid "Sample: %s"
msgstr "Piemēra pilsēta"
msgstr "Piemērs: %s"
#: pretix/base/forms/widgets.py:70
#, python-brace-format
@@ -3200,11 +3195,10 @@ msgid "Single price: {net_price} net / {gross_price} gross"
msgstr ""
#: pretix/base/invoice.py:659
#, fuzzy, python-brace-format
#| msgid "Single price"
#, python-brace-format
msgctxt "invoice"
msgid "Single price: {price}"
msgstr "Vienības cena"
msgstr "Vienības cena: {price}"
#: pretix/base/invoice.py:677 pretix/base/invoice.py:683
msgctxt "invoice"
@@ -3256,32 +3250,24 @@ msgid "Included taxes"
msgstr "Iekļautie nodokļi"
#: pretix/base/invoice.py:829
#, fuzzy, python-brace-format
#| msgctxt "invoice"
#| msgid ""
#| "Using the conversion rate of 1:{rate} as published by the European "
#| "Central Bank on {date}, this corresponds to:"
#, python-brace-format
msgctxt "invoice"
msgid ""
"Using the conversion rate of 1:{rate} as published by the {authority} on "
"{date}, this corresponds to:"
msgstr ""
"Izmantojot maiņas kursu 1:{rate}, ko Eiropas Centrālā banka publicēja "
"{date}, šis atbilst:"
"Izmantojot maiņas kursu 1:{rate}, ko {authority} publicēja {date}, šis "
"atbilst:"
#: pretix/base/invoice.py:844
#, fuzzy, python-brace-format
#| msgctxt "invoice"
#| msgid ""
#| "Using the conversion rate of 1:{rate} as published by the European "
#| "Central Bank on {date}, the invoice total corresponds to {total}."
#, python-brace-format
msgctxt "invoice"
msgid ""
"Using the conversion rate of 1:{rate} as published by the {authority} on "
"{date}, the invoice total corresponds to {total}."
msgstr ""
"Izmantojot maiņas kursu 1:{rate}, ko Eiropas Centrālā banka publicēja "
"{date}, rēķina kopsumma atbilst {total}."
"Izmantojot maiņas kursu 1:{rate}, ko {authority} publicēja {date}, rēķina "
"kopsumma atbilst {total}."
#: pretix/base/invoice.py:858
msgid "Modern Invoice Renderer (pretix 2.7)"
@@ -4032,10 +4018,8 @@ msgid "Export"
msgstr ""
#: pretix/base/models/exports.py:59
#, fuzzy
#| msgid "Additional fee"
msgid "Additional recipients"
msgstr "Papildu maksa"
msgstr "Papildu saņēmēji"
#: pretix/base/models/exports.py:61 pretix/base/models/exports.py:66
#: pretix/base/models/exports.py:71
@@ -4043,16 +4027,12 @@ msgid "You can specify multiple recipients separated by commas."
msgstr ""
#: pretix/base/models/exports.py:64
#, fuzzy
#| msgid "Additional fee"
msgid "Additional recipients (Cc)"
msgstr "Papildu maksa"
msgstr "Papildu saņēmēji (cc)"
#: pretix/base/models/exports.py:69
#, fuzzy
#| msgid "Additional fee"
msgid "Additional recipients (Bcc)"
msgstr "Papildu maksa"
msgstr "Papildu saņēmēji (Bcc)"
#: pretix/base/models/exports.py:74 pretix/control/forms/event.py:1070
#: pretix/control/forms/event.py:1132 pretix/control/forms/event.py:1144
@@ -7178,16 +7158,12 @@ msgstr[2] ""
#: pretix/base/services/cart.py:134 pretix/base/services/orders.py:154
#: pretix/presale/templates/pretixpresale/event/index.html:157
#: pretix/presale/views/waiting.py:104 pretix/presale/views/widget.py:715
#, fuzzy
#| msgid "The presale period for this event has not yet started."
msgid "The booking period for this event has not yet started."
msgstr "Šī pasākuma iepriekšpārdošanas periods vēl nav sācies."
msgstr "Šī pasākuma biļešu pārdošanas periods vēl nav sācies."
#: pretix/base/services/cart.py:135
#, fuzzy
#| msgid "The presale period for this event has ended."
msgid "The booking period for this event has ended."
msgstr "Šī pasākuma iepriekšpārdošanas periods ir beidzies."
msgstr "Šī pasākuma biļešu pārdošanas periods ir beidzies."
#: pretix/base/services/cart.py:136
msgid ""
@@ -7196,28 +7172,20 @@ msgid ""
msgstr ""
#: pretix/base/services/cart.py:138
#, fuzzy
#| msgid ""
#| "The presale period for this event has not yet started. The affected "
#| "positions have been removed from your cart."
msgid ""
"The booking period for this event has not yet started. The affected "
"positions have been removed from your cart."
msgstr ""
"Šī pasākuma iepriekšpārdošanas periods vēl nav sācies. Skartās pozīcijas ir "
"Šī pasākuma biļešu pārdošanas periods vēl nav sācies. Skartās pozīcijas ir "
"noņemtas no jūsu groza."
#: pretix/base/services/cart.py:141 pretix/base/services/orders.py:182
#, fuzzy
#| msgid ""
#| "The presale period for one of the events in your cart has ended. The "
#| "affected positions have been removed from your cart."
msgid ""
"The booking period for one of the events in your cart has ended. The "
"affected positions have been removed from your cart."
msgstr ""
"Ir beidzies iepriekšpārdošanas periods vienam no jūsu grozā esošajiem "
"pasākumiem. Skartās pozīcijas ir noņemtas no jūsu groza."
"Ir beidzies biļešu pārdošanas periods vienam no jūsu grozā esošajiem "
"pasākumiem. Skartās pozīcijas ir izņemtas no jūsu groza."
#: pretix/base/services/cart.py:143
#, fuzzy
@@ -7497,10 +7465,8 @@ msgid "number of entries before {datetime}"
msgstr ""
#: pretix/base/services/checkin.py:307
#, fuzzy
#| msgid "Weekend day"
msgid "week day"
msgstr "Nedēļas nogales diena"
msgstr "Nedēļas diena"
#: pretix/base/services/checkin.py:343 pretix/control/forms/filter.py:1130
msgid "Monday"
@@ -7558,17 +7524,14 @@ msgid "This ticket has been blocked."
msgstr "Šī biļete jau ir izmantota."
#: pretix/base/services/checkin.py:882 pretix/base/services/checkin.py:886
#, fuzzy, python-brace-format
#| msgctxt "subevent"
#| msgid "This voucher is not valid for this event date."
#, python-brace-format
msgid "This ticket is only valid after {datetime}."
msgstr "Šis kupons nav derīgs šajā pasākuma datumā."
msgstr "Šis kupons ir derīgs tikai pēc {datetime}."
#: pretix/base/services/checkin.py:896 pretix/base/services/checkin.py:900
#, fuzzy, python-brace-format
#| msgid "This ticket has already been redeemed."
#, python-brace-format
msgid "This ticket was only valid before {datetime}."
msgstr "Šī biļete jau ir izmantota."
msgstr "Šī biļete bija derīga tika līdz {datetime}."
#: pretix/base/services/checkin.py:931
msgid "This order position has an invalid product for this check-in list."
@@ -7865,10 +7828,8 @@ msgstr[2] ""
"no jūsu groza noņēmām liekos vienumus."
#: pretix/base/services/orders.py:155
#, fuzzy
#| msgid "The presale period has ended."
msgid "The booking period has ended."
msgstr "Iepriekšpārdošanas periods ir beidzies."
msgstr "Biļešu pārdošanas periods ir beidzies."
#: pretix/base/services/orders.py:161
msgid ""
@@ -7918,16 +7879,12 @@ msgstr ""
"kods."
#: pretix/base/services/orders.py:178
#, fuzzy
#| msgid ""
#| "The presale period for one of the events in your cart has not yet "
#| "started. The affected positions have been removed from your cart."
msgid ""
"The booking period for one of the events in your cart has not yet started. "
"The affected positions have been removed from your cart."
msgstr ""
"Vienam no jūsu grozā esošajiem pasākumiem vēl nav sācies pārdošanas laika "
"posms. Skartās pozīcijas ir noņemtas no jūsu groza."
"Vienam no jūsu grozā esošajiem pasākumiem vēl nav sācies biļešu pārdošanas "
"periods. Skartās pozīcijas ir izņemtas no jūsu groza."
#: pretix/base/services/orders.py:185
msgid ""
@@ -7946,16 +7903,11 @@ msgstr ""
"sēdvietām, tāpēc mēs noņēmām pozīciju no jūsu groza."
#: pretix/base/services/orders.py:202
#, fuzzy, python-format
#| msgid ""
#| "This order can not be canceled since the gift card {card} purchased in "
#| "this order has already been redeemed."
#, python-format
msgid ""
"You cannot remove the position %(addon)s since it has already been checked "
"in."
msgstr ""
"Šo pasūtījumu nevar atcelt, jo dāvanu karte {card}, kas iegādāta šajā "
"pasūtījumā, jau ir izmantota."
msgstr "Pozīciju %(addon)s nevar atcelt, jo tas jau ir iečekots."
#: pretix/base/services/orders.py:218
#, fuzzy
@@ -8567,10 +8519,9 @@ msgid ""
msgstr ""
#: pretix/base/settings.py:682 pretix/base/settings.py:704
#, fuzzy, python-brace-format
#| msgid "Please do not use special characters in names."
#, python-brace-format
msgid "Please only use the characters {allowed} in this field."
msgstr "Lūdzu neizmantojiet speciālās rakstzīmes vārdos."
msgstr "Lūdzu izmantojiet tikai atļautās rakstzīmes ({allowed})."
#: pretix/base/settings.py:695
msgid "Invoice number prefix for cancellations"
@@ -8747,7 +8698,7 @@ msgstr ""
#: pretix/base/settings.py:996
msgid "Accept late payments"
msgstr ""
msgstr "Pieņemt kavētus maksājumus"
#: pretix/base/settings.py:997
msgid ""
@@ -9963,7 +9914,7 @@ msgstr ""
"\n"
"{voucher_list}\n"
"\n"
"Jūs tos variet izmantot mūsu biļešu e-veikalā:\n"
"Jūs tos varat izmantot mūsu biļešu e-veikalā:\n"
"\n"
"{url}\n"
"\n"
@@ -10063,7 +10014,7 @@ msgstr ""
"\n"
"{voucher_list}\n"
"\n"
"Jūs tos variet izmantot mūsu biļešu e-veikalā:\n"
"Jūs tos varat izmantot mūsu biļešu e-veikalā:\n"
"\n"
"{url}\n"
"\n"
@@ -10799,7 +10750,7 @@ msgstr "Jums nav piekļuves šai lapai."
#: pretix/control/templates/pretixcontrol/user/staff_session_start.html:4
#: pretix/control/templates/pretixcontrol/user/staff_session_start.html:6
msgid "Admin mode"
msgstr ""
msgstr "Admin režīms"
#: pretix/base/templates/404.html:4 pretix/base/templates/404.html:8
msgid "Not found"
@@ -10860,7 +10811,7 @@ msgstr ""
#: pretix/base/templates/pretixbase/email/email_footer.html:3
#, python-format
msgid "powered by <a %(a_attr)s>pretix</a>"
msgstr ""
msgstr "darbina <a %(a_attr)s>pretix</a>"
#: pretix/base/templates/pretixbase/email/export_failed.txt:2
#, fuzzy
@@ -10869,10 +10820,8 @@ msgid "Your export failed."
msgstr "Exportētie faili"
#: pretix/base/templates/pretixbase/email/export_failed.txt:4
#, fuzzy
#| msgid "Refund reason"
msgid "Reason:"
msgstr "Naudas atmaksāšanas iemesls"
msgstr "Iemesls:"
#: pretix/base/templates/pretixbase/email/export_failed.txt:7
msgid "If your export fails five times in a row, it will no longer be sent."
@@ -12400,7 +12349,7 @@ msgstr "Sākuma datums līdz"
#: pretix/control/forms/filter.py:1888 pretix/control/forms/filter.py:1891
#: pretix/control/templates/pretixcontrol/users/index.html:52
msgid "Administrator"
msgstr ""
msgstr "Administrators"
#: pretix/control/forms/filter.py:1892
msgid "No administrator"
@@ -12547,7 +12496,7 @@ msgstr "Deaktivizētās ierīces"
#: pretix/control/forms/global_settings.py:60
msgid "Additional footer text"
msgstr "Papildus lapas galvenes (footer) teksts"
msgstr "Papildu teksts kājenei"
#: pretix/control/forms/global_settings.py:61
msgid "Will be included as additional text in the footer, site-wide."
@@ -15293,7 +15242,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/user/history.html:10
#: pretix/control/templates/pretixcontrol/user/settings.html:70
msgid "Account history"
msgstr ""
msgstr "Konta vēsture"
#: pretix/control/navigation.py:415
msgid "All users"
@@ -15303,7 +15252,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html:5
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html:7
msgid "Admin sessions"
msgstr ""
msgstr "Admin sesijas"
#: pretix/control/navigation.py:427
#: pretix/control/templates/pretixcontrol/global_settings_base.html:5
@@ -15390,7 +15339,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/auth/invite.html:7
msgid "Accept an invitation"
msgstr ""
msgstr "Pieņemt ielūgumu"
#: pretix/control/templates/pretixcontrol/auth/invite.html:10
#, python-format
@@ -15654,7 +15603,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/base.html:373
msgid "Read more"
msgstr ""
msgstr "Lasīt vairāk"
#: pretix/control/templates/pretixcontrol/base.html:390
msgid ""
@@ -16353,7 +16302,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/dashboard.html:15
msgid "Your upcoming events"
msgstr ""
msgstr "Jūsu gaidāmie pasākumi"
#: pretix/control/templates/pretixcontrol/dashboard.html:20
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
@@ -16367,7 +16316,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/dashboard.html:39
msgid "View all upcoming events"
msgstr ""
msgstr "Skatīt visus gaidāmos pasākumus"
#: pretix/control/templates/pretixcontrol/dashboard.html:44
msgid "Your most recent events"
@@ -16375,7 +16324,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/dashboard.html:60
msgid "View all recent events"
msgstr ""
msgstr "Skatīt visus nesenos pasākumus"
#: pretix/control/templates/pretixcontrol/dashboard.html:65
msgid "Your event series"
@@ -16387,7 +16336,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/dashboard.html:86
msgid "Other features"
msgstr ""
msgstr "Citas iespējas"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -16421,6 +16370,15 @@ msgid ""
"Best regards, \n"
"Your pretix team\n"
msgstr ""
"Sveicināti!\n"
"\n"
"Jūs pieprasījāt paroles maiņu. Lūdzu, dodieties uz šo lapu, lai atstatītu "
"paroli:\n"
"\n"
"%(url)s\n"
"\n"
"Ar cieņu\n"
"pretix komanda\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
@@ -16898,7 +16856,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/event/invoicing.html:26
msgid "Address form"
msgstr ""
msgstr "Adreses forma"
#: pretix/control/templates/pretixcontrol/event/invoicing.html:38
msgid "Issuer details"
@@ -18292,7 +18250,7 @@ msgstr "Jauns variants"
#: pretix/control/templates/pretixcontrol/item/include_variations.html:215
msgid "Add a new variation"
msgstr ""
msgstr "Pievienot jaunu variāciju"
#: pretix/control/templates/pretixcontrol/item/index.html:153
msgid "Availability"
@@ -18449,13 +18407,11 @@ msgid ""
msgstr ""
#: pretix/control/templates/pretixcontrol/items/discount_delete.html:21
#, fuzzy, python-format
#| msgid ""
#| "Are you sure you want to delete the rule <strong>%(subject)s</strong>?"
#, python-format
msgid "Are you sure you want to delete the discount <strong>%(name)s</strong>?"
msgstr ""
"Vai esat pārliecināts/-a, ka vēlaties dzēst noteikumu <strong>%(subject)s</"
"strong>?"
"Vai esat pārliecināts/-a, ka vēlaties dzēst atlaidi "
"<strong>%(name)s</strong>?"
#: pretix/control/templates/pretixcontrol/items/discount_delete.html:25
#, python-format
@@ -19472,10 +19428,9 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:450
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:107
#, fuzzy, python-format
#| msgid "Admission: %(datetime)s"
#, python-format
msgid "Valid %(datetime_range)s"
msgstr "Ieeja: %(datetime)s"
msgstr "Korekts %(datetime_range)s"
#: pretix/control/templates/pretixcontrol/order/index.html:496
msgid "Ticket page"
@@ -20452,7 +20407,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/orders/refunds.html:74
msgid "Actions"
msgstr ""
msgstr "Darbības"
#: pretix/control/templates/pretixcontrol/orders/search.html:7
#: pretix/control/templates/pretixcontrol/orders/search.html:9
@@ -20969,10 +20924,8 @@ msgid "Remove"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:66
#, fuzzy
#| msgid "Accept anyway"
msgid "Accept"
msgstr "Piekrist tik un tā"
msgstr "Piekrist"
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:69
msgid "Decline"
@@ -21324,7 +21277,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:81
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:122
msgid "Add"
msgstr ""
msgstr "Pievienot"
#: pretix/control/templates/pretixcontrol/organizers/team_members.html:88
msgid "API tokens"
@@ -29606,17 +29559,14 @@ msgstr "Citi datumi"
#: pretix/presale/templates/pretixpresale/event/index.html:142
#: pretix/presale/views/waiting.py:100 pretix/presale/views/widget.py:708
#, fuzzy
#| msgid "The presale period for this event is over."
msgid "The booking period for this event is over."
msgstr "Šī pasākuma iepriekšpārdošanas periods ir beidzies."
msgstr "Pasākuma biļešu pārdošanas periods ir beidzies."
#: pretix/presale/templates/pretixpresale/event/index.html:150
#: pretix/presale/views/widget.py:710
#, fuzzy, python-format
#| msgid "The presale for this event will start on %(date)s at %(time)s."
#, python-format
msgid "The booking period for this event will start on %(date)s at %(time)s."
msgstr "Šī pasākuma iepriekšpārdošana sāksies %(date)s plkst. %(time)s."
msgstr "Šī pasākuma biļešu pārdošanas periods sāksies %(date)s plkst. %(time)s."
#: pretix/presale/templates/pretixpresale/event/index.html:172
#: pretix/presale/templates/pretixpresale/event/seatingplan.html:23
@@ -30614,7 +30564,7 @@ msgstr "Netika atrasti publiski gaidāmi pasākumi."
#: pretix/presale/templates/pretixpresale/organizers/index.html:143
msgid "Show past events"
msgstr "Rādīt pagātnes pasākumus"
msgstr "Rādīt jau notikušus pasākumus"
#: pretix/presale/templates/pretixpresale/pagination.html:14
#, python-format
@@ -30687,10 +30637,8 @@ msgid "Your cart is empty"
msgstr "Jūsu grozs ir tukšs"
#: pretix/presale/views/checkout.py:59
#, fuzzy
#| msgid "The presale for this event is over or has not yet started."
msgid "The booking period for this event is over or has not yet started."
msgstr "Šī pasākuma iepriekšpārdošana ir beigusies vai vēl nav sākusies."
msgstr "Šī pasākuma biļešu pārdošanas periods ir beidzies vai vēl nav sācies."
#: pretix/presale/views/customer.py:247
msgid ""

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-18 07:26+0000\n"
"PO-Revision-Date: 2022-06-20 02:00+0000\n"
"PO-Revision-Date: 2023-11-02 13:02+0000\n"
"Last-Translator: fyksen <fredrik@fyksen.me>\n"
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nb_NO/>\n"
@@ -17,13 +17,13 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.12.2\n"
"X-Generator: Weblate 5.1.1\n"
#: 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:68
msgid "Marked as paid"
msgstr "Sett som betalt"
msgstr "Merkert som betalt"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
msgid "Comment:"
@@ -209,7 +209,7 @@ msgstr "Inngang"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
msgid "Exit"
msgstr "Utgang"
msgstr "Avslutt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
msgid "Scan a ticket or search and press return…"
@@ -230,11 +230,11 @@ msgstr "Ubetalt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
msgid "Canceled"
msgstr "Kansellert"
msgstr "Avlyst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
msgid "Redeemed"
msgstr "Løst inn"
msgstr "Innløst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
msgid "Cancel"
@@ -243,7 +243,7 @@ msgstr "Avbryt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
msgid "Ticket not paid"
msgstr "Billett ikke betalt"
msgstr "Billetten er ikke betalt."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
msgid "This ticket is not yet paid. Do you want to continue anyways?"
@@ -251,7 +251,7 @@ msgstr "Denne billetten er ikke betalt. Vil du fortsette likevel?"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
msgid "Additional information required"
msgstr "Ekstra informasjon kreves"
msgstr "pretix account invitation"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
msgid "Valid ticket"
@@ -263,11 +263,11 @@ msgstr "Utgang registrert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
msgid "Ticket already used"
msgstr "Billett allerede benyttet"
msgstr "Billetten er allerede brukt."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
msgid "Information required"
msgstr "Informasjon trengs"
msgstr "Informasjon påkrevd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
msgid "Unknown ticket"
@@ -286,16 +286,12 @@ msgid "Ticket code revoked/changed"
msgstr "Billettkode tilbakekalt/endret"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket blocked"
msgstr "Billett ikke betalt"
msgstr "Billett blokkert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket not valid at this time"
msgstr "Billett ikke betalt"
msgstr "Billetten er ikke gyldig på dette tidspunktet"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
msgid "Order canceled"
@@ -303,7 +299,7 @@ msgstr "Ordre kansellert"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
msgid "Ticket code is ambiguous on list"
msgstr ""
msgstr "Billettkoden er tvetydig på listen"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
msgid "Checked-in Tickets"
@@ -423,7 +419,7 @@ msgstr "Produkt variasjon"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:101
msgid "Gate"
msgstr ""
msgstr "Port"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:105
msgid "Current date and time"
@@ -442,16 +438,12 @@ msgid "Number of previous entries since midnight"
msgstr "Antall tidligere oppføringer siden midnatt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:121
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries since"
msgstr "Antall tidligere oppføringer"
msgstr "Antall tidligere oppføringer siden"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:125
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries before"
msgstr "Antall tidligere oppføringer"
msgstr "Antall tidligere oppføringer før"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:129
msgid "Number of days with a previous entry"
@@ -507,7 +499,7 @@ msgstr "minutter"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:174
msgid "Duplicate"
msgstr ""
msgstr "Duplikat"
#: pretix/static/pretixcontrol/js/ui/editor.js:72
msgid "Check-in QR"
@@ -689,44 +681,39 @@ msgid "Your local time:"
msgstr "Din lokale tid:"
#: pretix/static/pretixpresale/js/walletdetection.js:39
#, fuzzy
#| msgid "Apple Pay"
msgid "Google Pay"
msgstr "Apple Pay"
msgstr "Google Pay"
#: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget"
msgid "Quantity"
msgstr ""
msgstr "Antall"
#: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget"
msgid "Decrease quantity"
msgstr ""
msgstr "Begrenset antall"
#: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget"
msgid "Increase quantity"
msgstr ""
msgstr "Øk antall"
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Price"
msgstr ""
msgstr "Pris"
#: pretix/static/pretixpresale/js/widget/widget.js:20
#, fuzzy
#| msgid "Selected only"
msgctxt "widget"
msgid "Select"
msgstr "Kun valgte"
msgstr "Velg"
#: pretix/static/pretixpresale/js/widget/widget.js:21
#, fuzzy, javascript-format
#| msgid "Selected only"
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr "Kun valgte"
msgstr "Velg%s"
#: pretix/static/pretixpresale/js/widget/widget.js:22
#, fuzzy, javascript-format
@@ -937,6 +924,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available "
"again."
msgstr ""
"Noen eller alle billettkategorier er for øyeblikket utsolgt. Hvis du ønsker, "
"kan du legge deg til på ventelisten. Vi vil da gi deg beskjed hvis det blir "
"ledige seter igjen."
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 12:47+0000\n"
"PO-Revision-Date: 2023-07-04 06:00+0000\n"
"Last-Translator: Maciej Szymczak <maciej+github@szymczak.at>\n"
"PO-Revision-Date: 2023-11-06 09:00+0000\n"
"Last-Translator: Fast128 <fast128@post.pl>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix/pl/"
">\n"
"Language: pl\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.17\n"
"X-Generator: Weblate 5.1.1\n"
#: pretix/_base_settings.py:78
msgid "English"
@@ -78,7 +78,7 @@ msgstr "Grecki"
#: pretix/_base_settings.py:92
msgid "Indonesian"
msgstr ""
msgstr "Indonezyjski"
#: pretix/_base_settings.py:93
msgid "Italian"
@@ -333,10 +333,8 @@ msgstr ""
#: pretix/api/serializers/organizer.py:102
#: pretix/control/forms/organizer.py:798 pretix/presale/forms/customer.py:439
#, fuzzy
#| msgid "A voucher with this code already exists."
msgid "An account with this email address is already registered."
msgstr "Voucher o tym kodzie już istnieje."
msgstr "Konto o tym adresie e-mail już istnieje."
#: pretix/api/serializers/organizer.py:205
#: pretix/control/forms/organizer.py:647
@@ -551,23 +549,16 @@ msgid "Waiting list entry received voucher"
msgstr "Osoba z listy oczekujących dostała voucher"
#: pretix/api/webhooks.py:370
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgid "Customer account created"
msgstr "Klient"
msgstr "Stworzono konto użytkownika"
#: pretix/api/webhooks.py:374
#, fuzzy
#| msgid "Account information changed"
msgid "Customer account changed"
msgstr "Zmiana informacji konta"
msgstr "Zmieniono konto użytkownika"
#: pretix/api/webhooks.py:378
#, fuzzy
#| msgid "This product can only be bought using a voucher."
msgid "Customer account anonymized"
msgstr "Produkt może być kupiony tylko przy użyciu vouchera."
msgstr "Konto użytkownika zanonimizowane"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:934
@@ -2947,7 +2938,7 @@ msgstr "Wprowadzone obecne hasło jest nieprawidłowe."
#: pretix/base/forms/user.py:58
msgid "Please choose a password different to your current one."
msgstr ""
msgstr "Nowe hasło nie może być identyczne jak obecne."
#: pretix/base/forms/user.py:63 pretix/presale/forms/customer.py:373
#: pretix/presale/forms/customer.py:442
@@ -2989,10 +2980,8 @@ msgid "Smartphone with the Authenticator application"
msgstr "Smartfon z aplikacją Authenticator"
#: pretix/base/forms/user.py:178
#, fuzzy
#| msgid "U2F-compatible hardware token (e.g. Yubikey)"
msgid "WebAuthn-compatible hardware token (e.g. Yubikey)"
msgstr "Token hardware'owy kompatybilny z U2F (np. Yubikey)"
msgstr "Token hardware'owy kompatybilny z WebAuthn (np. Yubikey)"
#: pretix/base/forms/validators.py:62
msgid ""
@@ -3016,11 +3005,9 @@ msgstr "Przedsprzedaż nierozpoczęta"
#, fuzzy, python-brace-format
#| msgid "Invalid placeholder(s): %(value)s"
msgid "Available placeholders: {list}"
msgstr "Nieprawidłowy(e) symbol(e) zastępstwa: %(value)s"
msgstr "Dostępne symbole zastępstwa: %(value)"
#: pretix/base/forms/widgets.py:230 pretix/base/forms/widgets.py:235
#, fuzzy
#| msgid "Business customer"
msgid "Business or institutional customer"
msgstr "Klient firmowy"
@@ -3112,12 +3099,9 @@ msgid "Customer reference: {reference}"
msgstr "Adnotacja klienta: {reference}"
#: pretix/base/invoice.py:568
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgctxt "invoice"
msgid "Customer VAT ID"
msgstr "Klient"
msgstr "Numer NIP Klienta"
#: pretix/base/invoice.py:575
msgctxt "invoice"
@@ -3125,11 +3109,9 @@ msgid "Beneficiary"
msgstr "Beneficjent"
#: pretix/base/invoice.py:596
#, fuzzy
#| msgid "Invoice"
msgctxt "invoice"
msgid "Tax Invoice"
msgstr "Faktura"
msgstr "Faktura VAT"
#: pretix/base/invoice.py:597
msgctxt "invoice"
@@ -3177,14 +3159,14 @@ msgstr "Wartość"
#, python-brace-format
msgctxt "invoice"
msgid "Single price: {net_price} net / {gross_price} gross"
msgstr ""
msgstr "Cena jednostkowa: {net_price} netto / {gross_price} brutto"
#: pretix/base/invoice.py:659
#, fuzzy, python-brace-format
#| msgid "Original price"
msgctxt "invoice"
msgid "Single price: {price}"
msgstr "Cena oryginalna"
msgstr "Cena jednostkowa"
#: pretix/base/invoice.py:677 pretix/base/invoice.py:683
msgctxt "invoice"
@@ -3192,30 +3174,24 @@ msgid "Invoice total"
msgstr "Razem"
#: pretix/base/invoice.py:693
#, fuzzy
#| msgid "Only successful payments"
msgctxt "invoice"
msgid "Received payments"
msgstr "Tylko pomyślne płatności"
msgstr "Otrzymane płatności"
#: pretix/base/invoice.py:698
msgctxt "invoice"
msgid "Outstanding payments"
msgstr ""
msgstr "Zaległe płatności"
#: pretix/base/invoice.py:715
#, fuzzy
#| msgid "Gift card"
msgctxt "invoice"
msgid "Paid by gift card"
msgstr "Karta prezentowa"
msgstr "Zapłacono kartą prezentową"
#: pretix/base/invoice.py:720
#, fuzzy
#| msgid "Question option"
msgctxt "invoice"
msgid "Remaining amount"
msgstr "Opcja pytania"
msgstr "Pozostała kwota"
#: pretix/base/invoice.py:769
msgctxt "invoice"
@@ -3266,11 +3242,8 @@ msgstr ""
"Centrylny w dniu {date}, suma faktury wynosi: {total}.."
#: pretix/base/invoice.py:858
#, fuzzy
#| msgctxt "invoice"
#| msgid "Classic renderer (pretix 1.0)"
msgid "Modern Invoice Renderer (pretix 2.7)"
msgstr "Klasyczny renderer (pretix 1.0)"
msgstr "Modern Invoice Renderer (pretix 2.7)"
#: pretix/base/invoice.py:947
#, fuzzy
@@ -3281,12 +3254,12 @@ msgstr "Proszę wpisać to samo hasło dwukrotnie"
#: pretix/base/media.py:61
msgid "Barcode / QR-Code"
msgstr ""
msgstr "Kod kreskowy / Kod QR"
#: pretix/base/media.py:77
#: pretix/control/templates/pretixcontrol/organizers/edit.html:237
msgid "NFC UID-based"
msgstr ""
msgstr "NFC na bazie UID"
#: pretix/base/migrations/0077_auto_20171124_1629.py:33
#: pretix/base/migrations/0077_auto_20171124_1629_squashed_0088_auto_20180328_1217.py:35
@@ -3306,10 +3279,8 @@ msgid "Date joined"
msgstr "Data dołączenia"
#: pretix/base/models/auth.py:256
#, fuzzy
#| msgid "Repeat new password"
msgid "Force user to select a new password"
msgstr "Powtórz nowe hasło"
msgstr "Wymuś wybranie nowego hasła użytkownika"
#: pretix/base/models/auth.py:266
msgid "Two-factor authentication is required to log in"
@@ -3369,18 +3340,20 @@ msgstr ""
#: pretix/base/models/checkin.py:66
msgctxt "checkin"
msgid "Ignore check-ins on this list in statistics"
msgstr ""
msgstr "Ignoruj zameldowania obecne na tej liście w statystykach"
#: pretix/base/models/checkin.py:70
msgctxt "checkin"
msgid "Tickets with a check-in on this list should be considered \"used\""
msgstr ""
msgstr "Zameldowane bilety z tej listy powinny być uważane za \"użyte\""
#: pretix/base/models/checkin.py:71
msgid ""
"This is relevant in various situations, e.g. for deciding if a ticket can "
"still be canceled by the customer."
msgstr ""
"To istotne w wielu sytuacjach, np żeby zdecydować czy bilet może być "
"anulowany przez klienta."
#: pretix/base/models/checkin.py:75
msgctxt "checkin"
@@ -3399,6 +3372,7 @@ msgstr ""
#: pretix/base/models/checkin.py:80
msgid "Allow checking in add-on tickets by scanning the main ticket"
msgstr ""
"Pozwól na zameldowanie dodatkowych biletów przez skanowanie głównego biletu"
#: pretix/base/models/checkin.py:82
msgid ""
@@ -3410,36 +3384,37 @@ msgstr ""
#: pretix/base/models/checkin.py:86 pretix/control/navigation.py:626
#: pretix/control/templates/pretixcontrol/organizers/gates.html:5
msgid "Gates"
msgstr ""
msgstr "Bramki"
#: pretix/base/models/checkin.py:87
msgid ""
"Does not have any effect for the validation of tickets, only for the "
"automatic configuration of check-in devices."
msgstr ""
"Nie wpływa na sprawdzenie ważności biletu, tylko na automatyczną "
"konfigurację urządzeń do sprawdzania biletów."
#: pretix/base/models/checkin.py:91
msgid "Allow re-entering after an exit scan"
msgstr ""
msgstr "Pozwól na ponowne wejście bo zeskanowaniu na wyjściu"
#: pretix/base/models/checkin.py:95
msgid "Allow multiple entries per ticket"
msgstr ""
msgstr "Pozwól na wielokrotne wejścia na ten sam bilet"
#: pretix/base/models/checkin.py:96
msgid ""
"Use this option to turn off warnings if a ticket is scanned a second time."
msgstr ""
"Użyj tej opcji by wyłączyć ostrzeżenie, że bilet jest skanowany ponownie."
#: pretix/base/models/checkin.py:100
#, fuzzy
#| msgid "Ticket checked in"
msgid "Automatically check out everyone at"
msgstr "Bilet zameldowany"
msgstr "Automatycznie zamelduj wszystkich w"
#: pretix/base/models/checkin.py:106
msgid "Sales channels to automatically check in"
msgstr ""
msgstr "Kanały sprzedaży do automatycznego sprawdzania biletów"
#: pretix/base/models/checkin.py:107
msgid ""
@@ -3450,18 +3425,16 @@ msgid ""
msgstr ""
#: pretix/base/models/checkin.py:340
#, fuzzy
#| msgid "Country"
msgid "Entry"
msgstr "Kraj"
msgstr "Wejście"
#: pretix/base/models/checkin.py:341
msgid "Exit"
msgstr ""
msgstr "Wyjście"
#: pretix/base/models/checkin.py:358
msgid "Unknown ticket"
msgstr ""
msgstr "Bilet nieznany"
#: pretix/base/models/checkin.py:359
msgid "Ticket not paid"
@@ -3469,7 +3442,7 @@ msgstr "Bilet nie został opłacony"
#: pretix/base/models/checkin.py:360
msgid "Forbidden by custom rule"
msgstr ""
msgstr "Zabronione przez niestandardową regułę"
#: pretix/base/models/checkin.py:361
#, fuzzy
@@ -3497,7 +3470,7 @@ msgstr "Przedsprzedaż nierozpoczęta"
#: pretix/base/models/checkin.py:365
msgid "Ticket code is ambiguous on list"
msgstr ""
msgstr "Kod biletu jest niejednoznaczny na liście"
#: pretix/base/models/checkin.py:366
#, fuzzy
@@ -3518,14 +3491,12 @@ msgid "Ticket not valid at this time"
msgstr "Produkt nie będzie sprzedawany przed podaną datą."
#: pretix/base/models/customers.py:55
#, fuzzy
#| msgid "Attendee name"
msgid "Provider name"
msgstr "Imię uczstnika"
msgstr "Imię dostarczyciela"
#: pretix/base/models/customers.py:60
msgid "Login button label"
msgstr ""
msgstr "Etykieta przycisku logowania"
#: pretix/base/models/customers.py:64
#, fuzzy
@@ -3547,6 +3518,8 @@ msgid ""
"The identifier may only contain letters, numbers, dots, dashes, and "
"underscores. It must start and end with a letter or number."
msgstr ""
"Identyfikator może zawierać jedynie litery, cyfry, kropki, średniki i "
"podkreślniki. Może zaczynać się i kończyć literą, lub cyfrą."
#: pretix/base/models/customers.py:299 pretix/base/models/orders.py:1410
#: pretix/base/models/orders.py:2989 pretix/base/settings.py:1093
@@ -3562,40 +3535,34 @@ msgstr "Proszę wybrać kraj"
#: pretix/base/models/customers.py:370
msgctxt "openidconnect"
msgid "Confidential"
msgstr ""
msgstr "Poufne"
#: pretix/base/models/customers.py:371
msgctxt "openidconnect"
msgid "Public"
msgstr ""
msgstr "Publiczne"
#: pretix/base/models/customers.py:377
#, fuzzy
#| msgid "Cart positions"
msgctxt "openidconnect"
msgid "Authorization code"
msgstr "Pozycje wózka"
msgstr "Kod autoryzacyjny"
#: pretix/base/models/customers.py:378
msgctxt "openidconnect"
msgid "Implicit"
msgstr ""
msgstr "Domniemany"
#: pretix/base/models/customers.py:382
msgid "OpenID Connect access (required)"
msgstr ""
#: pretix/base/models/customers.py:383
#, fuzzy
#| msgid "Creation date"
msgid "Profile data (name, addresses)"
msgstr "Data stworzenia"
msgstr "Dane profilu (nazwa, adresy)"
#: pretix/base/models/customers.py:403
#, fuzzy
#| msgid "Payment fee"
msgid "Client type"
msgstr "Prowizja płatności"
msgstr "Typ klienta"
#: pretix/base/models/customers.py:406
#, fuzzy
@@ -3605,11 +3572,11 @@ msgstr "Typ urządzenia"
#: pretix/base/models/customers.py:417
msgid "Allowed access scopes"
msgstr ""
msgstr "Zakres dozwolonego dostępu"
#: pretix/base/models/customers.py:418
msgid "Separate multiple values with spaces"
msgstr ""
msgstr "Rozdziel wiele wartości spacją"
#: pretix/base/models/devices.py:71 pretix/base/models/items.py:1478
msgid "Internal identifier"
@@ -3626,7 +3593,7 @@ msgstr "Identyfikator użyty przy innym pytaniu."
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
#: pretix/plugins/checkinlists/exporters.py:741
msgid "Gate"
msgstr ""
msgstr "Bramka"
#: pretix/base/models/devices.py:132
#: pretix/control/templates/pretixcontrol/organizers/devices.html:83
@@ -3640,7 +3607,7 @@ msgstr "Data inicjalizacji"
#: pretix/base/models/discount.py:45
msgctxt "subevent"
msgid "Dates can be mixed without limitation"
msgstr ""
msgstr "Daty mogą być mieszane bez ograniczeń"
#: pretix/base/models/discount.py:46
#, fuzzy
@@ -3652,7 +3619,7 @@ msgstr "Produkt nie będzie sprzedawany przed podaną datą."
#: pretix/base/models/discount.py:47
msgctxt "subevent"
msgid "Each matching product must be for a different date"
msgstr ""
msgstr "Każdy pasujący produkt musi być na inną datę"
#: pretix/base/models/discount.py:65 pretix/base/models/items.py:1010
#: pretix/base/models/items.py:1287 pretix/base/models/items.py:1511
@@ -3673,7 +3640,7 @@ msgstr "Wszystkie produkty (łącznie z nowo stworzonymi)"
#: pretix/base/models/discount.py:97
msgid "Apply to specific products"
msgstr ""
msgstr "Zastosuj do wybranych produktów"
#: pretix/base/models/discount.py:102
#, fuzzy
@@ -3683,11 +3650,11 @@ msgstr "Ograniczenie do produktów"
#: pretix/base/models/discount.py:103 pretix/base/models/discount.py:158
msgid "Discounts never apply to bundled products"
msgstr ""
msgstr "Zniżki nigdy nie obowiązuja dla zgrupowanych produktów"
#: pretix/base/models/discount.py:107 pretix/base/models/discount.py:162
msgid "Ignore products discounted by a voucher"
msgstr ""
msgstr "Ignoruj produkty ze zniżką z kuponu"
#: pretix/base/models/discount.py:108
msgid ""
@@ -3696,14 +3663,18 @@ msgid ""
"use a voucher only to e.g. unlock a hidden product or gain access to sold-"
"out quota will still be considered."
msgstr ""
"Jeżeli ta opcja jest zaznaczona, produkty które już otrzymały zniżkę z "
"vouchera nie będą kwalifikować się do tej zniżki. Podukty przy których użyto "
"vouchera np po to by odblokować ukryty produkt, lub uzyskać dostęp do "
"wyprzedanego produktu nadal będą kwalifikować się do zniżki."
#: pretix/base/models/discount.py:113
msgid "Minimum number of matching products"
msgstr ""
msgstr "Minimalna ilość zgodnych produktów"
#: pretix/base/models/discount.py:117
msgid "Minimum gross value of matching products"
msgstr ""
msgstr "Minimalna wartość brutto zgodnych produktów"
#: pretix/base/models/discount.py:125
#, fuzzy
@@ -3726,11 +3697,11 @@ msgstr "Ograniczenie do produktów"
#: pretix/base/models/discount.py:137
msgid "Percentual discount on matching products"
msgstr ""
msgstr "Procentowa zniżka na zgodnych produktach"
#: pretix/base/models/discount.py:144
msgid "Apply discount only to this number of matching products"
msgstr ""
msgstr "Zastosuj zniżkę tylko dla tej ilości zgodnych produktów"
#: pretix/base/models/discount.py:146
msgid ""
@@ -4375,7 +4346,7 @@ msgid ""
"many times. If you keep the field empty or set it to 0, there is no special "
"limit for this product."
msgstr ""
"Ten produkt może zostać zamówiony tylko jeśli wózek użytkownika zawiera go "
"Ten produkt może zostać zamówiony tylko jeśli koszyk użytkownika zawiera go "
"przynajmniej tyle razy. Pozostawienie pola pustego lub wprowadzenie 0 "
"wyłącza ten limit."
@@ -5556,10 +5527,8 @@ msgstr ""
"uprawnienie."
#: pretix/base/models/organizer.py:283
#, fuzzy
#| msgid "Internal comment"
msgid "Can manage customer accounts"
msgstr "Komentarz wewnętrzny"
msgstr "Może zarządzać kontami użytkownikó∑"
#: pretix/base/models/organizer.py:287
#, fuzzy
@@ -7045,10 +7014,8 @@ msgid "Seat: seat number"
msgstr "Ulica i numer domu"
#: pretix/base/pdf.py:487
#, fuzzy
#| msgid "Date and time"
msgid "Date and time of first scan"
msgstr "Data i czas"
msgstr "Data i czas pierwszego skanu"
#: pretix/base/pdf.py:493
#, fuzzy
@@ -7761,6 +7728,8 @@ msgid ""
"You selected a membership for the product \"{product}\" which does not "
"require a membership."
msgstr ""
"Wybrano członkowstwo dla produktu \"{product}\", któryh nie wymaga "
"członkowstwa."
#: pretix/base/services/memberships.py:113
#, python-brace-format
@@ -7768,13 +7737,12 @@ msgid ""
"You selected the product \"{product}\" which requires an active membership "
"to be selected."
msgstr ""
"Wybrano produkt \"{product}\" który wymaga wybrania aktywnego członkowstwa."
#: pretix/base/services/memberships.py:142
#, fuzzy
#| msgid "You cannot select an item that belongs to a different event."
msgid ""
"You selected a membership that is connected to a different customer account."
msgstr "Wybrano przedmiot należący do innego wydarzenia."
msgstr "Wybrano członkowstwo, które jest połączone z innym kontem klienta."
#: pretix/base/services/memberships.py:147
#, fuzzy
@@ -7792,6 +7760,8 @@ msgid ""
"You selected a membership that is valid from {start} to {end}, but selected "
"an event taking place at {date}."
msgstr ""
"Wybrano członkowstwo które jest ważne od {start} do {end}, ale wybrane "
"wydarzenie ma miejsce {date}."
#: pretix/base/services/memberships.py:174
#, python-brace-format
@@ -7799,6 +7769,8 @@ msgid ""
"You selected a membership of type \"{type}\", which is not allowed for the "
"product \"{product}\"."
msgstr ""
"Wybrano członkowstwo typu \"{type}\", które nie jest dozwolone dla produktu "
"\"{product}\"."
#: pretix/base/services/memberships.py:183
#, python-brace-format
@@ -29774,7 +29746,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/index.html:243
msgid "If you already ordered a ticket"
msgstr ""
msgstr "Jeżeli bilet jest już zamówiony"
#: pretix/presale/templates/pretixpresale/event/index.html:247
msgid ""
@@ -29786,7 +29758,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/index.html:256
msgid "Resend order link"
msgstr ""
msgstr "Wyślij ponownie link do zamówienia"
#: pretix/presale/templates/pretixpresale/event/order.html:12
#: pretix/presale/templates/pretixpresale/event/order.html:29
@@ -30228,10 +30200,8 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/resend_link.html:4
#: pretix/presale/templates/pretixpresale/event/resend_link.html:11
#, fuzzy
#| msgid "Pending orders"
msgid "Resend order links"
msgstr "Zamówienia w toku"
msgstr "Wyślij ponownie linki do zamówienia"
#: pretix/presale/templates/pretixpresale/event/resend_link.html:15
msgid ""
@@ -30964,6 +30934,8 @@ msgid ""
"We've added you to the waiting list. We will send an email to {email} as "
"soon as this product gets available again."
msgstr ""
"Dodaliśmy Cię do listy oczekujących. Wyślemi Ci mail na {email} kiedy "
"produkt będzie znów dostępny."
#: pretix/presale/views/waiting.py:160
#, fuzzy

View File

@@ -25,9 +25,18 @@ from django.forms.models import ModelChoiceIterator
from django.utils.translation import gettext_lazy as _
from pretix.plugins.badges.models import BadgeItem, BadgeLayout
from pretix.plugins.badges.templates import TEMPLATES
class BadgeLayoutForm(forms.ModelForm):
template = forms.ChoiceField(
label=_('Template'),
help_text=_('You can modify the layout or change to a different page size in the next step.'),
choices=((k, v['label']) for k, v in TEMPLATES.items()),
widget=forms.RadioSelect,
initial='a6l',
)
class Meta:
model = BadgeLayout
fields = ('name',)

View File

@@ -0,0 +1,237 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# 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/>.
#
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from reportlab.lib import pagesizes
from reportlab.lib.units import mm
def _simple_template(w, h):
name_size = max(min(20, w / 20), 12) # Heuristic for font size
company_size = name_size - 2
return [
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "%.2f" % (((h - company_size * 1.5 - name_size) / 2 + company_size * 1.5) / mm),
"fontsize": name_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "%.2f" % (w / mm - 10),
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "%.2f" % ((((h - company_size * 1.5 - name_size) / 2) + company_size) / mm),
"fontsize": company_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "%.2f" % (w / mm - 10),
"downward": True,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
]
TEMPLATES = {
"a6l": {
"label": _("A6 landscape"),
"pagesize": pagesizes.landscape(pagesizes.A6),
"layout": _simple_template(*pagesizes.landscape(pagesizes.A6)),
},
"a6p": {
"label": _("A6 portrait"),
"pagesize": pagesizes.portrait(pagesizes.A6),
"layout": _simple_template(*pagesizes.portrait(pagesizes.A6)),
},
"a7l": {
"label": _("A7 landscape"),
"pagesize": pagesizes.landscape(pagesizes.A7),
"layout": _simple_template(*pagesizes.landscape(pagesizes.A7)),
},
"a7p": {
"label": _("A7 portrait"),
"pagesize": pagesizes.portrait(pagesizes.A7),
"layout": _simple_template(*pagesizes.portrait(pagesizes.A7)),
},
"82x203butterfly": {
"label": format_lazy(_("{width} x {height} mm butterfly badge"), width=82, height=203),
"pagesize": (82 * mm, 203 * mm),
"layout": [
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "152.55",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "144.55",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "77.10",
"bottom": "34.68",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 180,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "77.06",
"bottom": "44.28",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 180,
"align": "center",
},
],
},
"100x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=100, height=50),
"pagesize": (100 * mm, 50 * mm),
"layout": _simple_template(100 * mm, 50 * mm),
},
"83x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=83, height=50),
"pagesize": (83 * mm, 50 * mm),
"layout": _simple_template(83 * mm, 50 * mm),
},
"80x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=80, height=50),
"pagesize": (80 * mm, 50 * mm),
"layout": _simple_template(80 * mm, 50 * mm),
},
"75x52": {
"label": format_lazy(_("{width} x {height} mm label"), width=75, height=52),
"pagesize": (75 * mm, 52 * mm),
"layout": _simple_template(75 * mm, 52 * mm),
},
"70x36": {
"label": format_lazy(_("{width} x {height} mm label"), width=70, height=36),
"pagesize": (70 * mm, 36 * mm),
"layout": _simple_template(70 * mm, 36 * mm),
},
"63x29": {
"label": format_lazy(_("{width} x {height} mm label"), width=63, height=29),
"pagesize": (63.5 * mm, 29.6 * mm),
"layout": _simple_template(63.5 * mm, 29.6 * mm),
},
"60x90": {
"label": format_lazy(_("{width} x {height} mm label"), width=60, height=90),
"pagesize": (60 * mm, 90 * mm),
"layout": _simple_template(60 * mm, 90 * mm),
},
"54x90": {
"label": format_lazy(_("{width} x {height} mm label"), width=54, height=90),
"pagesize": (54 * mm, 90 * mm),
"layout": _simple_template(54 * mm, 90 * mm),
},
"50x80": {
"label": format_lazy(_("{width} x {height} mm label"), width=50, height=80),
"pagesize": (50 * mm, 80 * mm),
"layout": _simple_template(50 * mm, 80 * mm),
},
"40x75": {
"label": format_lazy(_("{width} x {height} mm label"), width=40, height=75),
"pagesize": (40 * mm, 75 * mm),
"layout": _simple_template(40 * mm, 75 * mm),
},
"40x40": {
"label": format_lazy(_("{width} x {height} mm label"), width=40, height=40),
"pagesize": (40 * mm, 40 * mm),
"layout": _simple_template(40 * mm, 40 * mm),
},
}

View File

@@ -18,21 +18,12 @@
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.name layout="control" %}
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Badge design" %}
</label>
<div class="col-md-9">
<p>
{% blocktrans trimmed %}
You can modify the design after you saved this page.
{% endblocktrans %}
</p>
</div>
</div>
{% if form.template %}
{% bootstrap_field form.template layout="control" %}
{% endif %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
{% trans "Save & continue" %}
</button>
</div>
</form>

View File

@@ -21,11 +21,13 @@
#
import json
from datetime import timedelta
from decimal import Decimal
from io import BytesIO
from django.contrib import messages
from django.contrib.staticfiles import finders
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db import transaction
from django.http import Http404
@@ -37,6 +39,7 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import CreateView, DetailView, ListView
from pypdf import PdfWriter
from reportlab.lib import pagesizes
from reportlab.pdfgen import canvas
@@ -51,6 +54,7 @@ from pretix.plugins.badges.tasks import badges_create_pdf
from ...helpers.compat import CompatDeleteView
from .models import BadgeLayout
from .templates import TEMPLATES
class LayoutListView(EventPermissionRequiredMixin, ListView):
@@ -71,14 +75,32 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
context_object_name = 'layout'
success_url = '/ignored'
def get_form(self, form_class=None):
form = super().get_form(form_class)
if self.copy_from:
del form.fields['template']
return form
@transaction.atomic
def form_valid(self, form):
form.instance.event = self.request.event
if not self.request.event.badge_layouts.filter(default=True).exists():
form.instance.default = True
messages.success(self.request, _('The new badge layout has been created.'))
if not self.copy_from:
form.instance.layout = json.dumps(TEMPLATES[form.cleaned_data["template"]]["layout"])
super().form_valid(form)
if form.instance.background and form.instance.background.name:
if not self.copy_from:
p = PdfWriter()
p.add_blank_page(
width=Decimal('%.5f' % TEMPLATES[form.cleaned_data["template"]]["pagesize"][0]),
height=Decimal('%.5f' % TEMPLATES[form.cleaned_data["template"]]["pagesize"][1]),
)
buffer = BytesIO()
p.write(buffer)
buffer.seek(0)
form.instance.background.save('background.pdf', ContentFile(buffer.read()))
elif form.instance.background and form.instance.background.name:
form.instance.background.save('background.pdf', form.instance.background)
form.instance.log_action('pretix.plugins.badges.layout.added', user=self.request.user,
data=dict(form.cleaned_data))

View File

@@ -57,6 +57,7 @@ var pretixpaypal = {
wechatpay: gettext('WeChat Pay'),
mercadopago: gettext('Mercado Pago')
},
readyToSubmitApproval: false,
load: function () {
if (pretixpaypal.paypal === null) {
@@ -141,6 +142,8 @@ var pretixpaypal = {
}
}
};
document.addEventListener("visibilitychange", this.onApproveSubmit);
},
ready: function () {
@@ -225,12 +228,16 @@ var pretixpaypal = {
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
let selectorstub = "#payment_paypal_" + method;
var $form = $(selectorstub + "_oid").closest("form");
// Insert the tokens into the form so it gets submitted to the server
// Insert the tokens into the form, so it gets submitted to the server
$(selectorstub + "_oid").val(pretixpaypal.order_id);
$(selectorstub + "_payer").val(pretixpaypal.payer_id);
// and submit
$form.get(0).submit();
// We are moving the submission to a separate function, which is also an EventListener, since
// SFSafariView refuses to submit a form that is not visible. Unfortunately, that is exactly the case
// when the ticket shop is used on iOS within an SFSafariView and the PayPal payment popup has not
// closed itself quickly enough.
pretixpaypal.readyToSubmitApproval = true;
pretixpaypal.onApproveSubmit();
// billingToken: null
// facilitatorAccessToken: "A21AAL_fEu0gDD-sIXyOy65a6MjgSJJrhmxuPcxxUGnL5gW2DzTxiiAksfoC4x8hD-BjeY1LsFVKl7ceuO7UR1a9pQr8Q_AVw"
@@ -249,6 +256,16 @@ var pretixpaypal = {
}
},
onApproveSubmit: function() {
if (document.visibilityState === "visible" && pretixpaypal.readyToSubmitApproval === true) {
let method = pretixpaypal.paypage ? "wallet" : pretixpaypal.method.method;
let selectorstub = "#payment_paypal_" + method;
var $form = $(selectorstub + "_oid").closest("form");
$form.get(0).submit();
}
},
renderAPMs: function () {
pretixpaypal.restore();
let inputselector = $("input[name=payment][value=paypal_apm]");

View File

@@ -570,7 +570,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
rate=a.tax_rate,
)
else:
v.initial_price = v.display_price
v.initial_price = v.suggested_price
i.expand = any(v.initial for v in i.available_variations)
else:
i.initial = len(current_addon_products[i.pk, None])
@@ -584,7 +584,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
rate=a.tax_rate,
)
else:
i.initial_price = i.display_price
i.initial_price = i.suggested_price
if items:
formsetentry['categories'].append({
@@ -1515,6 +1515,9 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
str(m) for m in self.confirm_messages.values()
]
}
unlock_hashes = request.session.get('pretix_unlock_hashes', [])
if unlock_hashes:
meta_info['unlock_hashes'] = unlock_hashes
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
meta_info.update(response)

View File

@@ -139,7 +139,15 @@
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}_price"
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% if var.initial_price.gross != var.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
step="any"
value="{% if event.settings.display_net_prices %}{{ var.initial_price.net|money_numberfield:event.currency }}{% else %}{{ var.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
>
@@ -268,7 +276,15 @@
id="price-item-{{ form.pos.pk }}-{{ item.pk }}"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.pos.pk }}_item_{{ item.id }}_price"
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
{% if item.initial_price.gross != item.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{ item.initial_price.net|money_numberfield:event.currency }}{% else %}{{ item.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
step="any">
</div>

View File

@@ -142,10 +142,6 @@
<td>
{% if new_pending_sum > 0 %}
<strong>{% trans "You will need to pay" %}</strong>
<br>
<span class="text-muted">
{% trans "Your entire order will be considered unpaid until you paid this difference." %}
</span>
{% else %}
<strong>{% trans "You will be refunded" %}</strong>
<br>
@@ -184,6 +180,12 @@
</table>
</div>
</div>
{% if new_pending_sum > 0 and order.status == "p" %}
<div class="alert alert-warning">
<strong>{% trans "Your entire order will be considered unpaid until you paid this difference." %}</strong>
{% trans "You might not be able to use any of the tickets in your order until this payment has been received." %}
</div>
{% endif %}
{% for k, l in request.POST.lists %}
{% for v in l %}
<input type="hidden" name="{{ k }}" value="{{ v }}">

View File

@@ -138,9 +138,17 @@
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="price_{{ item.id }}_{{ var.id }}"
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% if var.suggested_price.gross != var.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
step="any"
value="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
value="{% if event.settings.display_net_prices %}{{ var.suggested_price.net|money_numberfield:event.currency }}{% else %}{{ var.suggested_price.gross|money_numberfield:event.currency }}{% endif %}"
>
</div>
<p>
@@ -284,8 +292,16 @@
{% if not ev.presale_is_running %}disabled{% endif %}
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="price_{{ item.id }}"
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
value="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
{% if item.suggested_price.gross != item.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{ item.suggested_price.net|money_numberfield:event.currency }}{% else %}{{ item.suggested_price.gross|money_numberfield:event.currency }}{% endif %}"
step="any">
</div>
<p>

View File

@@ -20,7 +20,7 @@
</div>
</div>
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />
<input type="hidden" name="next" value="{{ request.path }}" />
<input type="hidden" name="next" value="{% if next_url %}{{ next_url }}{% else %}{{ request.path }}{% endif %}" />
<div class="col-md-4 col-sm-6 col-xs-12">
<button class="btn btn-block btn-primary" type="submit">
{% trans "Redeem voucher" %}

View File

@@ -13,12 +13,14 @@
Choose payment method: {{ code }}
{% endblocktrans %}
</h2>
<p>
{% blocktrans trimmed %}
Please note: If you change your payment method, your order total will change by the
amount displayed to the right of each method.
{% endblocktrans %}
</p>
{% if show_fees %}
<p>
{% blocktrans trimmed %}
Please note: If you change your payment method, your order total will change by the
amount displayed to the right of each method.
{% endblocktrans %}
</p>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="panel-group" id="payment_accordion">

View File

@@ -166,8 +166,16 @@
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}"
name="price_{{ item.id }}_{{ var.id }}"
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
value="{% if event.settings.display_net_prices %}{{var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}"
{% if var.suggested_price.gross != var.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{var.suggested_price.net|stringformat:"0.2f" }}{% else %}{{ var.suggested_price.gross|stringformat:"0.2f" }}{% endif %}"
step="any">
</div>
<p>
@@ -309,8 +317,16 @@
<input type="number" class="form-control input-item-price" placeholder="0"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|stringformat:"0.2f" }}{% else %}{{ item.display_price.gross|stringformat:"0.2f" }}{% endif %}"
name="price_{{ item.id }}"
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
value="{% if event.settings.display_net_prices %}{{ item.display_price.net|stringformat:"0.2f" }}{% else %}{{ item.display_price.gross|stringformat:"0.2f" }}{% endif %}"
{% if item.suggested_price.gross != item.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{ item.suggested_price.net|stringformat:"0.2f" }}{% else %}{{ item.suggested_price.gross|stringformat:"0.2f" }}{% endif %}"
step="any">
</div>
<p>

View File

@@ -9,6 +9,10 @@
{% block title %}{% trans "Voucher redemption" %}{% endblock %}
{% block content %}
{% if show_cart %}
{% include "pretixpresale/event/fragment_cart_box.html" with open=request.GET.show_cart %}
{% endif %}
<h2>{% trans "Redeem a voucher" %}</h2>
{% include "pretixpresale/event/fragment_voucher_form.html" %}
{% include "pretixpresale/event/fragment_voucher_form.html" with next_url=request.path|add:"?show_cart=true" %}
{% endblock %}

View File

@@ -600,8 +600,11 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
context['cart_redirect'] = eventreverse(self.request.event, 'presale:event.checkout.start',
kwargs={'cart_namespace': kwargs.get('cart_namespace') or ''})
else:
context['cart_redirect'] = eventreverse(self.request.event, 'presale:event.index',
kwargs={'cart_namespace': kwargs.get('cart_namespace') or ''})
if 'next' in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
context['cart_redirect'] = self.request.GET.get('next')
else:
context['cart_redirect'] = eventreverse(self.request.event, 'presale:event.index',
kwargs={'cart_namespace': kwargs.get('cart_namespace') or ''})
if context['cart_redirect'].startswith('https:'):
context['cart_redirect'] = '/' + context['cart_redirect'].split('/', 3)[3]
return context
@@ -634,7 +637,10 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
else:
err = error_messages['voucher_invalid']
else:
return render(request, 'pretixpresale/event/voucher_form.html')
context = {}
context['cart'] = self.get_cart()
context['show_cart'] = context['cart']['positions']
return render(request, 'pretixpresale/event/voucher_form.html', context)
if request.event.presale_start and now() < request.event.presale_start:
err = error_messages['not_started']

View File

@@ -127,52 +127,77 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
else:
prefetch_membership_types = []
prefetch_var = Prefetch(
'variations',
to_attr='available_variations',
queryset=ItemVariation.objects.using(settings.DATABASE_REPLICA).annotate(
subevent_disabled=Exists(
SubEventItemVariation.objects.filter(
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
variation_id=OuterRef('pk'),
subevent=subevent,
)
),
).filter(
variation_q,
active=True,
sales_channels__contains=channel,
quotas__isnull=False,
subevent_disabled=False
).prefetch_related(
*prefetch_membership_types,
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(
subevent=subevent))
).distinct()
)
prefetch_quotas = Prefetch(
'quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)
)
prefetch_bundles = Prefetch(
'bundles',
queryset=ItemBundle.objects.using(settings.DATABASE_REPLICA).prefetch_related(
Prefetch('bundled_item',
queryset=event.items.using(settings.DATABASE_REPLICA).select_related(
'tax_rule').prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(
subevent=subevent)),
)),
Prefetch('bundled_variation',
queryset=ItemVariation.objects.using(
settings.DATABASE_REPLICA
).select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(
subevent=subevent)),
)),
)
)
items = base_qs.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher, allow_addons=allow_addons).select_related(
'category', 'tax_rule', # for re-grouping
'hidden_if_available',
).prefetch_related(
*prefetch_membership_types,
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
Prefetch('bundles',
queryset=ItemBundle.objects.using(settings.DATABASE_REPLICA).prefetch_related(
Prefetch('bundled_item',
queryset=event.items.using(settings.DATABASE_REPLICA).select_related('tax_rule').prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)),
Prefetch('bundled_variation',
queryset=ItemVariation.objects.using(
settings.DATABASE_REPLICA
).select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)),
)),
Prefetch('variations', to_attr='available_variations',
queryset=ItemVariation.objects.using(settings.DATABASE_REPLICA).annotate(
subevent_disabled=Exists(
SubEventItemVariation.objects.filter(
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
variation_id=OuterRef('pk'),
subevent=subevent,
)
),
).filter(
variation_q,
active=True,
sales_channels__contains=channel,
quotas__isnull=False,
subevent_disabled=False
).prefetch_related(
*prefetch_membership_types,
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent))
).distinct()),
Prefetch(
'hidden_if_item_available',
queryset=event.items.annotate(
has_variations=Count('variations'),
).prefetch_related(
prefetch_var,
prefetch_quotas,
prefetch_bundles,
)
),
prefetch_quotas,
prefetch_var,
prefetch_bundles,
).annotate(
quotac=Count('quotas'),
has_variations=Count('variations'),
@@ -256,6 +281,19 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
item._remove = True
continue
if item.hidden_if_item_available:
if item.hidden_if_item_available.has_variations:
dependency_available = any(
var.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)[0] == Quota.AVAILABILITY_OK
for var in item.hidden_if_item_available.available_variations
)
else:
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
dependency_available = q[0] == Quota.AVAILABILITY_OK
if dependency_available:
item._remove = True
continue
if item.require_membership and item.require_membership_hidden:
if not memberships or not any([m.membership_type in item.require_membership_types.all() for m in memberships]):
item._remove = True
@@ -300,6 +338,10 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
price = original_price
item.display_price = item.tax(price, currency=event.currency, include_bundled=True)
if item.free_price and item.free_price_suggestion is not None:
item.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency, include_bundled=True)
else:
item.suggested_price = item.display_price
if price != original_price:
item.original_price = item.tax(original_price, currency=event.currency, include_bundled=True)
@@ -346,6 +388,15 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
var.display_price = var.tax(price, currency=event.currency, include_bundled=True)
if item.free_price and var.free_price_suggestion is not None:
var.suggested_price = item.tax(max(price, var.free_price_suggestion), currency=event.currency,
include_bundled=True)
elif item.free_price and item.free_price_suggestion is not None:
var.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency,
include_bundled=True)
else:
var.suggested_price = var.display_price
if price != original_price:
var.original_price = var.tax(original_price, currency=event.currency, include_bundled=True)
else:

View File

@@ -289,9 +289,17 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
ctx['can_pay'] = False
for provider in self.request.event.get_payment_providers().values():
if provider.is_enabled and provider.order_change_allowed(self.order):
ctx['can_pay'] = True
break
if provider.is_enabled:
if 'request' in inspect.signature(provider.order_change_allowed).parameters:
if provider.is_enabled and provider.order_change_allowed(self.order, request=self.request):
ctx['can_pay'] = True
break
else:
if provider.is_enabled and provider.order_change_allowed(self.order):
ctx['can_pay'] = True
break
if lp and lp.state not in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED,
OrderPayment.PAYMENT_STATE_CANCELED):
@@ -665,8 +673,16 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
providers = []
pending_sum = self.order.pending_sum
for provider in self.request.event.get_payment_providers().values():
if not provider.is_enabled or not provider.order_change_allowed(self.order):
if not provider.is_enabled:
continue
if 'request' in inspect.signature(provider.order_change_allowed).parameters:
if not provider.order_change_allowed(self.order, request=self.request):
continue
else:
if not provider.order_change_allowed(self.order):
continue
current_fee = sum(f.value for f in self.open_fees) or Decimal('0.00')
fee = provider.calculate_fee(pending_sum - current_fee)
if 'order' in inspect.signature(provider.payment_form_render).parameters:
@@ -1172,9 +1188,9 @@ class OrderPositionGiftCardDetails(EventViewMixin, OrderPositionDetailMixin, Lis
@cached_property
def giftcard(self):
return GiftCard.objects.filter(
return get_object_or_404(GiftCard.objects.filter(
Q(owner_ticket_id=self.position.pk) | Q(owner_ticket__addon_to_id=self.position.pk)
).get(pk=self.kwargs['pk'])
), pk=self.kwargs['pk'])
def get_queryset(self):
return self.giftcard.transactions.order_by('-datetime', '-pk')
@@ -1355,7 +1371,7 @@ class OrderChangeMixin:
rate=a.tax_rate,
)
else:
v.initial_price = v.display_price
v.initial_price = v.suggested_price
i.expand = any(v.initial for v in i.available_variations)
else:
i.initial = len(current_addon_products[i.pk, None])
@@ -1369,7 +1385,7 @@ class OrderChangeMixin:
rate=a.tax_rate,
)
else:
i.initial_price = i.display_price
i.initial_price = i.suggested_price
if items:
p.addon_form['categories'].append({
@@ -1579,6 +1595,8 @@ class OrderChangeMixin:
raise OrderError(_('You may only change your order in a way that increases the total price.'))
if ocm._totaldiff != Decimal('0.00') and pr == 'eq':
raise OrderError(_('You may not change your order in a way that changes the total price.'))
if ocm._totaldiff < Decimal('0.00') and self.order.total + ocm._totaldiff < self.order.payment_refund_sum and pr == 'gte_paid':
raise OrderError(_('You may not change your order in a way that would require a refund.'))
if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PAID:
self.order.set_expires(

View File

@@ -274,6 +274,7 @@ class WidgetAPIProductList(EventListMixin, View):
'order_min': item.min_per_order,
'order_max': item.order_max if not item.has_variations else None,
'price': price_dict(item, item.display_price) if not item.has_variations else None,
'suggested_price': price_dict(item, item.suggested_price) if not item.has_variations else None,
'min_price': item.min_price if item.has_variations else None,
'max_price': item.max_price if item.has_variations else None,
'allow_waitinglist': item.allow_waitinglist,
@@ -296,6 +297,7 @@ class WidgetAPIProductList(EventListMixin, View):
'order_max': var.order_max,
'description': str(rich_text(var.description, safelinks=False)) if var.description else None,
'price': price_dict(item, var.display_price),
'suggested_price': price_dict(item, var.suggested_price),
'original_price': (
(
var.original_price.net

View File

@@ -8,14 +8,14 @@
"name": "pretix",
"version": "0.0.0",
"dependencies": {
"@babel/core": "^7.23.0",
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"rollup": "^2.79.1",
"rollup-plugin-vue": "^5.0.1",
"vue": "^2.7.14",
"vue-template-compiler": "^2.7.14"
"vue": "^2.7.15",
"vue-template-compiler": "^2.7.15"
}
},
"node_modules/@ampproject/remapping": {
@@ -51,19 +51,19 @@
}
},
"node_modules/@babel/core": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz",
"integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-module-transforms": "^7.23.0",
"@babel/helpers": "^7.23.0",
"@babel/helpers": "^7.23.2",
"@babel/parser": "^7.23.0",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.0",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -445,12 +445,12 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.23.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz",
"integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.0",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0"
},
"engines": {
@@ -1695,9 +1695,9 @@
}
},
"node_modules/@rollup/plugin-babel": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz",
"integrity": "sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
"dependencies": {
"@babel/helper-module-imports": "^7.18.6",
"@rollup/pluginutils": "^5.0.1"
@@ -1708,7 +1708,7 @@
"peerDependencies": {
"@babel/core": "^7.0.0",
"@types/babel__core": "^7.1.9",
"rollup": "^1.20.0||^2.0.0||^3.0.0"
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"@types/babel__core": {
@@ -1720,9 +1720,9 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz",
"integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==",
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
@@ -1735,7 +1735,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0"
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -1795,9 +1795,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"node_modules/@vue/compiler-sfc": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz",
"integrity": "sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg==",
"dependencies": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
@@ -1805,9 +1805,9 @@
}
},
"node_modules/@vue/compiler-sfc/node_modules/postcss": {
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -1816,10 +1816,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -3082,9 +3086,15 @@
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -4018,11 +4028,11 @@
}
},
"node_modules/vue": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.15.tgz",
"integrity": "sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ==",
"dependencies": {
"@vue/compiler-sfc": "2.7.14",
"@vue/compiler-sfc": "2.7.15",
"csstype": "^3.1.0"
}
},
@@ -4032,9 +4042,9 @@
"integrity": "sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q=="
},
"node_modules/vue-template-compiler": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz",
"integrity": "sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==",
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
@@ -4134,19 +4144,19 @@
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ=="
},
"@babel/core": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz",
"integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
"requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-module-transforms": "^7.23.0",
"@babel/helpers": "^7.23.0",
"@babel/helpers": "^7.23.2",
"@babel/parser": "^7.23.0",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.0",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -4423,12 +4433,12 @@
}
},
"@babel/helpers": {
"version": "7.23.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz",
"integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
"requires": {
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.0",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0"
}
},
@@ -5254,18 +5264,18 @@
}
},
"@rollup/plugin-babel": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz",
"integrity": "sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
"requires": {
"@babel/helper-module-imports": "^7.18.6",
"@rollup/pluginutils": "^5.0.1"
}
},
"@rollup/plugin-node-resolve": {
"version": "15.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz",
"integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==",
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"requires": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
@@ -5318,9 +5328,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"@vue/compiler-sfc": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz",
"integrity": "sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg==",
"requires": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
@@ -5328,11 +5338,11 @@
},
"dependencies": {
"postcss": {
"version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"requires": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -6300,9 +6310,9 @@
"optional": true
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
},
"native-request": {
"version": "1.0.8",
@@ -7038,11 +7048,11 @@
"optional": true
},
"vue": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.15.tgz",
"integrity": "sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ==",
"requires": {
"@vue/compiler-sfc": "2.7.14",
"@vue/compiler-sfc": "2.7.15",
"csstype": "^3.1.0"
}
},
@@ -7052,9 +7062,9 @@
"integrity": "sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q=="
},
"vue-template-compiler": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz",
"integrity": "sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==",
"requires": {
"de-indent": "^1.0.2",
"he": "^1.2.0"

View File

@@ -4,13 +4,13 @@
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.23.0",
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.2.1",
"vue": "^2.7.14",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"vue": "^2.7.15",
"rollup": "^2.79.1",
"rollup-plugin-vue": "^5.0.1",
"vue-template-compiler": "^2.7.14"
"vue-template-compiler": "^2.7.15"
}
}

View File

@@ -336,7 +336,7 @@ Vue.component('pricebox', {
+ '<div v-if="free_price">'
+ '{{ $root.currency }} '
+ '<input type="number" class="pretix-widget-pricebox-price-input" placeholder="0" '
+ ' :min="display_price_nonlocalized" :value="display_price_nonlocalized" :name="field_name"'
+ ' :min="display_price_nonlocalized" :value="suggested_price_nonlocalized" :name="field_name"'
+ ' step="any" aria-label="'+strings.price+'">'
+ '</div>'
+ '<small class="pretix-widget-pricebox-tax" v-if="price.rate != \'0.00\' && price.gross != \'0.00\'">'
@@ -347,6 +347,7 @@ Vue.component('pricebox', {
price: Object,
free_price: Boolean,
field_name: String,
suggested_price: Object,
original_price: String,
mandatory_priced_addons: Boolean,
},
@@ -365,6 +366,17 @@ Vue.component('pricebox', {
return parseFloat(this.price.gross).toFixed(2);
}
},
suggested_price_nonlocalized: function () {
var price = this.suggested_price;
if (price === null) {
price = this.price;
}
if (this.$root.display_net_prices) {
return parseFloat(price.net).toFixed(2);
} else {
return parseFloat(price.gross).toFixed(2);
}
},
original_line: function () {
return this.$root.currency + " " + floatformat(parseFloat(this.original_price), 2);
},
@@ -420,7 +432,7 @@ Vue.component('variation', {
// Price
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="variation.price" :free_price="item.free_price" :original_price="orig_price" '
+ ' :mandatory_priced_addons="item.mandatory_priced_addons"'
+ ' :mandatory_priced_addons="item.mandatory_priced_addons" :suggested_price="variation.suggested_price"'
+ ' :field_name="\'price_\' + item.id + \'_\' + variation.id" v-if="$root.showPrices">'
+ '</pricebox>'
+ '<span v-if="!$root.showPrices">&nbsp;</span>'
@@ -478,7 +490,7 @@ Vue.component('item', {
// Price
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="item.price" :free_price="item.free_price" v-if="!item.has_variations && $root.showPrices"'
+ ' :mandatory_priced_addons="item.mandatory_priced_addons"'
+ ' :mandatory_priced_addons="item.mandatory_priced_addons" :suggested_price="item.suggested_price"'
+ ' :field_name="\'price_\' + item.id" :original_price="item.original_price">'
+ '</pricebox>'
+ '<div class="pretix-widget-pricebox" v-if="item.has_variations && $root.showPrices">{{ pricerange }}</div>'

View File

@@ -23,6 +23,7 @@ filterwarnings =
ignore:The 'warn' method is deprecated:DeprecationWarning
ignore::django.utils.deprecation.RemovedInDjango51Warning:django.core.files.storage
ignore:.*index_together.*:django.utils.deprecation.RemovedInDjango51Warning:
ignore:.*get_storage_class.*:django.utils.deprecation.RemovedInDjango51Warning:compressor
ignore::DeprecationWarning:mt940
ignore::DeprecationWarning:cbor2
ignore::DeprecationWarning:markdown

View File

@@ -34,10 +34,13 @@
import copy
import uuid
import zoneinfo
from datetime import time
import pytest
from django.utils.timezone import now
from pretix.base.models import CachedFile
from pretix.base.models import CachedFile, User
SAMPLE_EXPORTER_CONFIG = {
"identifier": "orderlist",
@@ -277,3 +280,532 @@ def test_org_level_export(token_client, organizer, team, event):
'_format': 'xlsx',
}, format='json')
assert resp.status_code == 404
@pytest.fixture
def event_scheduled_export(event, user):
e = event.scheduled_exports.create(
owner=user,
export_identifier="orderlist",
export_form_data={
"_format": "xlsx",
"date_range": "year_this"
},
locale="en",
mail_additional_recipients="foo@example.org",
mail_subject="Current order list",
mail_template="Here is the current order list",
schedule_rrule="DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
schedule_rrule_time=time(4, 0, 0),
)
e.compute_next_run()
e.save()
return e
TEST_SCHEDULED_EXPORT_RES = {
"owner": "dummy@dummy.dummy",
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"error_counter": 0,
}
@pytest.mark.django_db
def test_event_scheduled_export_list_token(token_client, organizer, event, user, team, event_scheduled_export):
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = event_scheduled_export.pk
res["schedule_next_run"] = event_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")). \
isoformat().replace("+00:00", "Z")
# Token can see it because it has change permission
resp = token_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
team.can_change_event_settings = False
team.save()
# Token can no longer sees it an gets error message
resp = token_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert resp.status_code == 403
@pytest.mark.django_db
def test_event_scheduled_export_list_user(user_client, organizer, event, user, team, event_scheduled_export):
user2 = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
team.members.add(user2)
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = event_scheduled_export.pk
res["schedule_next_run"] = event_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")).\
isoformat().replace("+00:00", "Z")
# User can see it because its their own
resp = user_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
team.can_change_event_settings = False
team.save()
# Owner still can
resp = user_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
# Other user can't see it and gets empty list
user_client.force_authenticate(user=user2)
resp = user_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_event_scheduled_export_detail(token_client, organizer, event, user, event_scheduled_export):
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = event_scheduled_export.pk
res["schedule_next_run"] = event_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")).\
isoformat().replace("+00:00", "Z")
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format(
organizer.slug, event.slug, event_scheduled_export.pk
)
)
assert resp.status_code == 200
assert res == resp.data
@pytest.mark.django_db
def test_event_scheduled_export_create(user_client, organizer, event, user):
resp = user_client.post(
'/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 201
created = event.scheduled_exports.get(id=resp.data["id"])
assert created.export_form_data == {"_format": "xlsx", "date_range": "year_this"}
assert created.owner == user
assert created.schedule_next_run > now()
@pytest.mark.django_db
def test_event_scheduled_export_create_requires_user(token_client, organizer, event, user):
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 403
@pytest.mark.django_db
def test_event_scheduled_export_delete_token(token_client, organizer, event, user, event_scheduled_export):
resp = token_client.delete(
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format(
organizer.slug, event.slug, event_scheduled_export.pk,
),
)
assert resp.status_code == 204
assert not event.scheduled_exports.exists()
@pytest.mark.django_db
def test_event_scheduled_export_update_token(token_client, organizer, event, user, event_scheduled_export):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format(
organizer.slug, event.slug, event_scheduled_export.pk,
),
data={
"export_form_data": {"_format": "xlsx", "date_range": "month_this"},
},
format='json'
)
assert resp.status_code == 200
created = event.scheduled_exports.get(id=resp.data["id"])
assert created.export_form_data == {"_format": "xlsx", "date_range": "month_this"}
@pytest.fixture
def org_scheduled_export(organizer, user):
e = organizer.scheduled_exports.create(
owner=user,
export_identifier="orderlist",
export_form_data={
"_format": "xlsx",
"date_range": "year_this"
},
locale="en",
mail_additional_recipients="foo@example.org",
mail_subject="Current order list",
mail_template="Here is the current order list",
schedule_rrule="DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
schedule_rrule_time=time(4, 0, 0),
)
e.compute_next_run()
e.save()
return e
@pytest.mark.django_db
def test_org_scheduled_export_list_token(token_client, organizer, user, team, org_scheduled_export):
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = org_scheduled_export.pk
res["schedule_next_run"] = org_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")). \
isoformat().replace("+00:00", "Z")
res["timezone"] = "UTC"
# Token can see it because it has change permission
resp = token_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
team.can_change_organizer_settings = False
team.save()
# Token can no longer sees it an gets error message
resp = token_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert resp.status_code == 403
@pytest.mark.django_db
def test_org_scheduled_export_list_user(user_client, organizer, user, team, org_scheduled_export):
user2 = User.objects.create_user('dummy2@dummy.dummy', 'dummy')
team.members.add(user2)
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = org_scheduled_export.pk
res["schedule_next_run"] = org_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")). \
isoformat().replace("+00:00", "Z")
res["timezone"] = "UTC"
# User can see it because its their own
resp = user_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert [res] == resp.data['results']
team.can_change_organizer_settings = False
team.save()
# Owner still can
resp = user_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert [res] == resp.data['results']
# Other user can't see it and gets empty list
user_client.force_authenticate(user=user2)
resp = user_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_org_scheduled_export_detail(token_client, organizer, user, org_scheduled_export):
res = dict(TEST_SCHEDULED_EXPORT_RES)
res["id"] = org_scheduled_export.pk
res["schedule_next_run"] = org_scheduled_export.schedule_next_run.astimezone(zoneinfo.ZoneInfo("UTC")). \
isoformat().replace("+00:00", "Z")
res["timezone"] = "UTC"
resp = token_client.get(
'/api/v1/organizers/{}/scheduled_exports/{}/'.format(
organizer.slug, org_scheduled_export.pk
)
)
assert resp.status_code == 200
assert res == resp.data
@pytest.mark.django_db
def test_org_scheduled_export_create(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 201
created = organizer.scheduled_exports.get(id=resp.data["id"])
assert created.export_form_data == {"_format": "xlsx", "date_range": "year_this", "event_date_range": "/"}
assert created.owner == user
assert created.schedule_next_run > now()
@pytest.mark.django_db
def test_org_scheduled_export_create_requires_user(token_client, organizer, user):
resp = token_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 403
@pytest.mark.django_db
def test_org_scheduled_export_delete_token(token_client, organizer, user, org_scheduled_export):
resp = token_client.delete(
'/api/v1/organizers/{}/scheduled_exports/{}/'.format(
organizer.slug, org_scheduled_export.pk,
),
)
assert resp.status_code == 204
assert not organizer.scheduled_exports.exists()
@pytest.mark.django_db
def test_org_scheduled_export_update_token(token_client, organizer, user, org_scheduled_export):
resp = token_client.patch(
'/api/v1/organizers/{}/scheduled_exports/{}/'.format(
organizer.slug, org_scheduled_export.pk,
),
data={
"export_form_data": {"_format": "xlsx", "date_range": "month_this"},
"timezone": "America/New_York"
},
format='json'
)
assert resp.status_code == 200
created = organizer.scheduled_exports.get(id=resp.data["id"])
assert created.export_form_data == {"_format": "xlsx", "date_range": "month_this", "event_date_range": "/"}
assert created.timezone == "America/New_York"
@pytest.mark.django_db
def test_org_scheduled_export_validate_identifier(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "unknownorg",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"export_identifier": ["\"unknownorg\" is not a valid choice."]}
@pytest.mark.django_db
def test_org_scheduled_export_validate_form_data(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "UNKNOWN"},
"locale": "en",
"mail_additional_recipients": "foo@example.org",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"export_form_data": {"date_range": ["Invalid date frame"]}}
@pytest.mark.django_db
def test_org_scheduled_export_validate_locale(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "BLÖDSINN",
"mail_additional_recipients": "",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"locale": ["\"BLÖDSINN\" is not a valid choice."]}
@pytest.mark.django_db
def test_org_scheduled_export_validate_timezone(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "de",
"mail_additional_recipients": "",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
"timezone": "Invalid"
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"timezone": ["\"Invalid\" is not a valid choice."]}
@pytest.mark.django_db
def test_org_scheduled_export_validate_additional_recipients(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "aaaaaa",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"mail_additional_recipients": ["Enter a valid email address."]}
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,"
"a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,"
"a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com,a@b.com",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"mail_additional_recipients": ["Please enter less than 25 recipients."]}
@pytest.mark.django_db
def test_org_scheduled_export_validate_rrule(user_client, organizer, user):
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "invalid content",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"schedule_rrule": ["Not a valid rrule."]}
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=WEEKLY;BYDAY=TU,WE,TH\nEXRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;BYDAY=TU,TH",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"schedule_rrule": ["Only a single RRULE is allowed, no combination of rules."]}
resp = user_client.post(
'/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug),
data={
"export_identifier": "orderlist",
"export_form_data": {"_format": "xlsx", "date_range": "year_this"},
"locale": "en",
"mail_additional_recipients": "",
"mail_additional_recipients_cc": "",
"mail_additional_recipients_bcc": "",
"mail_subject": "Current order list",
"mail_template": "Here is the current order list",
"schedule_rrule": "DTSTART:20230118T000000\nRRULE:FREQ=YEARLY;BYEASTER=0",
"schedule_rrule_time": "04:00:00",
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"schedule_rrule": ["BYEASTER not supported"]}

View File

@@ -277,6 +277,7 @@ TEST_ITEM_RES = {
"min_per_order": None,
"max_per_order": None,
"hidden_if_available": None,
"hidden_if_item_available": None,
"checkin_attention": False,
"has_variations": False,
"require_approval": False,
@@ -285,6 +286,7 @@ TEST_ITEM_RES = {
"bundles": [],
"show_quota_left": None,
"original_price": None,
"free_price_suggestion": None,
"meta_data": {
"day": "Tuesday"
},
@@ -384,6 +386,7 @@ def test_item_detail_variations(token_client, organizer, event, team, item):
"id": var.pk,
"value": {"en": "Children"},
"default_price": None,
"free_price_suggestion": None,
"price": "23.00",
"active": True,
"description": None,
@@ -1313,6 +1316,7 @@ TEST_VARIATIONS_RES = {
"available_until": None,
"hide_without_voucher": False,
"original_price": None,
"free_price_suggestion": None,
"meta_data": {}
}
@@ -1334,6 +1338,7 @@ TEST_VARIATIONS_UPDATE = {
"available_until": None,
"hide_without_voucher": False,
"original_price": None,
"free_price_suggestion": None,
"meta_data": {}
}

View File

@@ -71,7 +71,8 @@ def test_full_clone_same_organizer():
# todo: test that item pictures are copied, not linked
ItemMetaValue.objects.create(item=item1, property=item_meta, value="Foo")
assert item1.meta_data
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15)
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15,
hidden_if_item_available=item1)
item2v = item2.variations.create(value="red", default_price=15)
item2v.meta_values.create(property=item_meta, value="Bar")
item2.require_membership_types.add(membership_type)
@@ -156,6 +157,7 @@ def test_full_clone_same_organizer():
assert copied_item1.addons.get().addon_category == copied_event.categories.get()
assert copied_item1.bundles.get().bundled_item == copied_item2
assert copied_item1.bundles.get().bundled_variation == copied_item2.variations.get()
assert copied_item2.hidden_if_item_available == copied_item1
assert copied_q1.items.get() == copied_item1
assert copied_q2.items.get() == copied_item2
assert copied_q2.variations.get() == copied_item2.variations.get()

View File

@@ -3336,6 +3336,33 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
assert 'Workshop 1' in response.content.decode()
assert 'Workshop 2' in response.content.decode()
def test_set_addons_hidden_if_item_available(self):
with scopes_disabled():
self.workshopquota2 = Quota.objects.create(event=self.event, name='Workshop 1', size=5)
self.workshopquota2.items.add(self.workshop2)
self.workshopquota2.variations.add(self.workshop2a)
self.workshop2.hidden_if_item_available = self.workshop1
self.workshop2.save()
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat, min_count=1)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)
assert 'Workshop 1' in response.content.decode()
assert 'Workshop 2' not in response.content.decode()
self.workshopquota.size = 0
self.workshopquota.save()
response = self.client.get('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug), follow=True)
assert 'Workshop 1' in response.content.decode()
assert 'Workshop 2' in response.content.decode()
def test_set_addons_subevent(self):
with scopes_disabled():
self.event.has_subevents = True

View File

@@ -631,6 +631,30 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
self.assertNotIn("SOLD OUT", doc.select("section:nth-of-type(1)")[0].text)
self.assertIn("Late-bird", doc.select("section:nth-of-type(1)")[0].text)
def test_hidden_if_item_available(self):
with scopes_disabled():
q = Quota.objects.create(event=self.event, name='Early-bird', size=10)
q2 = Quota.objects.create(event=self.event, name='Late-bird', size=10)
item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=12)
item2 = Item.objects.create(event=self.event, name='Late-bird ticket', default_price=12,
hidden_if_item_available=item)
q.items.add(item)
q2.items.add(item2)
self.event.settings.hide_sold_out = True
doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertIn("Early-bird", doc.select("section:nth-of-type(1)")[0].text)
self.assertNotIn("SOLD OUT", doc.select("section:nth-of-type(1)")[0].text)
self.assertNotIn("Late-bird", doc.select("section:nth-of-type(1)")[0].text)
q.size = 0
q.save()
doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertNotIn("Early-bird", doc.select("section:nth-of-type(1)")[0].text)
self.assertNotIn("SOLD OUT", doc.select("section:nth-of-type(1)")[0].text)
self.assertIn("Late-bird", doc.select("section:nth-of-type(1)")[0].text)
def test_bundle_sold_out(self):
with scopes_disabled():
q = Quota.objects.create(event=self.event, name='Quota', size=2)

View File

@@ -1434,6 +1434,53 @@ class OrderChangeAddonsTest(BaseOrdersTest):
self.order.refresh_from_db()
assert self.order.total == Decimal('35.00')
def test_allow_user_price_gte_paid(self):
self.event.settings.change_allow_user_price = 'gte_paid'
with scopes_disabled():
OrderPosition.objects.create(
order=self.order,
item=self.workshop1,
variation=None,
price=Decimal("12"),
addon_to=self.ticket_pos,
attendee_name_parts={'full_name': "Peter"}
)
self.order.total += Decimal("12")
self.order.save()
self.order.payments.create(amount=Decimal("23"), provider="manual", state=OrderPayment.PAYMENT_STATE_CONFIRMED)
response = self.client.post(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{
},
follow=True
)
assert 'alert-danger' not in response.content.decode()
with scopes_disabled():
self.order.payments.create(amount=Decimal("12"), provider="manual", state=OrderPayment.PAYMENT_STATE_CONFIRMED)
response = self.client.post(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{
},
follow=True
)
assert 'alert-danger' in response.content.decode()
assert 'refund' in response.content.decode()
response = self.client.post(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{
'confirm': 'true'
},
follow=True
)
assert 'alert-danger' in response.content.decode()
assert 'refund' in response.content.decode()
self.order.refresh_from_db()
assert self.order.total == Decimal('35.00')
def test_allow_user_price_eq(self):
self.event.settings.change_allow_user_price = 'eq'
response = self.client.post(

View File

@@ -184,6 +184,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"order_min": None,
"max_price": None,
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
@@ -204,6 +205,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"order_min": None,
"max_price": "14.00",
"price": None,
"suggested_price": None,
"picture": None,
"picture_fullsize": None,
"has_variations": 4,
@@ -219,6 +221,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
'original_price': None,
"price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"suggested_price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"description": None,
"avail": [100, None],
"order_max": 2
@@ -229,6 +233,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
'original_price': None,
"price": {"gross": "12.00", "net": "10.08", "tax": "1.92", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"suggested_price": {"gross": "12.00", "net": "10.08", "tax": "1.92", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"description": None,
"avail": [100, None],
"order_max": 2
@@ -266,6 +272,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
"max_price": None,
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00",
"includes_mixed_tax_rate": False},
"suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00",
"includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
@@ -312,6 +320,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"order_min": None,
"max_price": "14.00",
"price": None,
"suggested_price": None,
"picture": None,
"picture_fullsize": None,
"has_variations": 4,
@@ -327,6 +336,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
'original_price': None,
"price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"suggested_price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "",
"rate": "19.00", "includes_mixed_tax_rate": False},
"description": None,
"avail": [100, None],
"order_max": 2
@@ -374,6 +385,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"order_min": None,
"max_price": None,
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
@@ -439,6 +451,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
'order_min': None,
'order_max': None,
'price': None,
'suggested_price': None,
'min_price': '14.00',
'max_price': '14.00',
'free_price': False,
@@ -459,6 +472,14 @@ class WidgetCartTest(CartTestMixin, TestCase):
'name': '',
'includes_mixed_tax_rate': False
},
'suggested_price': {
'gross': '14.00',
'net': '11.76',
'tax': '2.24',
'rate': '19.00',
'name': '',
'includes_mixed_tax_rate': False
},
'avail': [100, None]
},
]