Compare commits

...
Author SHA1 Message Date
Lukas Bockstaller addf94afe2 formatting 2026-11-11 12:16:31 +01:00
Lukas Bockstaller 0d828e439e adjust tests 2026-11-11 12:07:18 +01:00
Lukas Bockstaller 69108a1c20 change error serialization 2026-11-11 12:07:00 +01:00
Raphael Michel ceff9de5ee Customer step: Fix login URL for SSO in weird domain setup (#6687)
The current code links to a 404 page if the event has a custom domain
but the organizer uses the default system domain.
2026-11-10 14:44:48 +01:00
Lukas Bockstaller 38b467cef7 Add option to auto transmit invoices after modification by the customer (Z#23251416) (#6713)
* Add option to auto transmit invoices after modification by the customer

* Add caveat for peppol etc. to setting help text

* auto retransmit invoices after change

* code style
2026-11-10 14:41:19 +01:00
Raphael Michel ae95df7ea2 Merge branch 'ticketoutputpdf-security' into 'master'
[SECURITY] Fix missing permission check for ticket layout API (CVE-2026-125744)

See merge request pretix/pretix!60
2026-11-10 13:54:25 +01:00
d362f16417 Documentation for questionnaires API changes (#6704)
* Add docs pages, deprecation notes

* Add data field docs

* Items personalized deprecation

* Move paragraph

* fix indentation

* Document questionnaires resource

* Apply batched suggestions from code review

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: luelista <mira@teamwiki.de>

* rename to order_presale / position_presale

* _presale -> _purchase

* children -> rows

* change datafields API endpoint URLs

* removal of question change api endpoints

* update answers objects

* add print_on_invoice_label

* document restrictions

* Update personalized attribute

* fix inconsistent type

* Update doc/api/resources/datafields.rst

Co-authored-by: Raphael Michel <michel@pretix.eu>

* rename dependency_question -> dependency_datafield, document label/help_text fields for static text blocks

* add print_on_invoice_label to examples

* Add markers for new features

---------

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: Raphael Michel <michel@pretix.eu>
Co-authored-by: Raphael Michel <michel@rami.io>
2026-11-10 13:21:50 +01:00
Raphael Michel 7f1a0fd58f [SECURITY] Fix missing permission check for ticket layout API (CVE-2026-125744) 2026-11-10 13:13:31 +01:00
Martin Gross 6d05cfea8b Badges: Add Herma 9010 (Z#23253294) (#6759) 2026-11-10 12:29:47 +01:00
pajowu c1b8259075 Replace deprecated chained @classmethod (#6757) 2026-11-10 12:14:28 +01:00
pajowu e394054ae3 Fix duplicate default check in TicketLayoutSerializer (#6748) 2026-11-06 12:24:33 +01:00
Raphael Michel dda2a18fdf Week calendar: Fix navigation after dateutil 4.0 upgrade (#6746) 2026-11-06 10:03:55 +01:00
dependabot[bot] 68bd182f79 Bump arabic-reshaper from 3.0.1 to 3.1.0 (#6744)
Bumps [arabic-reshaper](https://github.com/mpcabd/python-arabic-reshaper) from 3.0.1 to 3.1.0.
- [Release notes](https://github.com/mpcabd/python-arabic-reshaper/releases)
- [Commits](https://github.com/mpcabd/python-arabic-reshaper/compare/v3.0.1...v3.1.0)

---
updated-dependencies:
- dependency-name: arabic-reshaper
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-05 20:03:54 +01:00
dependabot[bot] 2ee6ea7692 Update pytest-asyncio requirement from >=1.4.1 to >=1.4.1.post1 (#6743)
Updates the requirements on [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v1.4.1...v1.4.1.post1)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-version: 1.4.1.post1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-05 20:03:22 +01:00
dependabot[bot] a37305f8f4 Update flake8 requirement from ==7.4.* to ==7.5.* (#6745)
Updates the requirements on [flake8](https://github.com/pycqa/flake8) to permit the latest version.
- [Commits](https://github.com/pycqa/flake8/compare/7.4.0...7.5.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-version: 7.5.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-05 20:02:57 +01:00
Richard Schreiber bd4b049c16 Fix parameters for localization of SeatProtected errors (#6740) 2026-11-05 15:39:03 +01:00
Phin Wolkwitz cb852a8fa7 Fix itemcount in list beneath cart in event series (Z#23251632) (#6727) 2026-11-05 13:03:44 +01:00
Richard Schreiber 86dd5619e0 Move JavaScript importmap to top (#6734) 2026-11-05 12:39:21 +01:00
pajowu b1a1d66156 ticketoutputpdf api: save background to permanent file (#6735) 2026-11-05 10:33:08 +01:00
pajowu e229697d2a Fix changing sold-out addons if hide_sold_out is set (#6714)
The old item was shown in the frontend and you could de-select it, but this change was never applied
2026-11-04 20:37:05 +01:00
dependabot[bot] f9c7378da7 Update fakeredis requirement from ==2.39.* to ==2.40.* (#6737)
Updates the requirements on [fakeredis](https://github.com/cunla/fakeredis-py) to permit the latest version.
- [Release notes](https://github.com/cunla/fakeredis-py/releases)
- [Commits](https://github.com/cunla/fakeredis-py/compare/v2.39.0...v2.40.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-version: 2.40.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 20:21:03 +01:00
dependabot[bot] 86152a182a Update python-dateutil requirement from ==3.0.* to ==4.0.* (#6736)
Updates the requirements on [python-dateutil](https://github.com/dateutil/dateutil) to permit the latest version.
- [Release notes](https://github.com/dateutil/dateutil/releases)
- [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS)
- [Commits](https://github.com/dateutil/dateutil/compare/3.0.0...4.0.0)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-version: 4.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 20:20:12 +01:00
dependabot[bot] 76899600b0 Update pytest-asyncio requirement from >=1.4.0 to >=1.4.1 (#6739)
Updates the requirements on [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-version: 1.4.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 20:17:40 +01:00
pajowu 04a0b009a6 Accept a list of sales_channels in (Sub)Event.annotated, filter_available (Z#23252067) (#6723) 2026-11-04 18:49:19 +01:00
pajowu 4e9d60d238 Fix tests loading local pretix.cfg (#6705)
test/sqlite.cfg does not exist, so pretix.testutils.settings never set PRETIX_CONFIG_FILE, leading to pretix.settings loading the local pretix.cfg

Not setting VITE_DEV_MODE outside CI only worked because most devs probably have set django.debug=on in their pretix.cfg
2026-11-04 17:44:11 +01:00
Raphael Michel fe0d0ceb2e Drop importlib_metadata dependency (#6720)
No longer needed as a polyfill and starts to break things
2026-11-04 17:44:02 +01:00
dependabot[bot] 8457603555 Update sphinx requirement from ==9.1.* to ==9.0.* (#6731)
Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version.
- [Release notes](https://github.com/sphinx-doc/sphinx/releases)
- [Changelog](https://github.com/sphinx-doc/sphinx/blob/v9.0.4/CHANGES.rst)
- [Commits](https://github.com/sphinx-doc/sphinx/compare/v9.1.0rc1...v9.0.4)

---
updated-dependencies:
- dependency-name: sphinx
  dependency-version: 9.0.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 09:52:27 +01:00
dependabot[bot] ae35af596f Update tlds requirement from >=2026093003 to >=2026102900 (#6729)
Updates the requirements on [tlds](https://github.com/kichik/tlds) to permit the latest version.
- [Commits](https://github.com/kichik/tlds/commits)

---
updated-dependencies:
- dependency-name: tlds
  dependency-version: '2026102900'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 09:52:20 +01:00
Christiaan de Die le Clercq 47c3493372 Translations: Update Dutch
Currently translated at 96.9% (6255 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
Isaac Raño 85d65d17b4 Translations: Update Galician
Currently translated at 18.7% (1209 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
vihumelo e50c845e09 Translations: Update Portuguese (Brazil)
Currently translated at 98.8% (257 of 260 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
vihumelo c84d9061da Translations: Update Portuguese (Brazil)
Currently translated at 92.9% (5995 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
CVZ-es 25c9659655 Translations: Update Spanish
Currently translated at 100.0% (6452 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
CVZ-es b6bce961c3 Translations: Update French
Currently translated at 100.0% (6452 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
Mie Frydensbjerg 1de0455ebc Translations: Update Danish
Currently translated at 62.1% (4009 of 6452 strings)

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

powered by weblate
2026-11-04 09:52:05 +01:00
dependabot[bot] 9fdafa57de Update reportlab requirement from ==5.0.* to ==5.1.* (#6730)
Updates the requirements on [reportlab](https://www.reportlab.com/) to permit the latest version.

---
updated-dependencies:
- dependency-name: reportlab
  dependency-version: 5.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 09:51:36 +01:00
50bcfc8c74 OIDC: Expose SSO attributes to customer_signed_in signal (#6603)
* OIDC: Expose SSO attributes to customer_signed_in signal

* Change OIDC claim name

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>

* Document the changed signature of the customer_signed_in signal

* Put docs on right signal

* Fix test assertions

---------

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: Raphael Michel <michel@rami.io>
2026-11-04 09:48:03 +01:00
Raphael Michel fa552fe7d1 Permissions: Add helptext for outgoingmails:read (Z#23251039) (#6707) 2026-11-04 09:20:29 +01:00
dependabot[bot] cbfc24dd64 Update css-inline requirement from ==0.22.* to ==0.23.* (#6683)
Updates the requirements on [css-inline](https://github.com/Stranger6667/css-inline) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/css-inline/releases)
- [Changelog](https://github.com/Stranger6667/css-inline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/css-inline/compare/c-v0.22.0...c-v0.23.0)

---
updated-dependencies:
- dependency-name: css-inline
  dependency-version: 0.23.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-03 17:21:28 +01:00
dependabot[bot] 6e0c2d69a5 Bump pycparser from 3.11 to 3.12 (#6701)
Bumps [pycparser](https://github.com/eliben/pycparser) from 3.11 to 3.12.
- [Release notes](https://github.com/eliben/pycparser/releases)
- [Commits](https://github.com/eliben/pycparser/compare/release_v3.11...release_v3.12)

---
updated-dependencies:
- dependency-name: pycparser
  dependency-version: '3.12'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-03 17:19:46 +01:00
dependabot[bot]andRaphael Michel 34ed175abe Update dnspython requirement from ==2.8.* to ==2.9.* (#6671)
* Update dnspython requirement from ==2.8.* to ==2.9.*

Updates the requirements on [dnspython](https://github.com/rthalley/dnspython) to permit the latest version.
- [Release notes](https://github.com/rthalley/dnspython/releases)
- [Changelog](https://github.com/rthalley/dnspython/blob/main/doc/whatsnew.rst)
- [Commits](https://github.com/rthalley/dnspython/compare/v2.8.0rc1...v2.9.0)

---
updated-dependencies:
- dependency-name: dnspython
  dependency-version: 2.9.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pyproject.toml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Raphael Michel <michel@pretix.eu>
2026-11-03 17:19:17 +01:00
dependabot[bot] a9aaf8638e Update pytest-mock requirement from ==3.16.* to ==3.17.* (#6715)
Updates the requirements on [pytest-mock](https://github.com/pytest-dev/pytest-mock) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.16.0...v3.17.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-version: 3.17.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-03 17:18:21 +01:00
Raphael MichelandRaphael Michel a613affa05 Sentry: Scrub secrets from event details (#6218)
* Sentry: Scrub secrets from event details

* Apply suggestions from code review

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2026-11-03 16:45:35 +01:00
Raphael Michel cd0ed78b76 Tests: Do not rely on actual DNS for peppol validation (#6711) 2026-11-03 16:44:31 +01:00
Raphael Michel 4889abd75c PDF editor: Warn about missing scannable QR code (#6726)
* PDF editor: Warn about missing scannable QR code

* Fix
2026-11-03 16:43:59 +01:00
Raphael Michel 0a73faa8bd Bump to i18nfield 1.12.0 2026-11-03 10:38:37 +01:00
Tobias Kunze def5042661 Ignore spaces in bank transfer queries (#6724)
When copying an order code from e.g. the order list, it's easy to
include spaces when pasting, and then this does not match anything. And
as the JavaScript side of the search *does* compare the stripped string
(normalizeQuery), just deleting the whitespace does not trigger the
order to be found either, you have delete one more letter.
2026-11-03 09:12:38 +01:00
Richard Schreiber e6f8f178af Extend log_action filter and make recursive (Z#23247315) (#6598)
* Extend log_action filter and make recursive

* fix flake8

* fix type mismatch

* add "salt" to sensitive keys

* match "key" (e.g. see payone)

* add "auth" to sensitive keys

* clean lists recursively

* improve clean method name as it also cleans list recursively

* Add test
2026-10-29 10:52:00 +01:00
Raphael Michel 5f5380e8b1 Bump version to 2026.10.0.dev0 2026-10-29 09:59:11 +01:00
72 changed files with 2118 additions and 747 deletions

No files matched your search

+1 -1
View File
@@ -56,7 +56,7 @@ calculated automatically.
},
"answers": [
{
"question": 1,
"datafield": "A1B2C3D4",
"answer": "23",
"options": []
}
+18 -8
View File
@@ -3,6 +3,14 @@
Cart positions
==============
.. note::
This API is affected by an upcoming larger change to our API. The API documentation already reflects the future
version. Features or fields not yet available are marked with **NEW**.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
The API provides limited access to the cart position data model. This API currently only allows creating and deleting
cart positions to reserve quota.
@@ -31,12 +39,13 @@ voucher integer Internal ID of
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
is_bundled boolean If ``addon_to`` is set, this shows whether this is a bundled product or an addon product
subevent integer ID of the date inside an event series this position belongs to (or ``null``)
answers list of objects Answers to user-defined questions
├ question integer Internal ID of the answered question
answers list of objects Values of data fields (answers to user-defined questions)
├ datafield string **NEW** The data field's ``identifier`` (internal reference)
├ answer string Text representation of the answer
├ question_identifier string The question's ``identifier`` field
├ options list of integers Internal IDs of selected option(s)s (only for choice types)
└ option_identifiers list of strings The ``identifier`` fields of the selected option(s)s
├ option_identifiers list of strings The ``identifier`` fields of the selected option(s)s
├ question integer **Deprecated.** Internal ID of the data field
└ question_identifier string **Read-only, deprecated.** The data field's ``identifier`` (internal reference)
seat objects The assigned seat (or ``null``)
├ id integer Internal ID of the seat instance
├ name string Human-readable seat name
@@ -199,7 +208,8 @@ Cart position endpoints
* ``bundled`` (optional, expect a list of nested objects of cart positions)
* ``answers``
* ``question``
* ``datafield`` **NEW**
* ``question`` **DEPRECATED**
* ``answer``
* ``options``
@@ -223,7 +233,7 @@ Cart position endpoints
"attendee_email": null,
"answers": [
{
"question": 1,
"datafield": "A1B2C3D4",
"answer": "23",
"options": []
}
@@ -285,7 +295,7 @@ Cart position endpoints
"attendee_email": null,
"answers": [
{
"question": 1,
"datafield": "A1B2C3D4",
"answer": "23",
"options": []
}
@@ -303,7 +313,7 @@ Cart position endpoints
"attendee_email": null,
"answers": [
{
"question": 1,
"datafield": "A1B2C3D4",
"answer": "23",
"options": []
}
+37 -26
View File
@@ -5,6 +5,14 @@
Check-in
========
.. note::
This API is affected by an upcoming larger change to our API. The API documentation already reflects the future
version. Features or fields not yet available are marked with **NEW**.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
This page describes special APIs built for ticket scanning apps. For managing check-in configuration or other operations,
please also see :ref:`rest-checkinlists`. The check-in list API also contains endpoints to obtain statistics or log
failed scans.
@@ -24,41 +32,43 @@ Checking a ticket in
passed needs to be from a distinct event.
:query string expand: Expand a field inside the ``position`` object into a full object. Currently ``subevent``, ``item``, ``variation``, and ``answers.question`` are supported. Can be passed multiple times.
:<json string secret: Scanned QR code corresponding to the ``secret`` attribute of a ticket.
:<json string source_type: Type of source the ``secret`` was obtained form. Defaults to ``"barcode"``.
:<json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
:<json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
:<json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
:<json boolean force: Specifies that the check-in should succeed regardless of revoked barcode, previous check-ins or required
:>json string secret: Scanned QR code corresponding to the ``secret`` attribute of a ticket.
:>json string source_type: Type of source the ``secret`` was obtained form. Defaults to ``"barcode"``.
:>json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
:>json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
:>json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
:>json boolean force: Specifies that the check-in should succeed regardless of revoked barcode, previous check-ins or required
questions that have not been filled. This is usually used to upload offline scans that already happened,
because there's no point in validating them since they happened whether they are valid or not. Defaults to ``false``.
:<json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
:>json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
you do not implement question handling in your user interface, you **must**
set this to ``false``. In that case, questions will just be ignored. Defaults
to ``true``.
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
:>json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
Defaults to ``false`` and only works when ``include_pending`` is set on the check-in
list.
:<json object answers: If questions are supported/required, you may/must supply a mapping of question IDs to their
respective answers. The answers should always be strings. In case of (multiple-)choice-type
:>json object answers: If questions are supported/required, you may/must supply a mapping of ``identifier`` (`internal
reference`) of data fields to their respective answers (**NEW**).
For backwards compatibility, a mapping of data field ``id`` to answer is supported, as well.
The answers should always be strings. In case of (multiple-)choice-type
answers, the string should contain the (comma-separated) IDs of the selected options.
:<json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
:>json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
this request twice with the same nonce, the second request will also succeed but will always
create only one check-in object even when the previous request was successful as well. This
allows for a certain level of idempotency and enables you to re-try after a connection failure.
:<json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
:<json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
:<json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
:>json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
:>json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
:>json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
order) when building texts (currently only the ``reason_explanation`` response field).
Defaults to ``false`` in which case the server will determine the language (currently
the event default language, might change in the future with support for the
``Accept-Language`` header).
:<json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
:>json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
object will not reflect the simulated changes.
:>json string status: ``"ok"``, ``"incomplete"``, ``"exchange"``, or ``"error"``
:>json string reason: Reason code, only set on status ``"error"``, see below for possible values.
:>json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null.
:>json object position: Copy of the matching order position (if any was found). The contents are the same as the
:<json string status: ``"ok"``, ``"incomplete"``, ``"exchange"``, or ``"error"``
:<json string reason: Reason code, only set on status ``"error"``, see below for possible values.
:<json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null.
:<json object position: Copy of the matching order position (if any was found). The contents are the same as the
:ref:`order-position-resource`, with the following differences: (1) The ``checkins`` value
will only include check-ins for the selected list. (2) An additional boolean property
``require_attention`` will inform you whether either the order or the item have the
@@ -66,13 +76,13 @@ Checking a ticket in
back to values from a parent product or from invoice addresses. (4) Additional properties
``order__status``, ``order__valid_if_pending``, ``order__require_approval``, and
``order__locale`` are included with details form the order for convenience.
:>json boolean require_attention: Whether or not the ``require_attention`` flag is set on the item or order.
:>json list checkin_texts: List of additional texts to show to the user.
:>json object list: Excerpt of information about the matching :ref:`check-in list <rest-checkinlists>` (if any was found),
:<json boolean require_attention: Whether or not the ``require_attention`` flag is set on the item or order.
:<json list checkin_texts: List of additional texts to show to the user.
:<json object list: Excerpt of information about the matching :ref:`check-in list <rest-checkinlists>` (if any was found),
including the attributes ``id``, ``name``, ``event``, ``subevent``, and ``include_pending``.
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
:<json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
:<json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
:<json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
**Example request**:
@@ -92,7 +102,7 @@ Checking a ticket in
"datetime": null,
"questions_supported": true,
"answers": {
"4": "XS"
"A1B2C3D4": "XS"
}
}
@@ -318,6 +328,7 @@ Performing a ticket search
],
"answers": [
{
"datafield": "A1B2C3D4",
"question": 12,
"answer": "Foo",
"options": []
+21 -10
View File
@@ -5,6 +5,14 @@
Check-in lists
==============
.. note::
This API is affected by an upcoming larger change to our API. The API documentation already reflects the future
version. Features or fields not yet available are marked with **NEW**.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
Resource description
--------------------
@@ -569,6 +577,7 @@ Order position endpoints
],
"answers": [
{
"datafield": "A1B2C3D4",
"question": 12,
"answer": "Foo",
"options": []
@@ -609,27 +618,29 @@ Order position endpoints
:query boolean untrusted_input: If set to true, the lookup parameter is **always** interpreted as a ``secret``, never
as an ``id``. This should be always set if you are passing through untrusted, scanned
data to avoid guessing of ticket IDs.
:<json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
:>json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
you do not implement question handling in your user interface, you **must**
set this to ``false``. In that case, questions will just be ignored. Defaults
to ``true``.
:<json boolean canceled_supported: When this parameter is set to ``true``, the response code ``canceled`` may be
:>json boolean canceled_supported: When this parameter is set to ``true``, the response code ``canceled`` may be
returned. Otherwise, canceled orders will return ``unpaid``. (**Deprecated**, in
the future, this will be ignored and ``canceled`` may always be returned.)
:<json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
:<json boolean force: Specifies that the check-in should succeed regardless of revoked barcode, previous check-ins or required
:>json datetime datetime: Specifies the datetime of the check-in. If not supplied, the current time will be used.
:>json boolean force: Specifies that the check-in should succeed regardless of revoked barcode, previous check-ins or required
questions that have not been filled. This is usually used to upload offline scans that already happened,
because there's no point in validating them since they happened whether they are valid or not. Defaults to ``false``.
:<json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
:<json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
:>json string type: Send ``"exit"`` for an exit and ``"entry"`` (default) for an entry.
:>json boolean ignore_unpaid: Specifies that the check-in should succeed even if the order is in pending state.
Defaults to ``false`` and only works when ``include_pending`` is set on the check-in
list.
:<json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
:>json string nonce: You can set this parameter to a unique random value to identify this check-in. If you're sending
this request twice with the same nonce, the second request will also succeed but will always
create only one check-in object even when the previous request was successful as well. This
allows for a certain level of idempotency and enables you to re-try after a connection failure.
:<json object answers: If questions are supported/required, you may/must supply a mapping of question IDs to their
respective answers. The answers should always be strings. In case of (multiple-)choice-type
:>json object answers: If questions are supported/required, you may/must supply a mapping of ``identifier`` (`internal
reference`) of data fields to their respective answers (**NEW**).
For backwards compatibility, a mapping of data field ``id`` to answer is supported, as well.
The answers should always be strings. In case of (multiple-)choice-type
answers, the string should contain the (comma-separated) IDs of the selected options.
**Example request**:
@@ -648,7 +659,7 @@ Order position endpoints
"questions_supported": true,
"canceled_supported": true,
"answers": {
"4": "XS"
"A1B2C3D4": "XS"
}
}
+250
View File
@@ -0,0 +1,250 @@
.. _rest-datafieldoptions:
Data field options
================
.. note::
This API is part of an upcoming larger change to our API. The API documentation already reflects the future
version. Until the change, this resource is still available at ``/questions/(id)/options/``.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
Resource description
--------------------
:ref:`Data fields <rest-datafields>` of type "choice" or "multiple choice" can have different options attached.
As with data fields, data field options exist on two resource paths, depending on whether
the data field is attached to orders or order positions. Therefore, each endpoint below has two
different URLs.
The options resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the option
position integer An integer, used for sorting
identifier string An arbitrary string that can be used for matching with
other sources.
answer multi-lingual string The displayed value of this option
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/datafields/position/(datafield)/options/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(datafield)/options/
Returns a list of all options for a given data field.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/datafields/position/11/options/ 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": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 2,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 3,
"answer": {"en": "L"}
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query boolean active: If set to ``true`` or ``false``, only data fields with this value for the field ``active`` will be
returned.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param datafield: The ``id`` field of the data field to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/data field does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/datafields/position/(datafield)/options/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(datafield)/options/(id)/
Returns information on one option, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/datafields/position/1/options/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,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param datafield: The ``id`` field of the data field to fetch
:param id: The ``id`` field of the option 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)/datafields/position/(datafield)/options/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(datafield)/options/
Create a new option
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/datafields/position/1/options/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
:param organizer: The ``slug`` field of the organizer of the event/data field to create a option for
:param event: The ``slug`` field of the event to create a option for
:param datafield: The ``id`` field of the data field to create a option for
:statuscode 201: no error
:statuscode 400: The option 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)/datafields/position/(datafield)/options/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(datafield)/options/(id)/
Update an option. 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.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/datafields/position/1/options/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"position": 3
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
: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 data field to modify
:param id: The ``id`` field of the option to modify
:statuscode 200: no error
:statuscode 400: The option 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)/datafields/position/(datafield)/options/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(datafield)/options/(id)/
Delete an option.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/datafields/position/1/options/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 datafield: The ``id`` field of the data field to modify
:param id: The ``id`` field of the option 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.
+428
View File
@@ -0,0 +1,428 @@
.. _rest-datafields:
Data fields
=========
.. note::
This API is part of an upcoming larger change to our API. The API documentation already reflects the future
version. Until the change, please keep using the endpoints at ``/questions/``.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
Resource description
--------------------
Data fields define additional fields related to an order or an order position.
By referencing data fields in a questionnaire, you can have customers or staff fill them out -- during or
after checkout (web or POS), during check-in in the ticket scanner app, or in the backend.
There are two different resource paths for data fields, depending on whether the data field is attached
to orders or order positions. Therefore, each endpoint below has two different URLs.
The data field resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the data field
internal_name string **Required.** An internal field label
identifier string An arbitrary string that can be used for matching with
other sources.
Must be unique per event, across all types of data fields.
type string **Required.** The expected type of data. Valid options:
* ``N`` – number
* ``S`` – one-line string
* ``T`` – multi-line string
* ``B`` – boolean
* ``C`` – choice from a list
* ``M`` – multiple choice from a list
* ``F`` – file upload
* ``D`` – date
* ``H`` – time
* ``W`` – date and time
* ``CC`` – country code (ISO 3666-1 alpha-2)
* ``TEL`` – telephone number
show_during_checkin boolean If ``true``, the value of the data field will be shown
during check-in (if the check-in client supports it).
*Only supported on per-position data fields.*
print_on_invoice boolean If ``true``, the data field will be shown on
invoices.
*Only supported on per-position data fields.*
print_on_invoice_label multi-lingual string **Required if ``print_on_invoice`` is ``true``.** Label
to print on the invoice before the value of this field.
Otherwise `null`.
options list of objects In case of data field type ``C`` or ``M``, this lists the
available objects. Only writable during creation,
use :ref:`separate endpoint <rest-datafieldoptions>` to modify this later.
├ id integer Internal ID of the option
├ position integer An integer, used for sorting
├ identifier string An arbitrary string that can be used for matching with
other sources.
└ answer multi-lingual string The displayed value of this option
valid_number_min string Minimum value for number fields (optional)
valid_number_max string Maximum value for number fields (optional)
valid_date_min date Minimum value for date fields (optional)
valid_date_max date Maximum value for date fields (optional)
valid_datetime_min datetime Minimum value for date and time fields (optional)
valid_datetime_max datetime Maximum value for date and time fields (optional)
valid_file_portrait boolean Turn on file validation for portrait photos
valid_string_length_min integer Minimum length for string fields (optional)
valid_string_length_max integer Maximum length for string fields (optional)
referenced_by_questionnaires list of integers **Read-only.** List of questionnaire IDs that reference
this data field
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/datafields/position/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/
Returns a list of all data fields within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/datafields/position/ 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,
"internal_name": "T-Shirt size",
"type": "C",
"identifier": "WY3TP9SL",
"show_during_checkin": false,
"print_on_invoice": false,
"print_on_invoice_label": null,
"valid_number_min": null,
"valid_number_max": null,
"valid_date_min": null,
"valid_date_max": null,
"valid_datetime_min": null,
"valid_datetime_max": null,
"valid_string_length_min": null,
"valid_string_length_max": null,
"valid_file_portrait": false,
"options": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 0,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 1,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 2,
"answer": {"en": "L"}
}
],
"referenced_by_questionnaires": [1]
}
]
}
: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`` and ``position``.
Default: ``position``
:query string identifier: Only return questions with the given identifier string
:query boolean ask_during_checkin: Only return questions that are or are not to be asked during check-in
:query boolean required: Only return questions that are or are not required to fill in
: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)/datafields/position/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(id)/
Returns information on one data field, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/datafields/position/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,
"internal_name": "T-Shirt size",
"type": "C",
"identifier": "WY3TP9SL",
"show_during_checkin": false,
"print_on_invoice": false,
"print_on_invoice_label": null,
"valid_number_min": null,
"valid_number_max": null,
"valid_date_min": null,
"valid_date_max": null,
"valid_datetime_min": null,
"valid_datetime_max": null,
"valid_string_length_min": null,
"valid_string_length_max": null,
"valid_file_portrait": false,
"options": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 0,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 1,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 2,
"answer": {"en": "L"}
}
],
"referenced_by_questionnaires": [1]
}
: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 question 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)/datafields/position/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/
Create a new question
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/datafields/position/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"internal_name": "T-Shirt size",
"type": "C",
"show_during_checkin": false,
"print_on_invoice": false,
"print_on_invoice_label": null,
"options": [
{
"answer": {"en": "S"}
},
{
"answer": {"en": "M"}
},
{
"answer": {"en": "L"}
}
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"internal_name": "T-Shirt size",
"type": "C",
"identifier": "WY3TP9SL",
"show_during_checkin": false,
"print_on_invoice": false,
"print_on_invoice_label": null,
"valid_number_min": null,
"valid_number_max": null,
"valid_date_min": null,
"valid_date_max": null,
"valid_datetime_min": null,
"valid_datetime_max": null,
"valid_string_length_min": null,
"valid_string_length_max": null,
"valid_file_portrait": false,
"options": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 0,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 1,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 2,
"answer": {"en": "L"}
}
],
"referenced_by_questionnaires": [1]
}
: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)/datafields/position/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(id)/
Update a data field. 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.
You can change all fields of the resource except the ``options`` field. If
you need to update/delete options please use the nested dedicated endpoints.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/datafields/position/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"internal_name": "Clothing size"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"internal_name": "Clothing size",
"type": "C",
"identifier": "WY3TP9SL",
"show_during_checkin": false,
"print_on_invoice": false,
"print_on_invoice_label": null,
"valid_number_min": null,
"valid_number_max": null,
"valid_date_min": null,
"valid_date_max": null,
"valid_datetime_min": null,
"valid_datetime_max": null,
"valid_string_length_min": null,
"valid_string_length_max": null,
"valid_file_portrait": false,
"options": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 0,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 1,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 2,
"answer": {"en": "L"}
}
],
"referenced_by_questionnaires": [1]
}
: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 question to modify
:statuscode 200: no error
: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.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/datafields/position/(id)/
/api/v1/organizers/(organizer)/events/(event)/datafields/order/(id)/
Delete a data field.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/datafields/position/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 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.
+3
View File
@@ -21,6 +21,9 @@ at :ref:`plugin-docs`.
item_add-ons
item_meta_properties
item_program_times
datafields
datafield_options
questionnaires
questions
question_options
quotas
+13 -5
View File
@@ -37,12 +37,9 @@ tax_rule integer The internal
admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others
(such as add-ons or merchandise).
personalized boolean ``true`` for items that require personalization according
personalized boolean **DEPRECATED.** ``true`` for items that require personalization according
to event settings. Only affects system-level fields, not
custom questions. Currently only allowed for products with
``admission`` set to ``true``. For backwards compatibility,
when creating new items and this field is not given, it defaults
to the same value as ``admission``.
custom questions. Defaults to ``false``.
position integer An integer, used for sorting
picture file A product picture to be displayed in the shop
(can be ``null``).
@@ -233,6 +230,17 @@ meta_data object Values set fo
The ``program_times`` attribute has been added.
.. versionchanged:: 2027.1
The ``personalized`` attribute has been deprecated.
For compatibility with existing applications:
* When retrieving products, `personalized` is `True` if any attached questionnaire asks for `attendee_name` and/or `attendee_email`.
* When creating a new product, setting the `personalized` field to `True` will attach the "Attendee data" questionnaire to the new product. If no such questionnaire exists, it will be created.
Before, the `personalized` field defaulted to the same value as `admission`. Now, it defaults to `false`.
* When updating a product, the `personalized` field cannot be changed, returning an HTTP 400 error if attempted.
Notes
-----
+63 -12
View File
@@ -9,6 +9,14 @@
Orders
======
.. note::
This API is affected by an upcoming larger change to our API. The API documentation already reflects the future
version. Features or fields not yet available are marked with **NEW**.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
Order resource
--------------
@@ -119,6 +127,11 @@ cancellation_date datetime Time of order c
plugin_data object Additional data added by plugins.
use_gift_cards list of strings List of unique gift card secrets that are used to pay
for this order.
answers list of objects **NEW** Values of data fields (answers to user-defined questions)
├ datafield string The data field's ``identifier`` (internal reference)
├ answer string Text representation of the answer
├ options list of integers Internal IDs of selected option(s)s (only for choice types)
└ option_identifiers list of strings The ``identifier`` fields of the selected option(s)s
===================================== ========================== =======================================================
@@ -162,6 +175,10 @@ use_gift_cards list of strings List of unique
The ``use_gift_cards`` attribute has been added.
.. versionchanged:: 2027.1
The ``answers`` attribute has been added.
.. _order-position-resource:
Order position resource
@@ -231,12 +248,13 @@ print_logs list of objects List of print j
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
answers list of objects Answers to user-defined questions
├ question integer Internal ID of the answered question
├ answer string Text representation of the answer (URL if answer is a file)
├ question_identifier string The question's ``identifier`` field
answers list of objects Values of data fields (answers to user-defined questions)
├ datafield string **NEW** The data field's ``identifier`` (internal reference)
├ answer string Text representation of the answer
├ options list of integers Internal IDs of selected option(s)s (only for choice types)
└ option_identifiers list of strings The ``identifier`` fields of the selected option(s)s
├ option_identifiers list of strings The ``identifier`` fields of the selected option(s)s
├ question integer **Deprecated.** Internal ID of the data field
└ question_identifier string **Read-only, deprecated.** The data field's ``identifier`` (internal reference)
seat objects The assigned seat. Can be ``null``.
├ id integer Internal ID of the seat instance
├ name string Human-readable seat name
@@ -452,10 +470,11 @@ List of all orders
],
"answers": [
{
"datafield": "WY3TP9SL",
"question": 12,
"question_identifier": "WY3TP9SL",
"answer": "Foo",
"option_idenfiters": [],
"option_identifiers": [],
"options": []
}
],
@@ -488,7 +507,15 @@ List of all orders
],
"refunds": [],
"cancellation_date": null,
"plugin_data": {}
"plugin_data": {},
"answers": [
{
"datafield": "XYZ12345",
"answer": "42",
"option_identifiers": [],
"options": []
}
]
}
]
}
@@ -699,6 +726,7 @@ Fetching individual orders
],
"answers": [
{
"datafield": "WY3TP9SL",
"question": 12,
"question_identifier": "WY3TP9SL",
"answer": "Foo",
@@ -735,7 +763,15 @@ Fetching individual orders
],
"refunds": [],
"cancellation_date": null,
"plugin_data": {}
"plugin_data": {},
"answers": [
{
"datafield": "XYZ12345",
"answer": "42",
"option_identifiers": [],
"options": []
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -1079,7 +1115,8 @@ Creating orders
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
* ``answers``
* ``question``
* ``datafield`` (**NEW**)
* ``question`` (**DEPRECATED**)
* ``answer``
* ``options``
@@ -1114,6 +1151,12 @@ Creating orders
order isn't completely paid after its creation with ``use_gift_cards``, then a subsequent request to the payment
endpoint is needed.
* **NEW** ``answers`` (optional). Values for order-level data fields.
* ``datafield``
* ``answer``
* ``options``
If you want to use add-on products, you need to set the ``positionid`` fields of all positions manually
to incrementing integers starting with ``1``. Then, you can reference one of these
IDs in the ``addon_to`` field of another position. Note that all add_ons for a specific position need to come
@@ -1161,6 +1204,12 @@ Creating orders
"internal_reference": "",
"vat_id": ""
},
"answers": [
{
"datafield": "XYZ12345",
"answer": "42"
}
],
"positions": [
{
"positionid": 1,
@@ -1174,7 +1223,7 @@ Creating orders
"addon_to": null,
"answers": [
{
"question": 1,
"datafield": "WY3TP9SL",
"answer": "23",
"options": []
}
@@ -1684,6 +1733,7 @@ List of all order positions
],
"answers": [
{
"datafield": "WY3TP9SL",
"question": 12,
"question_identifier": "WY3TP9SL",
"answer": "Foo",
@@ -1863,6 +1913,7 @@ Fetching individual positions
],
"answers": [
{
"datafield": "WY3TP9SL",
"question": 12,
"question_identifier": "WY3TP9SL",
"answer": "Foo",
@@ -1971,7 +2022,7 @@ Manipulating individual positions
* ``answers``: If specified, you will need to provide **all** answers for this order position.
Validation is handled the same way as when creating orders through the API. You are therefore
expected to provide ``question``, ``answer``, and possibly ``options``. ``question_identifier``
expected to provide ``datafield``, ``answer``, and possibly ``options``. ``question`` is deprecated. ``question_identifier``
and ``option_identifiers`` will be ignored. As a special case, you can submit the magic value
``"file:keep"`` as the answer to a file question to keep the current value without re-uploading it.
@@ -2067,7 +2118,7 @@ Manipulating individual positions
* ``state``
* ``answers``: Validation is handled the same way as when creating orders through the API. You are therefore
expected to provide ``question``, ``answer``, and possibly ``options``. ``question_identifier``
expected to provide ``datafield``, ``answer``, and possibly ``options``. ``question`` is deprecated. ``question_identifier``
and ``option_identifiers`` will be ignored. As a special case, you can submit the magic value
``"file:keep"`` as the answer to a file question to keep the current value without re-uploading it.
+2 -224
View File
@@ -1,229 +1,7 @@
Question options
================
Resource description
--------------------
.. warning:: This resource is **deprecated**.
Questions of type "choice" or "multiple choice" can have different options attached.
The options resource contains the following public fields:
New applications must use the :ref:`rest-datafieldoptions` resource.
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the option
position integer An integer, used for sorting
identifier string An arbitrary string that can be used for matching with
other sources.
answer multi-lingual string The displayed value of this option
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/(question)/options/
Returns a list of all options for a given question.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questions/11/options/ 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": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
},
{
"id": 2,
"identifier": "DFEMJWMJ",
"position": 2,
"answer": {"en": "M"}
},
{
"id": 3,
"identifier": "W9AH7RDE",
"position": 3,
"answer": {"en": "L"}
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query boolean active: If set to ``true`` or ``false``, only questions with this value for the field ``active`` will be
returned.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param question: The ``id`` field of the question to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/question does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/(question)/options/(id)/
Returns information on one option, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/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,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param question: The ``id`` field of the question to fetch
:param id: The ``id`` field of the option 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)/questions/(question)/options/
Creates a new option
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
:param organizer: The ``slug`` field of the organizer of the event/question to create a option for
:param event: The ``slug`` field of the event to create a option for
:param question: The ``id`` field of the question to create a option for
:statuscode 201: no error
:statuscode 400: The option 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)/questions/(question)/options/(id)/
Update an option. 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.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"position": 3
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"identifier": "LVETRWVU",
"position": 1,
"answer": {"en": "S"}
}
: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 question to modify
:param id: The ``id`` field of the option to modify
:statuscode 200: no error
:statuscode 400: The option 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)/questions/(id)/options/(id)/
Delete an option.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/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 question to modify
:param id: The ``id`` field of the option 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.
+379
View File
@@ -0,0 +1,379 @@
.. _rest-questionnaires:
Questionnaires
=========
.. note::
This API is part of an upcoming larger change to our API. The API documentation already reflects the future
version. Until the change, please keep using the endpoints at ``/questions/``.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
Questionnaires are attached to one or more products, and a product can have multiple questionnaires.
They consist of questionnaire rows, each of which can be one of the following:
Reference to system field
To reference a system field, use one of the identifier strings from the table below in the row's `datafield` attribute.
Currently, system fields are only defined on order positions, not on orders.
=============== =========================== ============================
Container Identifier Label
=============== =========================== ============================
Order position `"@attendee_name_parts"` Attendee name
Order position `"@attendee_email"` Attendee email
Order position `"@company"` Company
Order position `"@street"` Street
Order position `"@zipcode"` ZIP code
Order position `"@city"` City
Order position `"@state"` State
Order position `"@country"` Country
=============== =========================== ============================
Reference to user-defined data field
To reference a :ref:`user-defined data field <rest-datafields>`, use its internal reference (`identifier`)
in the row's `datafield` attribute.
Static explanation text / subheading
To add a static text block, set the row's `datafield` attribute to `null`. In this case, the text
from the `label` attribute is displayed as a subheading, and the `help_text` attribute as a normal paragraph.
Resource description
--------------------
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the questionnaire
type string The type of questionnaire. Valid options:
* ``order_purchase`` — Order-wide, before purchase
* ``position_purchase`` — Per product, before purchase
* ``position_attendee`` — Per product, via attendee link
* ``position_checkin`` — Per product, at check-in
* ``position_hidden`` — Per product, hidden
internal_name string Internal label for the questionnaire.
items list of integers List of item IDs this questionnaire is assigned to.
*Only supported on per-product questionnaires.*
position integer An integer, used for sorting
all_sales_channels boolean If ``true`` (default), the questions from this questionnaire
are asked on all sales channels.
limit_sales_channels list of strings List of sales channel identifiers the questions are asked on
if ``all_sales_channels`` is ``false``.
rows list of objects List of all questionnaire rows.
├ datafield string Reference to a data field (see above)
├ required boolean If ``true``, the question needs to be filled out.
├ label multi-lingual string The field label shown to the customer. In case of a static
text block, the subheading.
├ help_text multi-lingual string The help text shown to the customer.
├ dependency_datafield string ``datafield`` reference of a different row in the same
questionnaire.
The current question will only be shown if the question
given in this attribute is set to one of the values given
in ``dependency_values``.
├ dependency_values list of strings If ``dependency_datafield`` is set to reference a boolean
data field, this should be ``["True"]`` or ``["False"]``.
Otherwise, it should be a list of ``identifier`` values
of question options.
├ rendered_help_text string **Read-only.** Markdown contents from help_text rendered
to HTML
===================================== ========================== =======================================================
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questionnaires/
Returns a list of all questionnaires within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questionnaires/ 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": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"type": "position_purchase",
"internal_name": "Attendee data",
"items": [1, 2],
"position": 0,
"all_sales_channels": true,
"limit_sales_channels": [],
"rows": [
{
"datafield": "@attendee_name_parts",
"required": false,
"label": {"en": "Attendee name"},
"help_text": {},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": ""
},
{
"datafield": "@attendee_email",
"required": false,
"label": {"en": "Attendee email"},
"help_text": {"en": "*We'll send you our program guide a few days before the event.*"},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": "<em>We'll send you our program guide a few days before the event.</em>"
}
]
},
{
"id": 2,
"type": "order_purchase",
"internal_name": "Per-order questions",
"items": [],
"position": 0,
"all_sales_channels": true,
"limit_sales_channels": [],
"rows": [
{
"datafield": "87Y8DCS9",
"required": false,
"label": {"en": "How did you find us?"},
"help_text": {},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": ""
}
]
}
]
}
: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`` and ``position``.
Default: ``position``
:query string type: Only return questionnaires with the given type
:query string items: Only return questionnaires associated with the given item (internal item ID)
:query boolean sales_channel: Only return questionnaires visible on the given sales channel (sales channel identifier string)
: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)/questionnaires/(id)/
Returns information on one questionnaire, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/questionnaires/2/ 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": 2,
"type": "order_purchase",
"internal_name": "Per-order questions",
"items": [],
"position": 0,
"all_sales_channels": true,
"limit_sales_channels": [],
"rows": [
{
"datafield": "87Y8DCS9",
"required": false,
"label": {"en": "How did you find us?"},
"help_text": {},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": ""
}
]
}
: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 questionnaire 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)/questionnaires/
Create a new questionnaire
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/questionnaires/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"type": "order_purchase",
"internal_name": "Per-order questions",
"position": 0,
"all_sales_channels": true,
"rows": [
{
"datafield": "87Y8DCS9",
"required": false,
"label": {"en": "How did you find us?"}
}
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 2,
"type": "order_purchase",
"internal_name": "Per-order questions",
"items": [],
"position": 0,
"all_sales_channels": true,
"limit_sales_channels": [],
"rows": [
{
"datafield": "87Y8DCS9",
"required": false,
"label": {"en": "How did you find us?"},
"help_text": {},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": ""
}
]
}
:param organizer: The ``slug`` field of the organizer of the event to create a questionnaire for
:param event: The ``slug`` field of the event to create a questionnaire for
:statuscode 201: no error
:statuscode 400: The questionnaire 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)/questionnaires/(id)/
Update a questionnaire. 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.
You can only change all ``rows`` en bloc, or keep all of them unchanged in case of a ``PATCH``. If you
change ``rows``, you need to specify all fields of the resource.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/questionnaires/2/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"internal_name": "Questions asked per order"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 2,
"type": "order_purchase",
"internal_name": "Questions asked per order",
"items": [],
"position": 0,
"all_sales_channels": true,
"limit_sales_channels": [],
"rows": [
{
"datafield": "87Y8DCS9",
"required": false,
"label": {"en": "How did you find us?"},
"help_text": {},
"dependency_datafield": null,
"dependency_values": [],
"rendered_help_text": ""
}
]
}
:param organizer: The ``slug`` field of the organizer of the questionnaire to modify
:param event: The ``slug`` field of the event of the questionnaire to modify
:param id: The ``id`` field of the questionnaire to modify
:statuscode 200: no error
:statuscode 400: The questionnaire 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)/questionnaires/(id)/
Delete a questionnaire.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/questionnaires/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 of the questionnaire to modify
:param event: The ``slug`` field of the event of the questionnaire to modify
:param id: The ``id`` field of the questionnaire 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.
+40 -1
View File
@@ -8,10 +8,23 @@
Questions
=========
.. warning:: This resource is **deprecated**. It is only provided for compatibility with existing applications
and should not be used in new development.
New applications must use the :ref:`rest-datafields` and :ref:`rest-questionnaires` resources.
The new version is expected to be released for hosted pretix early December and for self-hosted pretix with
version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
This resource will continue to work in read-only mode on events where the
:ref:`questionnaire configuration <rest-questionnaires>` is compatible with the old data model.
If incompatible configuration is detected, all endpoints return HTTP 410.
Resource description
--------------------
Questions define additional fields that need to be filled out by customers during checkout.
Questions define additional fields related to an order position that need to be filled out by customers during checkout.
The question resource contains the following public fields:
.. rst-class:: rest-resource-table
@@ -84,6 +97,10 @@ dependency_value string An old version
The ``show_during_checkin`` attribute has been added.
.. versionchanged:: 2026.[TODO]
The questions resource was deprecated and replaced by the :ref:`rest-datafields` and :ref:`rest-questionnaires` resources.
Endpoints
---------
@@ -249,6 +266,13 @@ Endpoints
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/questions/
.. warning:: This endpoint is **deprecated** and will be removed as part of an upcoming API change.
The new version is expected to be released for hosted pretix early December and for self-hosted
pretix with version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
After the change, instead create a :ref:`data field <rest-datafields>` first, then add
it to a :ref:`questionnaire <rest-questionnaires>`.
Creates a new question
**Example request**:
@@ -351,6 +375,13 @@ Endpoints
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/questions/(id)/
.. warning:: This endpoint is **deprecated** and will be removed as part of an upcoming API change.
The new version is expected to be released for hosted pretix early December and for self-hosted
pretix with version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
After the change, instead update the relevant :ref:`data field <rest-datafields>`, or
the reference to it inside a :ref:`questionnaire <rest-questionnaires>`.
Update a question. 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.
@@ -437,6 +468,14 @@ Endpoints
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/questions/(id)/
.. warning:: This endpoint is **deprecated** and will be removed as part of an upcoming API change.
The new version is expected to be released for hosted pretix early December and for self-hosted
pretix with version 2027.1.
You can find more information `on our blog <https://pretix.eu/about/en/blog/20261110-questionnaires/>`_.
After the change, insteadremove the questionnaire child from its :ref:`questionnaire <rest-questionnaires>`,
or delete the relevant :ref:`data field <rest-datafields>`.
Delete a question.
**Example request**:
+1 -1
View File
@@ -1,4 +1,4 @@
sphinx==9.1.*
sphinx==9.0.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
+1 -1
View File
@@ -1,5 +1,5 @@
-e ../
sphinx==9.1.*
sphinx==9.0.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
+13 -16
View File
@@ -27,16 +27,16 @@ classifiers = [
]
dependencies = [
"arabic-reshaper==3.0.1", # Support for Arabic in reportlab
"arabic-reshaper==3.1.0", # Support for Arabic in reportlab
"babel",
"BeautifulSoup4==4.15.*",
"bleach==6.4.*",
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=50.0.2",
"css-inline==0.22.*",
"css-inline==0.23.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"dnspython==2.9.*",
"Django[argon2]==5.2.*,>=5.2.17",
"django-bootstrap3==26.3",
"django-compressor==4.6.0",
@@ -46,7 +46,7 @@ dependencies = [
"django-formtools==2.7",
"django-hierarkey==2.0.*,>=2.0.2",
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
"django-i18nfield==1.12.*",
"django-libsass==0.9",
"django-localflavor==5.1",
"django-markup",
@@ -58,17 +58,14 @@ dependencies = [
"django-scopes==2.1.*",
"django-statici18n==2.8.*",
"djangorestframework>=3.18.3",
"dnspython==2.8.*",
"drf_ujson2==1.7.*",
"geoip2==5.*",
"importlib_metadata==9.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.11.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"markdown==3.11.2",
"mt-940==4.30.*",
"oauthlib==3.3.*",
"openpyxl==3.1.*",
@@ -82,23 +79,23 @@ dependencies = [
"protobuf==7.36.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.11",
"pycparser==3.12",
"pycryptodome==3.24.*",
"pypdf==6.20.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==3.0.*",
"python-dateutil==4.0.*",
"pytz",
"pytz-deprecation-shim==0.1.*",
"pyuca",
"qrcode==8.2",
"redis==7.4.*",
"reportlab==5.0.*",
"reportlab==5.1.*",
"requests==2.34.*",
"sentry-sdk==2.71.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2026093003",
"tlds>=2026102900",
"tqdm==4.*",
"ua-parser==1.0.*",
"vobject==0.9.*",
@@ -112,17 +109,17 @@ dev = [
"aiohttp==3.14.*",
"coverage",
"coveralls",
"fakeredis==2.39.*",
"flake8==7.4.*",
"fakeredis==2.40.*",
"flake8==7.5.*",
"freezegun",
"isort==9.0.*",
"pep8-naming==0.15.*",
"potypo",
"pytest-asyncio>=1.4.0",
"pytest-asyncio>=1.4.1.post1",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.16.*",
"pytest-mock==3.17.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest-playwright",
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.9.0"
__version__ = "2026.10.0.dev0"
+9 -9
View File
@@ -1282,7 +1282,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(
'Orders cannot have more than %(max)s positions.' % {'max': settings.PRETIX_MAX_ORDER_SIZE}
)
errs = [{} for p in data]
errs = defaultdict(dict)
if any([p.get('positionid') for p in data]):
if not all([p.get('positionid') for p in data]):
for i, p in enumerate(data):
@@ -1311,16 +1311,16 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
last_posid = p['positionid']
elif any([p.get('addon_to') for p in data]):
errs = [
{'positionid': ["If you set addon_to on any position, you need to specify position IDs manually."]}
for p in data
]
errs = {
i: {'positionid': ["If you set addon_to on any position, you need to specify position IDs manually."]}
for i, p in enumerate(data)
}
else:
for i, p in enumerate(data):
p['positionid'] = i + 1
if any(errs):
raise ValidationError(errs)
if errs:
raise ValidationError(dict(errs))
return data
def create(self, validated_data):
@@ -1437,7 +1437,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
# use further down
del quota_diff_for_locking, voucher_diff_for_locking, seat_diff_for_locking
errs = [{} for p in positions_data]
errs = defaultdict(dict)
for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'):
@@ -1566,7 +1566,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
)
]
if any(errs):
if errs:
raise ValidationError({'positions': errs})
if validated_data.get('locale', None) is None:
+7 -9
View File
@@ -19,6 +19,7 @@
# 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 collections import defaultdict
from decimal import Decimal
from rest_framework import serializers
@@ -38,23 +39,20 @@ class VoucherListSerializer(serializers.ListSerializer):
codes = set()
seats = set()
errs = []
err = False
for voucher_data in validated_data:
errs = defaultdict(dict)
for i, voucher_data in enumerate(validated_data):
if voucher_data.get('seat') and (voucher_data.get('seat'), voucher_data.get('subevent')) in seats:
err = True
errs.append({'code': ['Duplicate seat ID in request.']})
errs[i] = {'code': ['Duplicate seat ID in request.']}
continue
else:
seats.add((voucher_data.get('seat'), voucher_data.get('subevent')))
if voucher_data['code'] in codes:
err = True
errs.append({'code': ['Duplicate voucher code in request.']})
errs[i] = {'code': ['Duplicate voucher code in request.']}
else:
codes.add(voucher_data['code'])
errs.append({})
if err:
if errs:
raise ValidationError(errs)
return super().create(validated_data)
+2
View File
@@ -264,6 +264,8 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
)
)
profile['oidc_claims'] = userinfo
return profile
+3 -3
View File
@@ -29,6 +29,7 @@ from typing import List, Optional, Protocol
from django.conf import settings
from django.db import DatabaseError, transaction
from django.utils.functional import classproperty
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -101,9 +102,8 @@ class OutboundSyncProvider:
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
@classmethod
@property
def display_name(cls):
@classproperty
def display_name(cls): # noqa: N805
return str(cls.identifier)
@classmethod
+22 -19
View File
@@ -131,6 +131,27 @@ class PeppolIdValidator:
def __init__(self, validate_online=False):
self.validate_online = validate_online
def _validate_peppol_id_online(self, value):
base_hostnames = ['participant.sml.prod.tech.peppol.org', 'participant.sml.test.tech.peppol.org']
smp_id = base64.b32encode(hashlib.sha256(value.lower().encode()).digest()).decode().rstrip("=")
for base_hostname in base_hostnames:
smp_domain = f'{smp_id}.iso6523-actorid-upis.{base_hostname}'
resolver = dns.resolver.Resolver()
try:
answers = resolver.resolve(smp_domain, 'NAPTR', lifetime=1.0)
if answers:
return value
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
# ID not registered, do not set found=True
pass
except Exception: # noqa
# Error likely on our end or infrastructure is down, allow user to proceed
return value
raise ValidationError(
_("The Peppol participant ID is not registered on the Peppol network."),
)
def __call__(self, value):
if ":" not in value:
raise ValidationError(_("A Peppol participant ID always starts with a prefix, followed by a colon (:)."))
@@ -146,25 +167,7 @@ class PeppolIdValidator:
params={"number": prefix})
if self.validate_online:
base_hostnames = ['participant.sml.prod.tech.peppol.org', 'participant.sml.test.tech.peppol.org']
smp_id = base64.b32encode(hashlib.sha256(value.lower().encode()).digest()).decode().rstrip("=")
for base_hostname in base_hostnames:
smp_domain = f'{smp_id}.iso6523-actorid-upis.{base_hostname}'
resolver = dns.resolver.Resolver()
try:
answers = resolver.resolve(smp_domain, 'NAPTR', lifetime=1.0)
if answers:
return value
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
# ID not registered, do not set found=True
pass
except Exception: # noqa
# Error likely on our end or infrastructure is down, allow user to proceed
return value
raise ValidationError(
_("The Peppol participant ID is not registered on the Peppol network."),
)
value = self._validate_peppol_id_online(value)
return value
+21 -4
View File
@@ -151,12 +151,29 @@ class LoggingMixin:
logentry = LogEntry(content_object=self, user=user, action_type=action, event=event,
organizer_id=organizer_id, **kwargs)
if isinstance(data, dict):
sensitivekeys = ['password', 'secret', 'api_key']
# sensitivekeys are matched whether contained, not exactly
sensitivekeys = [
'auth',
'key',
'token',
'password',
'Password',
'_pass',
'salt',
'secret',
]
for sensitivekey in sensitivekeys:
for k, v in data.items():
if (sensitivekey in k) and v:
def clean_recursive(d):
data = d
data_iterator = d.items() if isinstance(d, dict) else enumerate(d)
for k, v in data_iterator:
if v and any([sensitivekey in str(k) for sensitivekey in sensitivekeys]):
data[k] = "********"
elif isinstance(v, (list, dict)):
data[k] = clean_recursive(v)
return data
data = clean_recursive(data)
logentry.data = json.dumps(data, cls=CustomJSONEncoder, sort_keys=True)
elif data:
+20 -6
View File
@@ -83,6 +83,8 @@ from .organizer import Organizer, Team
if TYPE_CHECKING:
from hierarkey.proxy import HierarkeyProxy
from .organizer import SalesChannel
logger = logging.getLogger(__name__)
@@ -336,12 +338,18 @@ class EventMixin:
return safe_string(json.dumps(eventdict))
@classmethod
def annotated(cls, qs, channel, voucher=None):
def annotated(cls, qs, channel: "str | SalesChannel | list[SalesChannel]", voucher=None):
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
# makes the query SIGNIFICANTLY faster
from pretix.base.models import Item, ItemVariation, Quota, SalesChannel
assert isinstance(channel, (SalesChannel, str))
assert isinstance(channel, (SalesChannel, str, list))
if isinstance(channel, SalesChannel):
channel = [channel]
if isinstance(channel, list):
assert all(isinstance(c, SalesChannel) for c in channel)
sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher).filter(
Q(variations__isnull=True)
@@ -364,8 +372,8 @@ class EventMixin:
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__identifier=channel))
else:
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels=channel))
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__in=channel))
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__in=channel))
if voucher:
if voucher.variation_id:
@@ -1661,11 +1669,17 @@ class SubEvent(EventMixin, LoggedModel):
return qs_annotated
@classmethod
def annotated(cls, qs, channel, voucher=None):
def annotated(cls, qs, channel: "str | SalesChannel | list[SalesChannel]", voucher=None):
from .items import SubEventItem, SubEventItemVariation
from .organizer import SalesChannel
assert isinstance(channel, (str, SalesChannel))
assert isinstance(channel, (SalesChannel, str, list))
if isinstance(channel, SalesChannel):
channel = [channel]
if isinstance(channel, list):
assert all(isinstance(c, SalesChannel) for c in channel)
qs = super().annotated(qs, channel, voucher=voucher)
qs = qs.annotate(
+9 -2
View File
@@ -313,7 +313,14 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False, allow_
# makes the query SIGNIFICANTLY faster
from .organizer import SalesChannel
assert isinstance(channel, (SalesChannel, str))
assert isinstance(channel, (SalesChannel, str, list))
if isinstance(channel, SalesChannel):
channel = [channel]
if isinstance(channel, list):
assert all(isinstance(c, SalesChannel) for c in channel)
q = (
# IMPORTANT: If this is updated, also update the ItemVariation query
# in models/event.py: EventMixin.annotated()
@@ -325,7 +332,7 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False, allow_
if isinstance(channel, str):
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
else:
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__in=channel))
if not allow_addons:
q &= Q(Q(category__isnull=True) | Q(category__is_addon=False))
+1
View File
@@ -330,5 +330,6 @@ def register_default_organizer_permissions(sender, **kwargs):
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "No access")),
PermissionOption(actions=("read",), label=pgettext_lazy("permission_level", "View")),
],
help_text=_("View access includes the option to e.g. abort pending emails or retry failed emails."),
),
]
+1 -1
View File
@@ -22,9 +22,9 @@
import os
import sys
from enum import Enum
from importlib import metadata
from typing import List
import importlib_metadata as metadata
from django.apps import AppConfig, apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
+2 -1
View File
@@ -2176,7 +2176,8 @@ class OrderChangeManager:
subevent=a.subevent,
voucher=None,
base_qs=Item.objects.filter(pk=a.item.pk),
allow_addons=True
allow_addons=True,
ignore_hide_sold_out_for_item_ids={a.item.pk}
)
if (not items) or items[0].current_unavailability_reason:
return False
+2 -2
View File
@@ -126,10 +126,10 @@ def generate_seats(event, subevent, plan, mapping, blocked_guids=None):
for s in current_seats.values():
if s.has_op:
raise SeatProtected(_('You can not change the plan since seat "%s" is not present in the new plan and is '
'already sold.', s.name))
'already sold.'), s.name)
if s.has_v:
raise SeatProtected(_('You can not change the plan since seat "%s" is not present in the new plan and is '
'already used in a voucher.', s.name))
'already used in a voucher.'), s.name)
Seat.objects.bulk_create(create_seats)
CartPosition.objects.filter(addon_to__seat__in=[s.pk for s in current_seats.values()]).delete()
+4 -1
View File
@@ -1199,11 +1199,14 @@ The ``sender`` keyword argument will contain the organizer.
customer_signed_in = OrganizerPluginSignal(allow_legacy_plugins=True)
"""
Arguments: ``customer``
Arguments: ``customer``, ``profile``
This signal is sent out every time a customer signs in. The ``customer`` object
is given as the first argument.
In case of SSO login, the full claims will be passed in the ``profile`` argument,
e.g. as ``oidc_claims`` for OIDC. Otherwise, ``profile`` will be ``None``.
The ``sender`` keyword argument will contain the organizer.
"""
+1 -1
View File
@@ -23,10 +23,10 @@ import json
import logging
import pathlib
import re
from importlib import metadata
from urllib.parse import urljoin
from urllib.request import urlopen
import importlib_metadata as metadata
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
@@ -16,6 +16,7 @@
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixcontrol/scss/main.scss" %}" />
<link rel="stylesheet" type="text/x-scss" href="{% static "lightbox/css/lightbox.scss" %}" />
{% endcompress %}
{% vite_importmap %}
{% if DEBUG %}
<script type="text/javascript" src="{% url 'javascript-catalog' lang=request.LANGUAGE_CODE %}"></script>
{% else %}
@@ -87,7 +88,6 @@
<meta name="theme-color" content="#3b1c4a">
<meta name="referrer" content="origin">
{% vite_importmap %}
{% block custom_header %}{% endblock %}
</head>
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}"
@@ -225,6 +225,14 @@
</div>
</div>
<div class="col-md-3" id="editor-toolbox-area">
{% if checkin_qr_warning %}
<div class="alert alert-warning" id="nocheckin-notice">
{% blocktrans trimmed %}
This layout does not contain a QR code that can be used to scan tickets at check-in.
{% endblocktrans %}
</div>
{% endif %}
<div class="panel panel-default" id="toolbox">
<div class="panel-heading">
<span id="toolbox-heading">
+2 -1
View File
@@ -31,7 +31,8 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import importlib_metadata as metadata
from importlib import metadata as metadata
from django.conf import settings
from django.contrib import messages
from django.db import transaction
+2
View File
@@ -65,6 +65,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
)
maxfilesize = settings.FILE_UPLOAD_MAX_SIZE_IMAGE
minfilesize = 10
checkin_qr_warning = False
title = None
def get(self, request, *args, **kwargs):
@@ -292,6 +293,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
ctx['title'] = self.title
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
ctx['maxfilesize'] = self.maxfilesize
ctx['checkin_qr_warning'] = self.checkin_qr_warning
return ctx
+6 -6
View File
@@ -4,16 +4,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-07-31 18:00+0000\n"
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
">\n"
"PO-Revision-Date: 2026-10-29 17:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/"
"da/>\n"
"Language: da\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 2026.7.1\n"
"X-Generator: Weblate 2026.10.1\n"
#: pretix/_base_settings.py
msgid "English"
@@ -34962,7 +34962,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_quota_left.html
#, python-format
msgid "%(num)s currently available"
msgstr "%(num)s pt. tilgængelig"
msgstr "%(num)s pt. ledig"
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html
+32 -35
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-10-26 23:00+0000\n"
"Last-Translator: Tim <plicnetwork@gmail.com>\n"
"PO-Revision-Date: 2026-10-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -7656,17 +7656,14 @@ msgid "This gift card was used in the meantime. Please try again."
msgstr "Mientras tanto, esta tarjeta de regalo se utilizó. Inténtalo de nuevo."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card not found."
msgstr "Código de la tarjeta de regalo"
msgstr "No se ha encontrado la tarjeta regalo."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Only test gift cards can be used in test mode."
msgid "Only test gift card can be used in test mode and vice versa."
msgstr ""
"En el modo de prueba solo se pueden utilizar tarjetas regalo de prueba."
"En el modo de prueba solo se pueden utilizar tarjetas regalo de prueba, y "
"viceversa."
#: pretix/base/payment.py
msgid ""
@@ -16689,12 +16686,9 @@ msgid "Phone"
msgstr "Teléfono"
#: pretix/control/forms/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgctxt "sso_oidc"
msgid "Base URL / issuer"
msgstr "URL de base"
msgstr "URL base / emisor"
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
@@ -16767,21 +16761,16 @@ msgstr ""
"endpoint de autorización. Introducir como: {example}"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgid "Base URL / issuer"
msgstr "URL de base"
msgstr "URL base / emisor"
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr "Invalidar el antiguo secreto del cliente y generar uno nuevo"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "will be retried"
msgid "(will be generated)"
msgstr "se volverá a intentar"
msgstr "(se generará)"
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
@@ -26404,16 +26393,16 @@ msgstr "Cliente SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, fuzzy
#| msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr "Crear un nuevo cliente SSO"
msgstr "Crear credenciales para un nuevo cliente de SSO"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
"Una vez enviado este formulario, se generarán las credenciales de cliente "
"para tu cliente SSO."
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -26423,6 +26412,9 @@ msgid ""
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
"Para obtener más información, consulta nuestra documentación en <a href=\"%"
"(url)s\">Uso de Pretix como proveedor de SSO con clientes de SSO externos</a>"
"."
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
@@ -26430,6 +26422,9 @@ msgid ""
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
"\n"
" \t«Iniciar sesión con pretix» en sistemas externos\n"
" "
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
@@ -26454,17 +26449,13 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr "Este proveedor de SSO no se puede eliminar porque ya se ha utilizado."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#, fuzzy
#| msgid "Delete SSO provider:"
msgid "External SSO provider:"
msgstr "Eliminar proveedor de SSO:"
msgstr "Proveedor externo de SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, fuzzy
#| msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr "Agregar un nuevo proveedor SSO"
msgstr "Conectarse a un proveedor externo de SSO"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
@@ -26473,6 +26464,8 @@ msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
"Para obtener más información, consulta nuestra documentación sobre <a "
"href=\"%(url)s\" target=\"_blank\">El uso de pretix como cliente de SSO</a>."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgctxt "sso"
@@ -26485,6 +26478,9 @@ msgid ""
" \tConnect to an external login provider\n"
" "
msgstr ""
"\n"
" \tConectarse a un proveedor de inicio de sesión externo\n"
" "
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
@@ -30025,15 +30021,10 @@ msgid "The provider has been created."
msgstr "Se ha creado el proveedor."
#: pretix/control/views/organizer.py
#, fuzzy
#| msgid ""
#| "Your changes have been saved. Please note down the following client "
#| "secret, it will never be shown again: {secret}"
msgid ""
"Please note down the following client secret, it will never be shown again."
msgstr ""
"Se han guardado los cambios. Anote el siguiente secreto de cliente; no se "
"volverá a mostrar nunca más: {secret}"
"Anota el siguiente secreto de cliente; no volverá a mostrarse nunca más."
#: pretix/control/views/organizer.py
msgid ""
@@ -32586,6 +32577,12 @@ msgid ""
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
"Es posible que se envíen varios correos electrónicos a la misma dirección de "
"correo si un pedido contiene varios productos para asistentes y se restringe "
"el acceso a los productos y, al mismo tiempo, se limita el envío de correos "
"únicamente a los asistentes. Esto es intencionado, ya que cada uno de ellos "
"se vincula a su propia página de pedido independiente, restringida "
"exclusivamente a ese producto."
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
@@ -33498,7 +33495,7 @@ msgstr "MobilePay"
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
msgstr "Wero"
#: pretix/plugins/stripe/payment.py
msgid "Destination"
+34 -36
View File
@@ -4,10 +4,10 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-10-27 17:00+0000\n"
"PO-Revision-Date: 2026-10-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
"fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -7707,16 +7707,14 @@ msgid "This gift card was used in the meantime. Please try again."
msgstr "Cette carte-cadeau a été utilisée entre-temps. Veuillez réessayer."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card not found."
msgstr "Code carte-cadeau"
msgstr "Carte cadeau introuvable."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Only test gift cards can be used in test mode."
msgid "Only test gift card can be used in test mode and vice versa."
msgstr "Seules les cartes-cadeaux de test peuvent être utilisées en mode test."
msgstr ""
"Seule la carte cadeau de test peut être utilisée en mode test, et "
"inversement."
#: pretix/base/payment.py
msgid ""
@@ -16850,12 +16848,9 @@ msgid "Phone"
msgstr "Téléphone"
#: pretix/control/forms/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgctxt "sso_oidc"
msgid "Base URL / issuer"
msgstr "URL de base"
msgstr "URL de base / émetteur"
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
@@ -16928,21 +16923,16 @@ msgstr ""
"Saisissez-les sous la forme suivante : {example}"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgid "Base URL / issuer"
msgstr "URL de base"
msgstr "URL de base / émetteur"
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr "Invalider l’ancienne clé secrète client et en générer une nouvelle"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "will be retried"
msgid "(will be generated)"
msgstr "sera réessayé"
msgstr "(sera généré)"
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
@@ -26606,16 +26596,16 @@ msgstr "Client SSO :"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, fuzzy
#| msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr "Créer un nouveau client SSO"
msgstr "Créer des identifiants pour un nouveau client SSO"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
"Une fois ce formulaire envoyé, les identifiants de votre client SSO seront "
"générés."
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -26625,6 +26615,9 @@ msgid ""
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
"Pour plus d'informations, consultez notre documentation sur <a href=\"%(url)"
"s\">L'utilisation de Pretix en tant que fournisseur SSO avec des clients SSO "
"externes</a>."
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
@@ -26632,6 +26625,9 @@ msgid ""
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
"\n"
" \t« Se connecter avec pretix » dans les systèmes externes\n"
" "
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
@@ -26659,17 +26655,13 @@ msgstr ""
"Ce fournisseur SSO ne peut pas être supprimé car il a déjà été utilisé."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#, fuzzy
#| msgid "Delete SSO provider:"
msgid "External SSO provider:"
msgstr "Supprimer le fournisseur SSO :"
msgstr "Fournisseur SSO externe :"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, fuzzy
#| msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr "Créer un nouveau fournisseur SSO"
msgstr "Se connecter à un fournisseur SSO externe"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
@@ -26678,6 +26670,8 @@ msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
"Pour plus d'informations, consultez notre documentation sur <a href=\"%(url)"
"s\" target=\"_blank\">L'utilisation de pretix en tant que client SSO</a>."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgctxt "sso"
@@ -26690,6 +26684,9 @@ msgid ""
" \tConnect to an external login provider\n"
" "
msgstr ""
"\n"
" \tSe connecter à un fournisseur d'authentification externe\n"
" "
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
@@ -30269,15 +30266,10 @@ msgid "The provider has been created."
msgstr "Le fournisseur a été créé."
#: pretix/control/views/organizer.py
#, fuzzy
#| msgid ""
#| "Your changes have been saved. Please note down the following client "
#| "secret, it will never be shown again: {secret}"
msgid ""
"Please note down the following client secret, it will never be shown again."
msgstr ""
"Vos modifications ont été enregistrées. Veuillez noter la clé secrète client "
"suivante, elle ne sera plus jamais affichée : {secret}"
"Veuillez noter le secret client suivant ; il ne s'affichera plus jamais."
#: pretix/control/views/organizer.py
msgid ""
@@ -32862,6 +32854,12 @@ msgid ""
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
"Il se peut que plusieurs e-mails soient envoyés à la même adresse e-mail si "
"une commande comprend plusieurs produits destinés à des participants, "
"lorsque vous limitez l'accès aux produits tout en restreignant les e-mails "
"aux seuls participants. Ce comportement est normal, car chacun de ces "
"produits est associé à sa propre page de commande, dont l'accès est limité à "
"ce produit uniquement."
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
@@ -33777,7 +33775,7 @@ msgstr "MobilePay"
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
msgstr "Wero"
#: pretix/plugins/stripe/payment.py
msgid "Destination"
+7 -8
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-04-21 11:05+0000\n"
"Last-Translator: Sandra Rial Pérez <sandrarial@gestiontickets.online>\n"
"PO-Revision-Date: 2026-11-02 11:32+0000\n"
"Last-Translator: Isaac Raño <isaac@ovixia.com>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix/"
"gl/>\n"
"Language: gl\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.17\n"
"X-Generator: Weblate 2026.10.1\n"
#: pretix/_base_settings.py
msgid "English"
@@ -93,7 +93,7 @@ msgstr "Hebreo"
#: pretix/_base_settings.py
msgid "Hungarian"
msgstr ""
msgstr "Húngaro"
#: pretix/_base_settings.py
msgid "Indonesian"
@@ -153,7 +153,7 @@ msgstr "Español (América Latina)"
#: pretix/_base_settings.py
msgid "Thai"
msgstr ""
msgstr "Tailandés"
#: pretix/_base_settings.py
msgid "Turkish"
@@ -409,7 +409,7 @@ msgstr ""
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
#, python-format
msgid "You've been invited to join %(organizer)s"
msgstr ""
msgstr "Convidáronte a unirte a %(organizer)s"
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
msgid "This user already has been invited for this team."
@@ -429,9 +429,8 @@ msgid "Medium connected to other event"
msgstr "O medio conectou a outro acontecemento"
#: pretix/api/views/checkin.py
#, fuzzy
msgid "You cannot exchange a medium for a medium."
msgstr "No puede cancelar este pedido."
msgstr "Non se pode trocar un medio por outro."
#: pretix/api/views/checkin.py
#, fuzzy
+33 -8
View File
@@ -7,16 +7,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-10-19 02:00+0000\n"
"PO-Revision-Date: 2026-11-03 08:12+0000\n"
"Last-Translator: Christiaan de Die le Clercq <contact@techwolf12.nl>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
">\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.10.1\n"
"X-Generator: Weblate 2026.11\n"
#: pretix/_base_settings.py
msgid "English"
@@ -8379,12 +8379,12 @@ msgstr "Niet ingesteld"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
msgstr ""
msgstr "Een relatieve datum kan niet worden uitgedrukt als \"voor\" voor \"{}\""
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
msgstr ""
msgstr "Een relatieve datum kan niet worden uitgedrukt als \"na\" voor \"{}\""
#: pretix/base/reldate.py
#, python-brace-format
@@ -8415,7 +8415,7 @@ msgstr "Evenement geannuleerd"
#: pretix/base/services/cancelevent.py
msgid "Confirm event cancellation and bulk refund"
msgstr ""
msgstr "Evenementannulering en bulkterugbetaling bevestigen"
#: pretix/base/services/cart.py pretix/base/services/modelimport.py
#: pretix/base/services/orders.py
@@ -9355,6 +9355,7 @@ msgstr "Een voucher kan niet worden aangemaakt zonder code."
msgid ""
"Voucher codes must be unique. Code \"{code}\" already exists in this import."
msgstr ""
"Vouchercodes moeten uniek zijn. Code \"{code}\" bestaat al in deze import."
#: pretix/base/services/modelimport.py
#, python-brace-format
@@ -9363,13 +9364,18 @@ msgid ""
msgid_plural ""
"Voucher codes must be unique. Import contains existing voucher codes {code}."
msgstr[0] ""
"Vouchercodes moeten uniek zijn. Import bevat al bestaande vouchercode {code}."
msgstr[1] ""
"Vouchercodes moeten uniek zijn. Import bevat al bestaande vouchercodes {code}"
"."
#: pretix/base/services/modelimport.py
msgid ""
"Vouchers could not be imported, probably due to a voucher code already being "
"in use."
msgstr ""
"Vouchers konden niet worden geïmporteerd, waarschijnlijk omdat een "
"vouchercode al in gebruik is."
#: pretix/base/services/orders.py
msgid ""
@@ -10431,7 +10437,7 @@ msgstr ""
#: pretix/base/settings.py
msgid "No dates match your criteria."
msgstr ""
msgstr "Geen data voldoen aan je zoekcriteria."
#: pretix/base/settings.py
msgctxt "subevents"
@@ -10632,6 +10638,10 @@ msgid ""
"their order without selecting a payment method. This is useful whenever "
"orders are not created by the same person who is making the payment."
msgstr ""
"Als uitgestelde betaling is toegestaan op een verkoopkanaal, kunnen klanten "
"hun bestelling afronden zonder een betaalmethode te kiezen. Dit is handig "
"wanneer bestellingen niet worden geplaatst door dezelfde persoon als degene "
"die de betaling uitvoert."
#: pretix/base/settings.py
msgid "Show start date"
@@ -11603,6 +11613,10 @@ msgid ""
"the email address above. Please note that you still need to add a contact "
"email address that will be shared with all emails you send."
msgstr ""
"Als je dit instelt, verwijst de contactlink in de voettekst hiernaartoe in "
"plaats van naar het bovenstaande e-mailadres. Let op dat je nog steeds een "
"contact-e-mailadres moet toevoegen dat wordt meegestuurd met alle e-mails "
"die je verzendt."
#: pretix/base/settings.py pretix/control/forms/event.py
msgid "Imprint URL"
@@ -13494,6 +13508,8 @@ msgid ""
"Don't share this code with anyone. The %(instance)s team will never ask you "
"for it."
msgstr ""
"Deel deze code met niemand. Het %(instance)s-team zal je hier nooit om "
"vragen."
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
#: pretix/base/templates/pretixbase/email/export_failed.txt
@@ -13502,6 +13518,8 @@ msgid ""
"Thanks, \n"
"The %(instance)s Team"
msgstr ""
"Bedankt, \n"
"Het %(instance)s-team"
#: pretix/base/templates/pretixbase/email/email_footer.html
#, python-format
@@ -13698,11 +13716,14 @@ msgstr "Ga verder in een nieuw tabblad"
#: pretix/base/templates/pretixbase/framebreak.html
msgid "For security reasons, the following step is only possible in a new tab."
msgstr ""
"Om veiligheidsredenen is de volgende stap alleen mogelijk in een nieuw "
"tabblad."
#: pretix/base/templates/pretixbase/framebreak.html
msgid ""
"If the new tab did not open automatically, please click the following button:"
msgstr ""
"Als het nieuwe tabblad niet automatisch opent, klik dan op de volgende knop:"
#: pretix/base/templates/pretixbase/framebreak.html
#: pretix/presale/templates/pretixpresale/event/cookies.html
@@ -37134,6 +37155,10 @@ msgid ""
"before retrying can help. As a last step, try using a different browser or "
"another device (for example, a desktop or laptop computer)."
msgstr ""
"Als het probleem zich blijft voordoen, kan het helpen om alle "
"browservensters te sluiten en cookies te verwijderen voordat je het opnieuw "
"probeert. Als laatste stap kun je proberen een andere browser of een ander "
"apparaat (bijvoorbeeld een desktop of laptop) te gebruiken."
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Your membership"
+151 -54
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-09-02 08:35+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"PO-Revision-Date: 2026-10-30 03:00+0000\n"
"Last-Translator: vihumelo <victordimelo@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
"Language: pt_BR\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 2026.8.1\n"
"X-Generator: Weblate 2026.10.1\n"
#: pretix/_base_settings.py
msgid "English"
@@ -411,7 +411,7 @@ msgstr ""
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
#, python-format
msgid "You've been invited to join %(organizer)s"
msgstr ""
msgstr "Você recebeu um convite para integrar %(organizer)s"
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
msgid "This user already has been invited for this team."
@@ -607,6 +607,9 @@ msgid ""
"This includes related events like creation, deletion, opening or closing of "
"quotas. No webhook is sent for changes to the resulting availability."
msgstr ""
"Isso inclui eventos relacionados, como a criação, exclusão, abertura ou "
"fechamento de cotas. Nenhum webhook é enviado em decorrência de alterações "
"na disponibilidade."
#: pretix/api/webhooks.py
msgid "Shop taken live"
@@ -1678,7 +1681,7 @@ msgstr "Endereço"
#: pretix/base/settings.py pretix/control/forms/filter.py
#: pretix/plugins/checkinlists/exporters.py pretix/plugins/reports/exporters.py
msgid "ZIP code"
msgstr "Código Postal"
msgstr "CEP"
#: pretix/base/exporters/invoices.py pretix/base/exporters/orderlist.py
#: pretix/base/forms/questions.py pretix/base/modelimport_orders.py
@@ -3622,6 +3625,9 @@ msgid ""
"The Peppol participant ID prefix %(number)s is not known to our system. "
"Please reach out to us if you are sure this ID is correct."
msgstr ""
"Nosso sistema não reconhece o prefixo %(number)s do identificador de "
"participante da Peppol. Se você tem certeza de que esse identificador está "
"correto, entre em contato conosco."
#: pretix/base/invoicing/peppol.py
#, python-format
@@ -5600,6 +5606,9 @@ msgid ""
"prior to their usage. Therefore, the selected media policy does not make "
"sense for this media type."
msgstr ""
"O tipo de mídia selecionado exige que todas as mídias sejam cadastradas no "
"sistema antes de serem utilizadas. Portanto, a política de mídia selecionada "
"não é adequada para esse tipo de mídia."
#: pretix/base/models/items.py
msgid ""
@@ -5821,7 +5830,7 @@ msgstr "Código do país (ISO 3166-1 alfa-2)"
#: pretix/base/models/items.py
msgid "Asked on"
msgstr ""
msgstr "Pergunta feita em"
#: pretix/base/models/items.py pretix/base/models/organizer.py
msgid ""
@@ -6171,12 +6180,12 @@ msgstr "retornou"
#: pretix/base/models/media.py
msgctxt "reusable_medium"
msgid "Claim token"
msgstr ""
msgstr "Token de vinculação"
#: pretix/base/models/media.py
msgctxt "reusable_medium"
msgid "Label"
msgstr ""
msgstr "Rótulo"
#: pretix/base/models/media.py
#, fuzzy
@@ -6190,6 +6199,9 @@ msgid ""
"validity. If multiple tickets are valid at once, this will lead to failed "
"check-ins."
msgstr ""
"Se você vincular mais de um ingresso, certifique-se de que os períodos de "
"validade não se sobreponham. Se vários ingressos estiverem válidos ao mesmo "
"tempo, poderão ocorrer falhas no check-in."
#: pretix/base/models/memberships.py
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
@@ -8119,6 +8131,8 @@ msgstr "Apenas API"
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"Esta opção só será exibida no menu se o usuário tiver permissão para acessar "
"as configurações gerais."
#: pretix/base/permissions.py
msgctxt "permission_level"
@@ -8136,6 +8150,8 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Isso inclui o acesso a todas as configurações que não estão explicitamente "
"listadas abaixo, incluindo as configurações dos plugins."
#: pretix/base/permissions.py
#: pretix/control/templates/pretixcontrol/event/payment.html
@@ -8165,7 +8181,7 @@ msgstr "Produtos, cotas e perguntas"
#: pretix/base/permissions.py
msgid "Also includes related objects like categories or discounts."
msgstr ""
msgstr "Também inclui elementos relacionados, como categorias ou descontos."
#: pretix/base/permissions.py
msgctxt "permission_level"
@@ -8219,12 +8235,14 @@ msgstr "Acessar eventos existentes"
#: pretix/base/permissions.py
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr ""
msgstr "Acessar eventos existentes e criar novos eventos"
#: pretix/base/permissions.py
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"O nível de acesso aos eventos é definido detalhadamente pelas configurações "
"abaixo."
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/event/invoicing.html
@@ -8241,12 +8259,17 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Isso inclui o acesso a todas as funcionalidades disponíveis no nível do "
"organizador que não estejam explicitamente listadas abaixo, incluindo as "
"configurações dos plugins."
#: pretix/base/permissions.py
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr ""
"Inclui a possibilidade de conceder permissões adicionais a alguém (inclusive "
"a si mesmo)."
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
@@ -8257,6 +8280,8 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"Inclui a possibilidade de conceder acesso a eventos e dados aos quais você "
"mesmo não tem acesso."
#: pretix/base/permissions.py
#, fuzzy
@@ -8349,22 +8374,22 @@ msgstr "Não definido"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
msgstr ""
msgstr "Uma data relativa não pode ser expressa como \"antes de\" para \"{}\""
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
msgstr ""
msgstr "Uma data relativa não pode ser expressa como \"depois de\" para \"{}\""
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative time cannot be expressed as \"before\" for \"{}\""
msgstr ""
msgstr "Um tempo relativo não pode ser expresso como \"antes de\" para \"{}\""
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative time cannot be expressed as \"after\" for \"{}\""
msgstr ""
msgstr "Um tempo relativo não pode ser expresso como \"depois de\" para \"{}\""
#: pretix/base/secrets.py
msgid "Random (default, works with all pretix apps)"
@@ -8385,7 +8410,7 @@ msgstr "Evento cancelado"
#: pretix/base/services/cancelevent.py
msgid "Confirm event cancellation and bulk refund"
msgstr ""
msgstr "Confirmar o cancelamento do evento e o reembolso em massa"
#: pretix/base/services/cart.py pretix/base/services/modelimport.py
#: pretix/base/services/orders.py
@@ -8933,13 +8958,15 @@ msgstr "Você precisa responder a perguntas para concluir este check-in."
#: pretix/base/services/checkin.py
msgid "Ticket needs to be exchanged to a suitable medium."
msgstr ""
msgstr "O ingresso precisa ser trocado por um meio adequado."
#: pretix/base/services/checkin.py
msgid ""
"This ticket has already been exchanged for a reusable medium that now needs "
"to be used instead."
msgstr ""
"Este ingresso já foi trocado por uma mídia reutilizável, que agora deve ser "
"utilizado em seu lugar."
#: pretix/base/services/checkin.py
msgid "This ticket has already been redeemed."
@@ -9105,62 +9132,44 @@ msgid "You are receiving this email because you placed an order for {event}."
msgstr "Você está recebendo este email porque fez um pedido para {event}."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "Invalid input type."
msgid "Invalid medium type."
msgstr "Tipo de entrada inválido."
msgstr "Tipo de mídia inválido."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "The selected media type is not enabled in your organizer settings."
msgid "Medium type is not enabled for organizer."
msgstr ""
"O tipo de mídia selecionado não está habilitado nas configurações do "
"organizador."
msgstr "O tipo de mídia não está habilitado para o organizador."
#: pretix/base/services/media.py
msgid "Incorrect medium type for product."
msgstr ""
msgstr "Tipo de mídia incorreto para o produto."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "This ticket has already been redeemed."
msgid "Ticket is already exchanged for reusable medium."
msgstr "Este ingresso já foi resgatado."
msgstr "Este ingresso já foi trocado por uma mídia reutilizável."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "Reusable Medium ID"
msgid "Reusable medium not found."
msgstr "ID de mídia reutilizável"
msgstr "Mídia reutilizável não encontrada."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "The reusable medium has been created."
msgid "Reusable medium is inactive or expired."
msgstr "A mídia reutilizável foi criada."
msgstr "A mídia reutilizável está inativa ou expirada."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "The reusable medium has been created."
msgid "Reusable medium not found and could not be created."
msgstr "A mídia reutilizável foi criada."
msgstr "A mídia reutilizável não foi encontrada e não pôde ser criada."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "Reusable media type"
msgid "Reusable medium already exists."
msgstr "Tipo de mídia reutilizável"
msgstr "A mídia reutilizável já existe."
#: pretix/base/services/media.py
#, fuzzy
#| msgid "The reusable medium has been created."
msgid "Reusable medium could not be created."
msgstr "A mídia reutilizável foi criada."
msgstr "Não foi possível criar a mídia reutilizável."
#: pretix/base/services/media.py
msgid "Product does not support medium exchange."
msgstr ""
msgstr "O produto não permite a troca por mídia."
#: pretix/base/services/memberships.py
#, python-brace-format
@@ -9325,6 +9334,8 @@ msgstr "Um cupom não pode ser criado sem um código."
msgid ""
"Voucher codes must be unique. Code \"{code}\" already exists in this import."
msgstr ""
"Os códigos dos cupons devem ser únicos. O código \"{code}\" já existe nesta "
"importação."
#: pretix/base/services/modelimport.py
#, python-brace-format
@@ -9333,13 +9344,19 @@ msgid ""
msgid_plural ""
"Voucher codes must be unique. Import contains existing voucher codes {code}."
msgstr[0] ""
"Os códigos dos cupons devem ser únicos. A importação contém um código de "
"cupom já existente: {code}."
msgstr[1] ""
"Os códigos dos cupons devem ser únicos. A importação contém códigos de "
"cupons já existentes: {code}."
#: pretix/base/services/modelimport.py
msgid ""
"Vouchers could not be imported, probably due to a voucher code already being "
"in use."
msgstr ""
"Não foi possível importar os cupons, provavelmente porque um dos códigos já "
"está em uso."
#: pretix/base/services/orders.py
msgid ""
@@ -9917,7 +9934,7 @@ msgstr ""
#: pretix/base/settings.py
msgid "Enforce the usage of issued reusable media for check-in"
msgstr ""
msgstr "Exigir o uso de mídias reutilizáveis emitidas para realizar o check-in"
#: pretix/base/settings.py
msgid ""
@@ -9925,6 +9942,10 @@ msgid ""
"medium has been created and linked to a ticket. Keeping this option turned "
"off will treat the reusable medium and ticket as equals."
msgstr ""
"Se esta opção estiver ativada, o código de barras do ingresso não será mais "
"aceito caso uma mídia reutilizável tenha sido criada e vinculada a ele. Se a "
"opção permanecer desativada, a mídia reutilizável e o ingresso serão "
"considerados equivalentes."
#: pretix/base/settings.py
msgid "Length of barcodes"
@@ -10392,12 +10413,12 @@ msgstr ""
#: pretix/base/settings.py
msgid "No dates match your criteria."
msgstr ""
msgstr "Nenhuma data corresponde aos seus critérios."
#: pretix/base/settings.py
msgctxt "subevents"
msgid "Text for empty date results"
msgstr ""
msgstr "Texto exibido quando a data não retorna resultados"
#: pretix/base/settings.py
msgctxt "subevents"
@@ -10408,6 +10429,11 @@ msgid ""
"touch with you to arrange further dates. We do not recommend more than one "
"or two sentences."
msgstr ""
"Este texto será exibido quando o calendário ou a lista de datas estiver "
"vazio, por exemplo, quando não houver datas disponíveis em determinado mês "
"ou quando um filtro selecionado pelo usuário não retornar resultados. Você "
"pode usar esse espaço para informar como entrar em contato com você para "
"combinar novas datas. Recomendamos usar apenas uma ou duas frases."
#: pretix/base/settings.py
msgid "Guidance text"
@@ -10593,6 +10619,9 @@ msgid ""
"their order without selecting a payment method. This is useful whenever "
"orders are not created by the same person who is making the payment."
msgstr ""
"Se o pagamento posterior for permitido em um canal de vendas, os clientes "
"poderão concluir seus pedidos sem selecionar uma forma de pagamento. Isso é "
"útil quando a pessoa que faz o pedido não é a mesma que realiza o pagamento."
#: pretix/base/settings.py
msgid "Show start date"
@@ -11561,6 +11590,10 @@ msgid ""
"the email address above. Please note that you still need to add a contact "
"email address that will be shared with all emails you send."
msgstr ""
"Se você configurar esta opção, o link de contato no rodapé direcionará para "
"o endereço informado aqui, em vez de utilizar o endereço de e-mail acima. "
"Observe que ainda será necessário informar um endereço de e-mail de contato, "
"que será incluído em todos os e-mails enviados."
#: pretix/base/settings.py pretix/control/forms/event.py
msgid "Imprint URL"
@@ -13156,7 +13189,7 @@ msgstr ""
#: pretix/base/settings.py
#, python-brace-format
msgid "VAT-ID is not supported for \"{}\"."
msgstr ""
msgstr "O VAT-ID não é compatível para \"{}\"."
#: pretix/base/settings.py
msgid "The last payment date cannot be before the end of presale."
@@ -13451,6 +13484,8 @@ msgid ""
"Don't share this code with anyone. The %(instance)s team will never ask you "
"for it."
msgstr ""
"Não compartilhe este código com ninguém. A equipe do %(instance)s nunca "
"solicitará esse código."
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
#: pretix/base/templates/pretixbase/email/export_failed.txt
@@ -13459,6 +13494,8 @@ msgid ""
"Thanks, \n"
"The %(instance)s Team"
msgstr ""
"Obrigado,\n"
"Time %(instance)s"
#: pretix/base/templates/pretixbase/email/email_footer.html
#, python-format
@@ -14021,6 +14058,8 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"Você inseriu uma URL, o que não é permitido. Remova %(match)s do texto "
"informado."
#: pretix/base/views/errors.py
msgid ""
@@ -14281,6 +14320,8 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"Você não pode escolher um time que lhe conceda um nível de acesso superior "
"ao que você possui no evento que está copiando."
#: pretix/control/forms/event.py
msgid "Copy configuration from"
@@ -14295,6 +14336,8 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"Você não pode escolher um evento no qual tenha um nível de acesso inferior "
"ao do time selecionado na etapa anterior."
#: pretix/control/forms/event.py pretix/control/forms/item.py
#: pretix/control/forms/subevents.py
@@ -14397,7 +14440,7 @@ msgstr "Data de pagamento"
#: pretix/control/forms/event.py
msgid "same as above"
msgstr ""
msgstr "o mesmo que acima"
#: pretix/control/forms/event.py
#, fuzzy
@@ -14479,6 +14522,13 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"Recomendado para a emissão de faturas eletrônicas quando você vende "
"principalmente para consumidores finais. Os preços brutos ou líquidos de "
"alguns produtos podem ser ajustados automaticamente para garantir o "
"arredondamento correto do total do pedido. O sistema procura manter os "
"preços brutos conforme configurados sempre que possível. No entanto, esses "
"preços ainda podem sofrer alterações quando não for possível calculá-los a "
"partir de um preço líquido arredondado."
#: pretix/control/forms/event.py
msgid "Generate invoices for Sales channels"
@@ -16648,7 +16698,7 @@ msgstr "Você pode definir apenas um domínio de organizador."
#: pretix/control/forms/organizer.py
msgid "Provided by a plugin"
msgstr ""
msgstr "Fornecido por um plugin"
#: pretix/control/forms/organizer.py
msgid ""
@@ -16906,7 +16956,7 @@ msgstr "terceiro"
#: pretix/control/forms/rrule.py
msgctxt "rrule"
msgid "fourth"
msgstr ""
msgstr "quarto"
#: pretix/control/forms/rrule.py
msgctxt "rrule"
@@ -16925,7 +16975,7 @@ msgstr "Dia do fim de semana"
#: pretix/control/forms/subevents.py
msgctxt "subevent"
msgid "Skip dates that overlap with any existing date"
msgstr ""
msgstr "Ignorar datas que se sobreponham a qualquer data existente"
#: pretix/control/forms/subevents.py
msgctxt "subevent"
@@ -16935,6 +16985,10 @@ msgid ""
"This respects even inactive dates and works best if all dates have both a "
"start and end time."
msgstr ""
"Isso pode ser útil se todas as datas ocorrerem no mesmo local e você quiser "
"evitar a criação de datas recorrentes que entrem em conflito com eventos "
"especiais já existentes. Essa opção também considera as datas inativas e "
"funciona melhor quando todas as datas têm horários de início e término."
#: pretix/control/forms/subevents.py
msgid "Keep the current values"
@@ -16984,6 +17038,9 @@ msgid ""
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
"Você não pode reduzir o número máximo de resgates para %(max_usages)s, pois "
"pelo menos um dos cupons selecionados já foi resgatato %(max_redeemed)s "
"vezes."
#: pretix/control/forms/vouchers.py
#, fuzzy
@@ -17017,6 +17074,8 @@ msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
"Não é possível alterar em massa o número máximo de utilizações se algum dos "
"cupons selecionados estiver vinculado a um assento."
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
@@ -17024,18 +17083,24 @@ msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
"Não é possível alterar a data em massa se algum dos cupons selecionados "
"estiver vinculado a um assento."
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
"Não é possível alterar o produto para uma cota se algum dos cupons "
"selecionados estiver vinculado a um assento."
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
"Esta alteração não pode ser concluída porque nem todos os assentos "
"vinculados aos cupons ainda estão disponíveis"
#: pretix/control/forms/vouchers.py
msgid "Codes"
@@ -18198,6 +18263,8 @@ msgid ""
"The ticket #{positionid} was exchanged for reusable medium "
"{medium_identifier}."
msgstr ""
"O ingresso #{positionid} foi trocado pela mídia reutilizável "
"{medium_identifier}."
#: pretix/control/logdisplay.py
msgid "The medium has been connected to a new gift card."
@@ -18255,6 +18322,8 @@ msgstr "Um código de emergência de dois fatores foi gerado."
#: pretix/control/logdisplay.py
msgid "Drift and throttle values for two-factor devices have been reset."
msgstr ""
"Os dados de sincronização e de controle de tentativas dos dispositivos de "
"autenticação de dois fatores foram redefinidos."
#: pretix/control/logdisplay.py
#, python-brace-format
@@ -20067,6 +20136,8 @@ msgid ""
"This ticket needs to be exchanged into a <strong>%(media_type)s</strong> "
"reusable medium. <strong>%(media_policy)s</strong>."
msgstr ""
"Este ingresso precisa ser trocado por uma mídia reutilizável do tipo <strong>"
"%(media_type)s</strong>. <strong>%(media_policy)s</strong>."
#: pretix/control/templates/pretixcontrol/checkin/simulator.html
msgid "Special attention required"
@@ -20322,6 +20393,18 @@ msgid ""
"Thanks, \n"
"The %(instance)s Team\n"
msgstr ""
"Olá,\n"
"\n"
"Recebemos uma solicitação para redefinir a senha da sua conta %(instance)s. "
"Para definir uma nova senha, acesse o link abaixo:\n"
"\n"
"%(url)s\n"
"\n"
"Se você não fez essa solicitação, pode ignorar este e-mail com segurança — "
"sua senha não será alterada.\n"
"\n"
"Obrigado,\n"
"Equipe %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt
#, fuzzy, python-format
@@ -20385,14 +20468,17 @@ msgid ""
"\n"
"We noticed a new sign-in to your %(instance)s account:\n"
msgstr ""
"Olá,\n"
"\n"
"Identificamos um novo acesso à sua conta %(instance)s:\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt
msgid "Browser"
msgstr ""
msgstr "Navegador"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt
msgid "Operating system"
msgstr ""
msgstr "Sistema operacional"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt
#, python-format
@@ -20407,6 +20493,14 @@ msgid ""
"Thanks, \n"
"The %(instance)s Team\n"
msgstr ""
"Se foi você, nenhuma ação é necessária.\n"
"\n"
"Se você não reconhece este acesso, altere sua senha imediatamente:\n"
"\n"
"%(url)s\n"
"\n"
"Obrigado,\n"
"Equipe %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt
#, fuzzy, python-format
@@ -22387,6 +22481,9 @@ msgid ""
"debugging problems in your pretix installation, and verifying that your "
"usage of pretix is in compliance with the Enterprise license you purchased."
msgstr ""
"Isso tem duas finalidades: coletar informações úteis que possam ajudar a "
"diagnosticar problemas na sua instalação do pretix e verificar se o uso do "
"pretix está em conformidade com a licença Enterprise que você adquiriu."
#: pretix/control/templates/pretixcontrol/global_sysreport.html
msgid "First month of license term:"
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"PO-Revision-Date: 2026-03-25 08:00+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"PO-Revision-Date: 2026-10-30 03:00+0000\n"
"Last-Translator: vihumelo <victordimelo@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/pt_BR/>\n"
"Language: pt_BR\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.16.2\n"
"X-Generator: Weblate 2026.10.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
msgid "Marked as paid"
@@ -430,7 +430,7 @@ msgstr "Pressione Ctrl+C para copiar!"
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
msgid "Edit"
msgstr ""
msgstr "Editar"
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
msgid "Visualize"
+8
View File
@@ -196,6 +196,14 @@ OPTIONS = OrderedDict([
'offsets': [46 * mm, 46 * mm],
'pagesize': pagesizes.A4,
}),
('herma_75x40', {
'name': 'HERMA 75 x 40 mm (9010)',
'cols': 2,
'rows': 6,
'margins': [28.5 * mm, 25 * mm, 28.5 * mm, 25 * mm],
'offsets': [85 * mm, 40 * mm],
'pagesize': pagesizes.A4,
}),
('herma_88.9x33.87', {
'name': 'HERMA 88,9 x 33,87 mm (4515)',
'cols': 2,
+5
View File
@@ -219,6 +219,11 @@ TEMPLATES = {
"pagesize": (75 * mm, 52 * mm),
"layout": _simple_template(75 * mm, 52 * mm),
},
"75x40": {
"label": format_lazy(_("{width} x {height} mm label"), width=75, height=40),
"pagesize": (75 * mm, 40 * mm),
"layout": _simple_template(75 * mm, 40 * mm),
},
"70x36": {
"label": format_lazy(_("{width} x {height} mm label"), width=70, height=36),
"pagesize": (70 * mm, 36 * mm),
+1 -1
View File
@@ -239,7 +239,7 @@ class ActionView(View):
})
def get(self, request, *args, **kwargs):
u = request.GET.get('query', '')
u = request.GET.get('query', '').strip()
if len(u) < 2:
return JsonResponse({'results': []})
+15 -2
View File
@@ -98,19 +98,32 @@ class TicketLayoutSerializer(I18nAwareModelSerializer):
return d
def validate(self, attrs):
if attrs.get('default') and self.context['event'].ticket_layouts.filter(default=True).exists:
if attrs.get('default') and self.context['event'].ticket_layouts.filter(default=True).exists():
raise ValidationError('You cannot have two layouts with default = True')
return attrs
def create(self, validated_data):
validated_data["event"] = self.context["event"]
return super().create(validated_data)
background = validated_data.pop("background", None)
instance = super().create(validated_data)
if background:
instance.background.save('background.pdf', background)
return instance
def update(self, instance, validated_data):
background = validated_data.pop("background", None)
if background:
if instance.background and TicketLayout.objects.filter(background=instance.background).count() == 1:
instance.background.delete()
instance.background.save('background.pdf', background)
return super().update(instance, validated_data)
class TicketLayoutViewSet(viewsets.ModelViewSet):
serializer_class = TicketLayoutSerializer
queryset = TicketLayout.objects.none()
lookup_field = 'id'
write_permission = 'event.settings.general:write'
def get_queryset(self):
return self.request.event.ticket_layouts.all()
@@ -235,6 +235,7 @@ class LayoutGetDefault(EventPermissionRequiredMixin, View):
class LayoutEditorView(BaseEditorView):
checkin_qr_warning = True
@cached_property
def layout(self):
+9
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import sys
from collections import Counter
from datetime import datetime
from typing import Optional
@@ -472,3 +473,11 @@ def _item_num_options(item):
def get_item_option_count(items):
return sum(_item_num_options(item) for item in items)
def get_item_var_sums(positions, subevent):
itemvarsums = Counter()
for pos in positions:
if pos.subevent == subevent:
itemvarsums[pos.variation or pos.item] += 1
return itemvarsums
@@ -55,7 +55,7 @@
<div class="col-md-6 col-md-offset-3">
{% for provider in request.organizer.sso_providers.all %}
{% if provider.is_active %}
<a href="{% eventurl request.organizer "presale:organizer.customer.login" provider=provider.pk %}?next={% if request.domain_mode == 'event' or request.domain_mode == 'organizer_alternative' %}{{ request.scheme }}://{{ request.get_host }}{% endif %}{{ request.get_full_path|urlencode }}"
<a href="{% abseventurl request.organizer "presale:organizer.customer.login" provider=provider.pk %}?next={% if request.domain_mode == 'event' or request.domain_mode == 'organizer_alternative' %}{{ request.scheme }}://{{ request.get_host }}{% endif %}{{ request.get_full_path|urlencode }}"
class="btn btn-primary btn-lg btn-block" data-open-in-popup-window>
{{ provider.button_label }}
</a>
@@ -125,10 +125,10 @@
<div class="col-md-8 col-sm-6 col-xs-12">
<h{{ headline_level|default:3|add:2 }} class="h5" id="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend">
{{ var }}
{% if cart.itemvarsums|getitem:var %}
{% if itemvarsums|getitem:var %}
<span class="textbubble-success">
{% icon "shopping-cart" %}
{% blocktrans trimmed count amount=cart.itemvarsums|getitem:var %}
{% blocktrans trimmed count amount=itemvarsums|getitem:var %}
{{ amount }}× in your cart
{% plural %}
{{ amount }}× in your cart
@@ -299,10 +299,10 @@
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h{{ headline_level|default:3|add:1 }} class="h4" id="{{ form_prefix }}item-{{ item.pk }}-legend">
{{ item.name }}
{% if cart.itemvarsums|getitem:item %}
{% if itemvarsums|getitem:item %}
<span class="textbubble-success">
{% icon "shopping-cart" %}
{% blocktrans trimmed count amount=cart.itemvarsums|getitem:item %}
{% blocktrans trimmed count amount=itemvarsums|getitem:item %}
{{ amount }}× in your cart
{% plural %}
{{ amount }}× in your cart
+1 -6
View File
@@ -33,7 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import copy
import warnings
from collections import Counter, defaultdict
from collections import defaultdict
from datetime import datetime, timedelta
from decimal import Decimal
from functools import wraps
@@ -266,10 +266,6 @@ class CartMixin:
minutes_left = None
seconds_left = None
itemvarsums = Counter()
for p in cartpos:
itemvarsums[p.variation or p.item] += 1
return {
'positions': positions,
'invoice_address': self.invoice_address,
@@ -291,7 +287,6 @@ class CartMixin:
not self.request.event.settings.display_net_prices and
sum(c.price_includes_rounding_correction for c in positions) + sum(f.price_includes_rounding_correction for f in fees)
),
'itemvarsums': itemvarsums,
'current_selected_payments': [
p for p in self.current_selected_payments(lcp, fees, self.invoice_address)
if p.get('multi_use_supported')
+3 -1
View File
@@ -70,7 +70,8 @@ from pretix.helpers.http import redirect_to_url
from pretix.helpers.safedownload import check_token
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.productlist import (
get_item_option_count, item_group_by_category, prepare_item_list_for_shop,
get_item_option_count, get_item_var_sums, item_group_by_category,
prepare_item_list_for_shop,
)
from pretix.presale.views import (
CartMixin, EventViewMixin, allow_cors_if_namespaced,
@@ -701,6 +702,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
)
context['cart'] = self.get_cart()
context['itemvarsums'] = get_item_var_sums(context['cart'].get('raw', []), self.subevent)
context['show_cart'] = context['cart']['positions']
# Regroup those by category
+2 -2
View File
@@ -156,7 +156,7 @@ class LoginView(RedirectBackMixin, FormView):
"""Security check complete. Log the user in."""
customer = form.get_customer()
customer_login(self.request, customer)
customer_signed_in.send(customer.organizer, customer=customer)
customer_signed_in.send(customer.organizer, customer=customer, profile=None)
return HttpResponseRedirect(self.get_success_url())
@@ -901,7 +901,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
popup_origin
)
customer_signed_in.send(customer.organizer, customer=customer)
customer_signed_in.send(customer.organizer, customer=customer, profile=profile)
if popup_origin:
return render(self.request, 'pretixpresale/postmessage.html', {
+3 -1
View File
@@ -72,7 +72,8 @@ from pretix.helpers.http import redirect_to_url
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.ical import get_public_ical
from pretix.presale.productlist import (
get_item_option_count, item_group_by_category, prepare_item_list_for_shop,
get_item_option_count, get_item_var_sums, item_group_by_category,
prepare_item_list_for_shop,
)
from pretix.presale.signals import seatingframe_html_head
from pretix.presale.views.organizer import (
@@ -244,6 +245,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['display_add_to_cart'] = display_add_to_cart
context['cart'] = self.get_cart()
context['itemvarsums'] = get_item_var_sums(context['cart'].get('raw', []), self.subevent)
context['has_addon_choices'] = any(cp.has_addon_choices for cp in get_cart(self.request))
templating_context = PlaceholderContext(event_or_subevent=self.subevent or self.request.event, event=self.request.event)
+4 -1
View File
@@ -76,7 +76,7 @@ from pretix.base.models.tax import TaxedPrice
from pretix.base.payment import PaymentException
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
invoice_qualified,
invoice_qualified, transmit_invoice,
)
from pretix.base.services.orders import (
OrderChangeManager, OrderError, _try_auto_refund, cancel_order,
@@ -907,6 +907,9 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
self.order.log_action('pretix.event.order.invoice.reissued', data={
'invoice': inv.pk
})
transmit_invoice.apply_async(args=(c.event_id, c.pk, True))
if c.pk != inv.pk:
transmit_invoice.apply_async(args=(inv.event_id, inv.pk, True))
messages.success(self.request, _('The invoice has been reissued.'))
except Exception as e:
self.order.log_action("pretix.event.order.invoice.failed", data={
+6 -1
View File
@@ -35,6 +35,7 @@ import calendar
import hashlib
import math
import operator
import re
from collections import defaultdict
from datetime import date, datetime, time, timedelta
from functools import reduce
@@ -381,7 +382,11 @@ class EventListMixin:
def _set_week_year(self):
if 'date' in self.request.GET:
try:
iso = dateutil.parser.isoparse(self.request.GET.get('date')).isocalendar()
input = self.request.GET.get('date')
# Our parameters for the single-digit weeks of the year are 2027-W2, as emitted by Django's "W" format code,
# but dateutil 4.0 is strict about ISO and only accepts 2027-W02 as valid.
input = re.sub('-W([0-9])$', '-W0\\1', input)
iso = dateutil.parser.isoparse(input).isocalendar()
except ValueError:
iso = time_machine_now().isocalendar()
self.year = iso[0]
+35 -1
View File
@@ -19,12 +19,18 @@
# 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/>.
#
import re
import typing
import weakref
from celery.exceptions import Retry
from sentry_sdk import Hub
from sentry_sdk.integrations import django as djangosentry
from sentry_sdk.utils import capture_internal_exceptions
from sentry_sdk.scrubber import EventScrubber
from sentry_sdk.utils import AnnotatedValue, capture_internal_exceptions
if typing.TYPE_CHECKING:
from sentry_sdk._types import Event
def _make_event_processor(weak_request, integration):
@@ -96,3 +102,31 @@ def setup_custom_filters():
hub = Hub.current
with hub.configure_scope() as scope:
scope.add_event_processor(ignore_retry)
class PretixEventScrubber(EventScrubber):
secret_re = re.compile(
'.*(pretixsecret|ramiiosecret|PRIVATE KEY|://[^/@:]*:[^/@:]+@).*',
re.IGNORECASE
)
def scrub_dict(self, d: object) -> None:
if not isinstance(d, dict):
return
super().scrub_dict(d)
self._scrub_dict_for_known_secrets(d)
def _scrub_dict_for_known_secrets(self, d: dict):
for k, v in d.items():
if isinstance(v, str) and self.secret_re.match(v):
d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
def scrub_exception(self, event: "Event") -> None:
with capture_internal_exceptions():
if "exception" in event:
self.scrub_dict(event["exception"])
def scrub_event(self, event: "Event") -> None:
super().scrub_event(event)
self.scrub_exception(event)
+4 -5
View File
@@ -41,7 +41,7 @@ from json import loads
from pathlib import Path
from urllib.parse import urlparse
import importlib_metadata as metadata
from importlib import metadata
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import get_random_string
from kombu import Queue
@@ -507,7 +507,6 @@ REST_FRAMEWORK = {
],
'EXCEPTION_HANDLER': 'pretix.api.exception.custom_exception_handler',
'UNICODE_JSON': False,
'LIST_SERIALIZER_ERRORS_AS_DICT': False
}
@@ -721,9 +720,9 @@ if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell
from sentry_sdk.integrations.logging import (
LoggingIntegration, ignore_logger,
)
from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber
from sentry_sdk.scrubber import DEFAULT_DENYLIST
from .sentry import PretixSentryIntegration, setup_custom_filters
from .sentry import PretixSentryIntegration, PretixEventScrubber, setup_custom_filters
SENTRY_TOKEN = config.get('sentry', 'traces_sample_token', fallback='')
SENTRY_ENABLE_LOGS = config.getboolean('sentry', 'enable_logs', fallback=False)
@@ -759,7 +758,7 @@ if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell
traces_sampler=traces_sampler,
environment=urlparse(SITE_URL).netloc,
release=__version__,
event_scrubber=EventScrubber(denylist=pretix_denylist, recursive=True),
event_scrubber=PretixEventScrubber(denylist=pretix_denylist, recursive=True),
send_default_pii=False,
propagate_traces=False, # see https://github.com/getsentry/sentry-python/issues/1717
)
@@ -618,18 +618,21 @@ var editor = {
$('#loading-initial').remove()
})
}
editor._update_version_notice()
editor._update_notices()
},
_update_version_notice: function () {
_update_notices: function () {
$('#version-notice').toggle(
editor._other_page_objects.some((o) => o.type === 'textcontainer')
|| editor.fabric.getObjects().some((o) => o.type === 'textcontainer')
)
$('#nocheckin-notice').toggle(
!editor.fabric.getObjects().some((o) => o.type === 'barcodearea' && (!o.content || o.content === 'secret'))
)
},
_update_toolbox_values: function () {
editor._update_version_notice()
editor._update_notices()
let o = editor.fabric.getActiveObject()
if (!o) {
return
@@ -898,6 +901,7 @@ var editor = {
o.setCoords()
editor.fabric.renderAll()
editor._update_notices()
},
_update_toolbox: function () {
@@ -103,3 +103,6 @@ body {
.panel-pdf-editor .panel-heading form > .btn-group {
margin-right: 1em;
}
#nocheckin-notice {
display: none;
}
+5 -2
View File
@@ -25,8 +25,10 @@ import tempfile
tmpdir = tempfile.TemporaryDirectory()
os.environ.setdefault('DATA_DIR', tmpdir.name)
if os.path.exists('test/sqlite.cfg'):
os.environ.setdefault('PRETIX_CONFIG_FILE', 'test/sqlite.cfg')
if os.path.exists('tests/ci_sqlite.cfg'):
os.environ.setdefault('PRETIX_CONFIG_FILE', 'tests/ci_sqlite.cfg')
else:
os.environ.setdefault("PRETIX_CONFIG_FILE", "")
from pretix.settings import * # NOQA
@@ -100,3 +102,4 @@ class DisableMigrations(object):
if not os.environ.get("GITHUB_WORKFLOW", ""):
MIGRATION_MODULES = DisableMigrations()
VITE_DEV_MODE = True
-1
View File
@@ -57,7 +57,6 @@ filterwarnings =
ignore:teardown:pytest.PytestWarning
ignore:avoid running initialization queries:RuntimeWarning
ignore:.*The 'strip_cdata' option of HTMLParser.*:DeprecationWarning:
ignore:The list-based error format for `ListSerializer`:rest_framework.deprecation.RemovedInDRF320Warning
[coverage:run]
+16 -13
View File
@@ -424,7 +424,7 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'question': ['The specified question does not belong to this event.']}]}
assert resp.data == {'answers': {0: {'question': ['The specified question does not belong to this event.']}}}
res['answers'][0]['question'] = question.pk
res['answers'][0]['options'] = [question.options.first().pk]
@@ -434,8 +434,9 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['You should not specify options if the question is not of a choice type.']}]}
assert resp.data == {'answers': {
0: {'non_field_errors': ['You should not specify options if the question is not of a choice type.']}
}}
question.type = Question.TYPE_CHOICE
question.save()
@@ -447,7 +448,7 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['You need to specify options if the question is of a choice type.']}]}
'answers': {0: {'non_field_errors': ['You need to specify options if the question is of a choice type.']}}}
with scopes_disabled():
question.options.create(answer="L")
@@ -461,7 +462,8 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['You can specify at most one option for this question.']}]}
assert resp.data == {
'answers': {0: {'non_field_errors': ['You can specify at most one option for this question.']}}}
r = token_client.post(
'/api/v1/upload',
@@ -533,7 +535,7 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['A valid number is required.']}]}
assert resp.data == {'answers': {0: {'non_field_errors': ['A valid number is required.']}}}
question.type = Question.TYPE_BOOLEAN
question.save()
@@ -573,7 +575,8 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': ['Please specify "true" or "false" for boolean questions.']}]}
assert resp.data == {
'answers': {0: {'non_field_errors': ['Please specify "true" or "false" for boolean questions.']}}}
question.type = Question.TYPE_DATE
question.save()
@@ -599,7 +602,7 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
)
assert resp.status_code == 400
assert resp.data == {
'answers': [{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}]}
'answers': {0: {'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}}}
question.type = Question.TYPE_DATETIME
question.save()
@@ -624,9 +627,9 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [{'non_field_errors': [
assert resp.data == {'answers': {0: {'non_field_errors': [
'Datetime has wrong format. Use one of these formats instead: '
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].']}]}
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].']}}}
question.type = Question.TYPE_TIME
question.save()
@@ -651,8 +654,8 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'answers': [
{'non_field_errors': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].']}]}
assert resp.data == {'answers': {0: {'non_field_errors': [
'Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].']}}}
@pytest.mark.django_db
@@ -1387,7 +1390,7 @@ def test_cartpos_create_bulk_validation_error_in_addon(token_client, organizer,
'data': None,
'success': False,
'errors': {
'addons': [{'item': ['Invalid pk "-1" - object does not exist.']}]
'addons': {0: {'item': ['Invalid pk "-1" - object does not exist.']}}
}
}
]
+3 -3
View File
@@ -958,9 +958,9 @@ def test_item_create_with_addon(token_client, organizer, event, item, category,
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() in [
'{"addons":["The minimum count needs to be equal to or greater than zero."]}',
'{"addons":[{"min_count":["Ensure this value is greater than or equal to 0."]}]}',
assert resp.data in [
{"addons": {0: "The minimum count needs to be equal to or greater than zero."}},
{"addons": {0: {"min_count": ["Ensure this value is greater than or equal to 0."]}}},
]
with scopes_disabled():
assert 2 == Item.objects.all().count()
+11 -3
View File
@@ -26,6 +26,7 @@ from unittest import mock
import pytest
from django.core import mail as djmail
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.utils.timezone import now
from django_countries.fields import Country
@@ -248,7 +249,14 @@ def test_order_update_state_validation(token_client, organizer, event, order):
@pytest.mark.django_db
def test_order_update_transmission_validation(token_client, organizer, event, order):
def test_order_update_transmission_validation(token_client, organizer, event, order, monkeypatch):
def mock_validate(self, value):
if value == "9930:DE111111111":
return value
raise ValidationError("Invalid")
monkeypatch.setattr("pretix.base.invoicing.peppol.PeppolIdValidator._validate_peppol_id_online", mock_validate)
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orders/{}/'.format(
organizer.slug, event.slug, order.code
@@ -374,7 +382,7 @@ def test_order_update_transmission_validation(token_client, organizer, event, or
"vat_id": "",
"transmission_type": "peppol",
"transmission_info": {
"transmission_peppol_participant_id": "9930:DE811569869",
"transmission_peppol_participant_id": "9930:DE111111111",
"ignored": "parameter",
},
}
@@ -384,7 +392,7 @@ def test_order_update_transmission_validation(token_client, organizer, event, or
order.invoice_address.refresh_from_db()
assert order.invoice_address.transmission_type == "peppol"
assert order.invoice_address.transmission_info == {
"transmission_peppol_participant_id": "9930:DE811569869",
"transmission_peppol_participant_id": "9930:DE111111111",
}
+82 -140
View File
@@ -978,7 +978,7 @@ def test_order_create_position_secret_optional(token_client, organizer, event, i
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'secret': ['You cannot assign a position secret that already exists.']}]}
assert resp.data == {'positions': {0: {'secret': ['You cannot assign a position secret that already exists.']}}}
@pytest.mark.django_db
@@ -1024,7 +1024,7 @@ def test_order_create_fee_type_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'fees': [{'fee_type': ['"unknown" is not a valid choice.']}]}
assert resp.data == {'fees': {0: {'fee_type': ['"unknown" is not a valid choice.']}}}
@pytest.mark.django_db
@@ -1143,7 +1143,7 @@ def test_order_create_tax_rule_wrong_event(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'fees': [{'tax_rule': ['The specified tax rate does not belong to this event.']}]}
assert resp.data == {'fees': {0: {'tax_rule': ['The specified tax rate does not belong to this event.']}}}
@pytest.mark.django_db
@@ -1158,7 +1158,7 @@ def test_order_create_subevent_not_allowed(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'subevent': ['You cannot set a subevent for this event.']}]}
assert resp.data == {'positions': {0: {'subevent': ['You cannot set a subevent for this event.']}}}
@pytest.mark.django_db
@@ -1185,7 +1185,7 @@ def test_order_create_subevent_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'subevent': ['You need to set a subevent.']}]}
assert resp.data == {'positions': {0: {'subevent': ['You need to set a subevent.']}}}
res['positions'][0]['subevent'] = subevent2.pk
resp = token_client.post(
@@ -1194,7 +1194,7 @@ def test_order_create_subevent_validation(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'subevent': ['The specified subevent does not belong to this event.']}]}
assert resp.data == {'positions': {0: {'subevent': ['The specified subevent does not belong to this event.']}}}
@pytest.mark.django_db
@@ -1210,7 +1210,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'item': ['The specified item is not active.']}]}
assert resp.data == {'positions': {0: {'item': ['The specified item is not active.']}}}
item.active = True
item.save()
@@ -1222,7 +1222,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'item': ['The specified item does not belong to this event.']}]}
assert resp.data == {'positions': {0: {'item': ['The specified item does not belong to this event.']}}}
with scopes_disabled():
var2 = item2.variations.create(value="A")
@@ -1236,7 +1236,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'variation': ['You cannot specify a variation for this item.']}]}
assert resp.data == {'positions': {0: {'variation': ['You cannot specify a variation for this item.']}}}
with scopes_disabled():
var1 = item.variations.create(value="A")
@@ -1248,7 +1248,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'item': ['The product "Budget Ticket" is not assigned to a quota.']}]}
assert resp.data == {'positions': {0: {'item': ['The product "Budget Ticket" is not assigned to a quota.']}}}
with scopes_disabled():
quota.variations.add(var1)
@@ -1267,7 +1267,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'variation': ['The specified variation does not belong to the specified item.']}]}
'positions': {0: {'variation': ['The specified variation does not belong to the specified item.']}}}
res['positions'][0]['variation'] = None
resp = token_client.post(
@@ -1276,7 +1276,7 @@ def test_order_create_item_validation(token_client, organizer, event, item, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'variation': ['You should specify a variation for this item.']}]}
assert resp.data == {'positions': {0: {'variation': ['You should specify a variation for this item.']}}}
@pytest.mark.django_db
@@ -1294,7 +1294,7 @@ def test_order_create_subevent_disabled(token_client, organizer, event, item, su
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'item': ['The product "Budget Ticket" is not available on this date.']}]}
assert resp.data == {'positions': {0: {'item': ['The product "Budget Ticket" is not available on this date.']}}}
s.delete()
resp = token_client.post(
@@ -1326,7 +1326,7 @@ def test_order_create_subevent_variation_disabled(token_client, organizer, event
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'item': ['The product "Budget Ticket" is not available on this date.']}]}
assert resp.data == {'positions': {0: {'item': ['The product "Budget Ticket" is not available on this date.']}}}
s.delete()
resp = token_client.post(
@@ -1453,16 +1453,15 @@ def test_order_create_positionid_validation(token_client, organizer, event, item
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{
'positions': {
1: {
'addon_to': [
'If you set addon_to, you need to make sure that the '
'referenced position ID exists and is transmitted directly '
'before its add-ons.'
]
}
]
}
}
res['positions'] = [
@@ -1493,10 +1492,14 @@ def test_order_create_positionid_validation(token_client, organizer, event, item
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [
{'positionid': ["If you set addon_to on any position, you need to specify position IDs manually."]},
{'positionid': ["If you set addon_to on any position, you need to specify position IDs manually."]}
]}
assert resp.data == {'positions': {
0:
{'positionid': [
"If you set addon_to on any position, you need to specify position IDs manually."]},
1:
{'positionid': [
"If you set addon_to on any position, you need to specify position IDs manually."]}
}}
res['positions'] = [
{
@@ -1526,12 +1529,11 @@ def test_order_create_positionid_validation(token_client, organizer, event, item
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{
'positions': {
1: {
'positionid': ['If you set position IDs manually, you need to do so for all positions.']
}
]
}
}
res['positions'] = [
@@ -1563,12 +1565,11 @@ def test_order_create_positionid_validation(token_client, organizer, event, item
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{
'positions': {
1: {
'positionid': ['Position IDs need to be consecutive.']
}
]
}
}
res['positions'] = [
@@ -1615,7 +1616,7 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'answers': [{'question': ['The specified question does not belong to this event.']}]}]}
'positions': {0: {'answers': {0: {'question': ['The specified question does not belong to this event.']}}}}}
res['positions'][0]['answers'][0]['question'] = question.pk
res['positions'][0]['answers'][0]['options'] = [question.options.first().pk]
@@ -1625,8 +1626,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'answers': [
{'non_field_errors': ['You should not specify options if the question is not of a choice type.']}]}]}
assert resp.data == {'positions': {0: {'answers': {
0: {'non_field_errors': ['You should not specify options if the question is not of a choice type.']}}}}}
question.type = Question.TYPE_CHOICE
question.save()
@@ -1637,8 +1638,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [
{'answers': [{'non_field_errors': ['You need to specify options if the question is of a choice type.']}]}]}
assert resp.data == {'positions': {0: {
'answers': {0: {'non_field_errors': ['You need to specify options if the question is of a choice type.']}}}}}
with scopes_disabled():
question2.options.create(answer="L")
@@ -1653,7 +1654,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'answers': [{'non_field_errors': ['The specified option does not belong to this question.']}]}]}
'positions': {
0: {'answers': {0: {'non_field_errors': ['The specified option does not belong to this question.']}}}}}
with scopes_disabled():
question.options.create(answer="L")
@@ -1669,7 +1671,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'answers': [{'non_field_errors': ['You can specify at most one option for this question.']}]}]}
'positions': {
0: {'answers': {0: {'non_field_errors': ['You can specify at most one option for this question.']}}}}}
r = token_client.post(
'/api/v1/upload',
@@ -1745,7 +1748,7 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'answers': [{'non_field_errors': ['A valid number is required.']}]}]}
assert resp.data == {'positions': {0: {'answers': {0: {'non_field_errors': ['A valid number is required.']}}}}}
question.type = Question.TYPE_BOOLEAN
question.save()
@@ -1787,8 +1790,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'answers': [{'non_field_errors': ['Please specify "true" or "false" for boolean questions.']}]}]}
assert resp.data == {'positions': {
0: {'answers': {0: {'non_field_errors': ['Please specify "true" or "false" for boolean questions.']}}}}}
question.type = Question.TYPE_DATE
question.save()
@@ -1814,8 +1817,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'answers': [
{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}]}]}
assert resp.data == {'positions': {0: {'answers': {
0: {'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}}}}}
question.type = Question.TYPE_DATETIME
question.save()
@@ -1841,9 +1844,9 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'answers': [{'non_field_errors': [
assert resp.data == {'positions': {0: {'answers': {0: {'non_field_errors': [
'Datetime has wrong format. Use one of these formats instead: '
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].']}]}]}
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].']}}}}}
question.type = Question.TYPE_TIME
question.save()
@@ -1869,8 +1872,8 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'answers': [
{'non_field_errors': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].']}]}]}
assert resp.data == {'positions': {0: {'answers': {
0: {'non_field_errors': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].']}}}}}
@pytest.mark.django_db
@@ -1910,10 +1913,10 @@ def test_order_create_quota_validation(token_client, organizer, event, item, quo
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
{'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
]
'positions': {
0: {'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
1: {'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
}
}
quota.size = 1
@@ -1924,12 +1927,8 @@ def test_order_create_quota_validation(token_client, organizer, event, item, quo
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
]
}
assert resp.data == {'positions': {
1: {'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']}}}
res['force'] = True
resp = token_client.post(
@@ -1961,11 +1960,8 @@ def test_order_create_quota_consume_cart(token_client, organizer, event, item, q
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
]
}
assert resp.data == {'positions': {
0: {'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']}}}
res['consume_carts'] = [cr.cart_id]
resp = token_client.post(
@@ -2000,11 +1996,8 @@ def test_order_create_quota_consume_cart_expired(token_client, organizer, event,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']},
]
}
assert resp.data == {'positions': {
0: {'item': ['There is not enough quota available on quota "Budget Quota" to perform the operation.']}}}
@pytest.mark.django_db
@@ -2176,11 +2169,7 @@ def test_order_create_with_blocked_seat(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'seat': ['The selected seat "Seat A1" is not available.']},
]
}
assert resp.data == {'positions': {0: {'seat': ['The selected seat "Seat A1" is not available.']}}}
@pytest.mark.django_db
@@ -2199,11 +2188,7 @@ def test_order_create_with_used_seat(token_client, organizer, event, item, quota
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'seat': ['The selected seat "Seat A1" is not available.']},
]
}
assert resp.data == {'positions': {0: {'seat': ['The selected seat "Seat A1" is not available.']}}}
@pytest.mark.django_db
@@ -2218,11 +2203,7 @@ def test_order_create_with_unknown_seat(token_client, organizer, event, item, qu
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'seat': ['The specified seat does not exist.']},
]
}
assert resp.data == {'positions': {0: {'seat': ['The specified seat does not exist.']}}}
@pytest.mark.django_db
@@ -2236,11 +2217,7 @@ def test_order_create_require_seat(token_client, organizer, event, item, quota,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'seat': ['The specified product requires to choose a seat.']},
]
}
assert resp.data == {'positions': {0: {'seat': ['The specified product requires to choose a seat.']}}}
@pytest.mark.django_db
@@ -2258,11 +2235,7 @@ def test_order_create_unseated(token_client, organizer, event, item, quota, seat
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'seat': ['The specified product does not allow to choose a seat.']},
]
}
assert resp.data == {'positions': {0: {'seat': ['The specified product does not allow to choose a seat.']}}}
@pytest.mark.django_db
@@ -2300,12 +2273,7 @@ def test_order_create_with_duplicate_seat(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{'seat': ['The selected seat "Seat A1" is not available.']},
]
}
assert resp.data == {'positions': {1: {'seat': ['The selected seat "Seat A1" is not available.']}}}
@pytest.mark.django_db
@@ -2810,8 +2778,8 @@ def test_order_create_autopricing_voucher_budget_full(token_client, organizer, e
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{}, {'voucher': ['The voucher has a remaining budget of 0.00, therefore a '
'discount of 1.50 can not be given.']}]}
assert resp.data == {'positions': {1: {'voucher': ['The voucher has a remaining budget of 0.00, therefore a '
'discount of 1.50 can not be given.']}}}
@pytest.mark.django_db
@@ -2832,8 +2800,8 @@ def test_order_create_voucher_budget_exceeded(token_client, organizer, event, it
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'voucher': ['The voucher has a remaining budget of 3.00, therefore a '
'discount of 4.00 can not be given.']}]}
assert resp.data == {'positions': {0: {'voucher': ['The voucher has a remaining budget of 3.00, therefore a '
'discount of 4.00 can not be given.']}}}
@pytest.mark.django_db
@@ -2876,11 +2844,7 @@ def test_order_create_voucher_unknown_code(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'voucher': ['Object with code=FOOBAR does not exist.']},
]
}
assert resp.data == {'positions': {0: {'voucher': ['Object with code=FOOBAR does not exist.']}}}
@pytest.mark.django_db
@@ -2899,9 +2863,7 @@ def test_order_create_voucher_redeemed(token_client, organizer, event, item, quo
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'voucher': ['The voucher has already been used the maximum number of times.']},
]
'positions': {0: {'voucher': ['The voucher has already been used the maximum number of times.']}}
}
@@ -2924,11 +2886,10 @@ def test_order_create_voucher_redeemed_partially(token_client, organizer, event,
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{'voucher': ['The voucher has already been used the maximum number of times.']},
{'voucher': ['The voucher has already been used the maximum number of times.']},
]
'positions': {
1: {'voucher': ['The voucher has already been used the maximum number of times.']},
2: {'voucher': ['The voucher has already been used the maximum number of times.']},
}
}
@@ -2949,10 +2910,7 @@ def test_order_create_voucher_item_mismatch(token_client, organizer, event, item
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'voucher': ['This voucher is not valid for this product.']},
]
}
'positions': {0: {'voucher': ['This voucher is not valid for this product.']}}}
@pytest.mark.django_db
@@ -2972,10 +2930,7 @@ def test_order_create_voucher_expired(token_client, organizer, event, item, quot
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{'voucher': ['This voucher is expired.']},
]
}
'positions': {0: {'voucher': ['This voucher is expired.']}}}
@pytest.mark.django_db
@@ -3103,10 +3058,8 @@ def test_create_cart_and_consume_cart_with_addons(token_client, organizer, event
)
assert resp.status_code == 400
assert resp.data == {
'positions': [
{},
{'item': ['There is not enough quota available on quota "Addon Quota" to perform the operation.']},
]
'positions': {
1: {'item': ['There is not enough quota available on quota "Addon Quota" to perform the operation.']}}
}
res['consume_carts'] = ['aaa@api']
@@ -3214,12 +3167,7 @@ def test_order_create_use_medium_other_organizer(token_client, organizer, event,
), format='json', data=res
)
assert resp.data == {
"positions": [
{
"use_reusable_medium": ["The specified medium does not belong to this organizer."]
}
]
}
"positions": {0: {"use_reusable_medium": ["The specified medium does not belong to this organizer."]}}}
assert resp.status_code == 400
@@ -3361,14 +3309,8 @@ def test_order_create_auto_pricing_explicit_discount_not_allowed(token_client, o
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
"positions": [
{},
{
"discount": ["You can only specify a discount if you do the price computation, but price is not set."]
}
]
}
assert resp.data == {"positions": {
1: {"discount": ["You can only specify a discount if you do the price computation, but price is not set."]}}}
@pytest.mark.django_db
+4 -3
View File
@@ -409,7 +409,7 @@ def test_subevent_create(team, token_client, organizer, event, subevent, meta_pr
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"item_price_overrides":[{"item":["Invalid pk \\"555\\" - object does not exist."]}]}'
assert resp.data == {"item_price_overrides": {0: {"item": ["Invalid pk \"555\" - object does not exist."]}}}
@pytest.mark.django_db
@@ -580,7 +580,7 @@ def test_subevent_update(token_client, organizer, event, subevent, item, item2,
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"item_price_overrides":[{"item":["Invalid pk \\"123\\" - object does not exist."]}]}'
assert resp.data == {"item_price_overrides": {0: {"item": ["Invalid pk \"123\" - object does not exist."]}}}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
@@ -686,7 +686,8 @@ def test_subevent_update(token_client, organizer, event, subevent, item, item2,
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"variation_price_overrides":[{"variation":["Invalid pk \\"123\\" - object does not exist."]}]}'
assert resp.data == {
"variation_price_overrides": {0: {"variation": ["Invalid pk \"123\" - object does not exist."]}}}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk),
+3 -3
View File
@@ -1061,7 +1061,7 @@ def test_create_multiple_vouchers_one_invalid(token_client, organizer, event, it
], format='json'
)
assert resp.status_code == 400
assert resp.data == [{}, {'code': ['Ensure this field has at least 5 characters.']}]
assert resp.data == {1: {'code': ['Ensure this field has at least 5 characters.']}}
with scopes_disabled():
assert Voucher.objects.count() == 0
@@ -1106,7 +1106,7 @@ def test_create_multiple_vouchers_duplicate_code(token_client, organizer, event,
], format='json'
)
assert resp.status_code == 400
assert resp.data == [{}, {'code': ['Duplicate voucher code in request.']}]
assert resp.data == {1: {'code': ['Duplicate voucher code in request.']}}
with scopes_disabled():
assert Voucher.objects.count() == 0
@@ -1214,7 +1214,7 @@ def test_create_multiple_vouchers_duplicate_seat(token_client, organizer, event,
], format='json'
)
assert resp.status_code == 400
assert resp.data == [{}, {'code': ['Duplicate seat ID in request.']}]
assert resp.data == {1: {'code': ['Duplicate seat ID in request.']}}
with scopes_disabled():
assert Voucher.objects.count() == 0
+77
View File
@@ -94,6 +94,83 @@ def question(event, item):
return q
@pytest.mark.django_db
def test_log_action_filter(event, order):
sensitivekeys = [
'auth',
'key',
'token',
'password',
'Password',
'_pass',
'salt',
'secret',
]
log_data = log_data_filtered = {
'foo': 'bar',
}
for d in (log_data, log_data_filtered):
value = '********' if d == log_data_filtered else '__sensitive_info__'
for k in sensitivekeys:
d[k] = d['prefix' + k] = d['prefix' + k + 'suffix'] = d[k + 'suffix'] = value
d['recursive_dict'] = {
'test': 'dummy',
'test_token': value,
'd': {
'baz': 'foo',
'mypassword': value,
'l': [
'foo',
'bar',
{
'baz': True,
'superkey': value,
},
],
},
'l': [
'okay',
{
'salt_to_secret': value,
'name': 'okay'
},
'element',
[
'another',
{
'api_password': value,
},
'list'
]
]
}
d['recursive_list'] = [
'foo',
'bar',
[
'foo',
'bar',
{
'foo': 'bar',
'api_token': value,
},
'baz',
],
{
'foo': 'bar',
'MyPassword': value,
}
]
l1 = order.log_action(
'dummy.log',
data=log_data,
)
l1.refresh_from_db()
assert json.loads(l1.data) == log_data_filtered
@pytest.mark.django_db
def test_email_shredder(event, order):
l1 = order.log_action(
+51 -3
View File
@@ -147,6 +147,7 @@ def test_api_create(env, token_client):
assert resp.status_code == 201
tl = env[0].ticket_layouts.get(pk=resp.data["id"])
assert tl.background
assert "cachedfile" not in tl.background.name
@pytest.mark.django_db
@@ -163,6 +164,21 @@ def test_api_create_validate_default(env, token_client):
assert resp.status_code == 400
assert resp.data == {"non_field_errors": ["You cannot have two layouts with default = True"]}
env[0].ticket_layouts.filter(default=True).update(default=False)
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketlayouts/'.format(env[0].slug, env[0].slug),
{
'name': 'Foo',
'default': True,
'layout': [],
},
format='json'
)
assert resp.status_code == 201
tl = env[0].ticket_layouts.get(pk=resp.data["id"])
assert tl.default
@pytest.mark.django_db
def test_api_create_validate_layout(env, token_client):
@@ -215,6 +231,7 @@ def test_api_update(env, token_client):
assert json.loads(env[1].layout) == [
{"type": "barcodearea", "left": "7.00", "bottom": "11.15", "size": "45.00", "content": "secret"}
]
assert "cachedfile" not in env[1].background.name
@pytest.mark.django_db
@@ -230,6 +247,18 @@ def test_api_update_validate_default(env, token_client):
assert resp.status_code == 400
assert resp.data == {"non_field_errors": ["You cannot have two layouts with default = True"]}
env[0].ticket_layouts.filter(default=True).update(default=False)
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/ticketlayouts/{}/'.format(env[0].slug, env[0].slug, tl2.pk),
{
"default": True,
},
format='json'
)
assert resp.status_code == 200
tl2.refresh_from_db()
assert tl2.default
@pytest.mark.django_db
def test_api_delete(env, token_client):
@@ -282,7 +311,7 @@ def test_renderer_batch_invalid(env, token_client, position):
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"orderposition": ["Invalid pk \"-2\" - object does not exist."]}]}
assert resp.data == {"parts": {0: {"orderposition": ["Invalid pk \"-2\" - object does not exist."]}}}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
@@ -297,7 +326,7 @@ def test_renderer_batch_invalid(env, token_client, position):
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"override_layout": ["Invalid pk \"-2\" - object does not exist."]}]}
assert resp.data == {"parts": {0: {"override_layout": ["Invalid pk \"-2\" - object does not exist."]}}}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
@@ -312,7 +341,7 @@ def test_renderer_batch_invalid(env, token_client, position):
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"override_channel": ["Object with identifier=magic does not exist."]}]}
assert resp.data == {"parts": {0: {"override_channel": ["Object with identifier=magic does not exist."]}}}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
@@ -327,3 +356,22 @@ def test_renderer_batch_invalid(env, token_client, position):
)
assert resp.status_code == 400
assert resp.data == {"parts": ["Please do not submit more than 1000 parts."]}
@pytest.mark.django_db
def test_api_permission(env, token_client):
t = env[0].organizer.teams.get()
t.limit_event_permissions = {"event.orders:read": True}
t.all_event_permissions = False
t.save()
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/ticketlayouts/{}/'.format(env[0].slug, env[0].slug, env[1].pk),
{
"name": "Bar",
"layout": [
{"type": "barcodearea", "left": "7.00", "bottom": "11.15", "size": "45.00", "content": "secret"}
]
},
format='json'
)
assert resp.status_code == 403
+13 -4
View File
@@ -193,7 +193,7 @@ def test_org_login_logout(env, client, mocker):
})
assert r.status_code == 302
customer_signed_in.send.assert_called_once_with(customer.organizer, customer=customer)
customer_signed_in.send.assert_called_once_with(customer.organizer, customer=customer, profile=None)
r = client.get('/bigevents/account/')
assert r.status_code == 200
@@ -349,7 +349,10 @@ def test_org_sso_login_new_customer(env, client, provider, mocker):
c = Customer.objects.get(provider=provider)
assert c.external_identifier == "abcdf"
customer_created.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c, profile={
'uid': 'abcdf', 'email': 'test@example.org',
'oidc_claims': {'sub': 'abcdf', 'email': 'test@example.org'},
})
r = client.get('/bigevents/account/')
assert r.status_code == 200
@@ -401,7 +404,10 @@ def test_org_sso_login_returning_customer_new_email(env, client, provider, mocke
_sso_login(client, provider)
with scopes_disabled():
c = Customer.objects.get(provider=provider)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c, profile={
'uid': 'abcdf', 'email': 'test@example.org',
'oidc_claims': {'sub': 'abcdf', 'email': 'test@example.org'},
})
customer_signed_in.send.reset_mock()
r = client.get('/bigevents/account/logout')
@@ -410,7 +416,10 @@ def test_org_sso_login_returning_customer_new_email(env, client, provider, mocke
_sso_login(client, provider, 'new@example.net')
c.refresh_from_db()
assert c.email == "new@example.net"
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c)
customer_signed_in.send.assert_called_once_with(c.organizer, customer=c, profile={
'uid': 'abcdf', 'email': 'new@example.net',
'oidc_claims': {'sub': 'abcdf', 'email': 'new@example.net'},
})
@pytest.mark.django_db(transaction=True)
+55
View File
@@ -1393,6 +1393,61 @@ class OrderChangeAddonsTest(BaseOrdersTest):
assert a.item == self.workshop2
assert a.variation == self.workshop2a
def test_quota_sold_out_change_hide_sold_out(self):
self.event.settings.hide_sold_out = True
self.workshopquota.size = 1
self.workshopquota.items.remove(self.workshop2)
self.workshopquota.variations.remove(self.workshop2a)
self.workshopquota.variations.remove(self.workshop2b)
self.workshopquota.save()
self.workshopquota2 = Quota.objects.create(event=self.event, name='Workshop 2', size=5)
self.workshopquota2.items.add(self.workshop2)
self.workshopquota2.variations.add(self.workshop2a)
self.workshopquota2.variations.add(self.workshop2b)
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()
response = self.client.get(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
assert 'Workshop 1' in response.content.decode()
doc = BeautifulSoup(response.content.decode(), "lxml")
assert doc.select(f'input[name=cp_{self.ticket_pos.pk}_item_{self.workshop1.pk}]')[0].attrs['checked']
response = self.client.post(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{
f'cp_{self.ticket_pos.pk}_variation_{self.workshop2.pk}_{self.workshop2a.pk}': '1'
},
follow=True
)
doc = BeautifulSoup(response.content.decode(), "lxml")
assert 'alert-danger' not in response.content.decode()
form_data = extract_form_fields(doc.select('.main-box form')[0])
form_data['confirm'] = 'true'
response = self.client.post(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), form_data, follow=True
)
assert 'alert-success' in response.content.decode()
with scopes_disabled():
a = self.ticket_pos.addons.get(canceled=False)
assert a.item == self.workshop2
assert a.variation == self.workshop2a
def _assert_ws2a_not_allowed(self):
response = self.client.get(
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
+31 -26
View File
@@ -422,37 +422,42 @@ class OrdersTest(BaseOrdersTest):
with scopes_disabled():
assert self.order.invoices.count() == 1
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'zipcode': '12345',
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
with self.captureOnCommitCallbacks(execute=True):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'zipcode': '12345',
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
with scopes_disabled():
assert self.order.invoices.count() == 3
last_two = self.order.invoices.order_by('-pk')[:2]
for inv in last_two:
assert inv.transmission_status != Invoice.TRANSMISSION_STATUS_PENDING
self.event.settings.set('invoice_reissue_after_modify', False)
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'zipcode': '54321',
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
with self.captureOnCommitCallbacks(execute=True):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'zipcode': '54321',
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
with scopes_disabled():
assert self.order.invoices.count() == 3