Compare commits

..
148 Commits
Author SHA1 Message Date
Raphael Michel b382f30995 Make it harder for dates way in the past to enter the system
The input components even allow adding setting dates before christus,
but then retrieving the rows back from the DB is impossible:

    File "django/db/models/sql/compiler.py", line 1661, in execute_sql
      return list(result)
    File "django/db/models/sql/compiler.py", line 2171, in cursor_iter
      for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
    File "django/db/models/sql/compiler.py", line 2171, in <lambda>
      for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel):
    File "django/db/utils.py", line 98, in inner
      return func(*args, **kwargs)
    ValueError: year -1 is out of range
2026-11-13 10:11:44 +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
Raphael Michel ff102d561d Bump version to 2026.9.0 2026-10-29 09:59:06 +01:00
Raphael Michel 1c7d0233fd Fix test failure after security patch 2026-10-28 14:41:38 +01:00
Raphael Michel 936977972e Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6452 of 6452 strings)

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

powered by weblate
2026-10-28 12:15:10 +01:00
Raphael Michel 46f2baa103 Translations: Update German
Currently translated at 100.0% (6452 of 6452 strings)

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

powered by weblate
2026-10-28 12:15:10 +01:00
Raphael Michel 1308630f6c Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@pretix.eu>
2026-10-28 11:48:31 +01:00
CVZ-es 45bd124629 Translations: Update French
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-28 11:47:55 +01:00
CVZ-es a1b9a557e0 Translations: Update French
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-28 11:47:55 +01:00
dependabot[bot] 1860e24f8c Update python-dateutil requirement from ==2.9.* to ==3.0.* (#6710)
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/2.9.0...3.0.0)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-version: 3.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-10-28 11:45:53 +01:00
Raphael Michel cfbc903595 Customer SSO: Check values for "email_verified" without relying on the type (#6708) 2026-10-28 11:40:50 +01:00
Raphael Michel 9632f69edb [SECURITY] Fix more ID lookups for file downloads (CVE-2026-101272) 2026-10-28 11:22:49 +01:00
Raphael Michel 87e3b33537 [SECURITY] Customer SSO: Fix missing validation of grant type (CVE-2026-101274, Z#23251494) 2026-10-28 11:22:49 +01:00
Kara Engelhardt a4cbb58217 [SECURITY] Properly check gift card acceptance everywhere (CVE-2026-101273) 2026-10-28 11:22:49 +01:00
Raphael Michel 15527fc5de Event list: Reduce disk load of SQL query (#6505) 2026-10-28 09:26:47 +01:00
Richard Schreiber 17fc69af18 Add support for relative date to subevents batch-edit (Z#23250805) (#6693) 2026-10-28 09:08:32 +01:00
Richard Schreiber da622e6752 Fix styling for disabled buttons other than btn-default (#6685) 2026-10-27 10:40:50 +01:00
Tim b18dde855c Translations: Update Spanish
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-27 10:08:20 +01:00
dependabot[bot] 9dce205e12 Bump django-bootstrap3 from 26.2 to 26.3 (#6673)
Bumps [django-bootstrap3](https://github.com/zostera/django-bootstrap3) from 26.2 to 26.3.
- [Changelog](https://github.com/zostera/django-bootstrap3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zostera/django-bootstrap3/compare/v26.2...v26.3)

---
updated-dependencies:
- dependency-name: django-bootstrap3
  dependency-version: '26.3'
  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-10-26 17:58:40 +01:00
dependabot[bot] 5d47ebd8b8 Update djangorestframework requirement from >=3.18.2 to >=3.18.3 (#6698)
Updates the requirements on [djangorestframework](https://github.com/encode/django-rest-framework) to permit the latest version.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.18.2...3.18.3)

---
updated-dependencies:
- dependency-name: djangorestframework
  dependency-version: 3.18.3
  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-10-26 17:56:33 +01:00
dependabot[bot] c550a35e62 Bump vite from 8.0.16 to 8.3.5 (#6690)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.16 to 8.3.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.3.5
  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-10-26 17:56:22 +01:00
dependabot[bot] 76c69e785e Bump markdown from 3.11 to 3.11.2 (#6697)
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.11 to 3.11.2.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.11.0...3.11.2)

---
updated-dependencies:
- dependency-name: markdown
  dependency-version: 3.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-26 17:56:11 +01:00
Lukas Bockstaller 59b207b756 API: run clean_live() after updates to the event (Z#23250960) (#6695)
* API: run `clean_live()` after updates to the event

* raise correct validation error

* add test cases for required meta_properties and going live
2026-10-26 11:34:59 +01:00
dependabot[bot]andLukas Bockstaller c9f7bf8680 Update djangorestframework requirement from ==3.17.* to ==3.18.* (#6470)
* Update djangorestframework requirement from ==3.17.* to ==3.18.*

Updates the requirements on [djangorestframework](https://github.com/encode/django-rest-framework) to permit the latest version.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.17.0...3.18.0)

---
updated-dependencies:
- dependency-name: djangorestframework
  dependency-version: 3.18.0
  dependency-type: direct:production
...

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

* RestFramework: use LIST_SERIALIZER_ERRORS_AS_DICT compatibility option

* set lower bound for djangorestframework

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lukas Bockstaller <bockstaller@pretix.eu>
2026-10-23 16:05:54 +02:00
Martin Gross 2fcb51bf2f Translations: Update Basque
Currently translated at 46.5% (121 of 260 strings)

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

powered by weblate
2026-10-23 15:03:21 +02:00
Martin Gross 6a762854e2 Translations: Update Basque
Currently translated at 48.0% (3093 of 6442 strings)

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

powered by weblate
2026-10-23 15:03:21 +02:00
IÑIGO ANSA b29fb69790 Translations: Update Basque
Currently translated at 48.0% (3093 of 6442 strings)

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

powered by weblate
2026-10-23 13:21:10 +02:00
IÑIGO ANSA c7a23da055 Translations: Update Basque
Currently translated at 45.7% (119 of 260 strings)

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

powered by weblate
2026-10-23 13:21:10 +02:00
IÑIGO ANSA a3704c54e9 Translations: Update Basque
Currently translated at 47.9% (3091 of 6442 strings)

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

powered by weblate
2026-10-23 13:21:10 +02:00
Daniel Jaraud 764d49482a Translations: Update French
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-23 13:21:10 +02:00
Raphael Michel e188796a81 Bump Pillow to 13.0.* (#6694) 2026-10-22 15:44:57 +02:00
Lukas Bockstaller 1ca41269cd test: unset EMAIL_HOST_USER and EMAIL_HOST_PASSWORD for smtp tests (#6688)
Harden tests so that they don't fail locally when the config values are set in pretix.cfg
2026-10-22 12:03:57 +02:00
Lukas Bockstaller 9581c08949 put last_seen attribute into the defaults parameter of the get_or_create (#6689) 2026-10-22 12:00:35 +02:00
luelista 5393aff5f0 Attempt to make customer account SSO configuration a bit less confusing (#6586)
* More explanation text, better titles
* Direct links to docs
* Add all information to copy/paste in one place
2026-10-22 10:57:37 +02:00
Raphael Michel 03e488ef20 Add comment on known permission issue 2026-10-21 13:55:13 +02:00
Douwe Somers 619c68e51a Fix interpolate function (#6672) 2026-10-21 10:59:46 +02:00
Richard Schreiber 10c1d5106d API: make bundle_variation optional when creating items with bundles (#6681)
* API: make bundle_variation optional when creating items with bundles

* Add test
2026-10-21 10:32:15 +02:00
Martin Gross 876566759d Stripe: Reenable Continue button for Affirm/Klarna even if CC-element is not present 2026-10-21 10:05:34 +02:00
Christiaan de Die le Clercq c6a0e06e87 Translations: Update Dutch
Currently translated at 96.9% (6247 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Martin Gross 39e4a6c9f7 Translations: Update Luxembourgish
Currently translated at 25.1% (1619 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Martin Gross 431bd038fc Translations: Update German
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Martin Gross 2374a33424 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Martin Gross 463de6ae12 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Jakob 117b300c86 Translations: Update Luxembourgish
Currently translated at 25.1% (1619 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Jakob 36d7f684c3 Translations: Update German
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Jakob 9ceab479a4 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
Jakob 6229b4a848 Translations: Update German
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-20 12:09:18 +02:00
dependabot[bot] 16d0f17587 Bump pycparser from 3.0 to 3.11 (#6669)
Bumps [pycparser](https://github.com/eliben/pycparser) from 3.0 to 3.11.
- [Release notes](https://github.com/eliben/pycparser/releases)
- [Commits](https://github.com/eliben/pycparser/compare/release_v3.00...release_v3.11)

---
updated-dependencies:
- dependency-name: pycparser
  dependency-version: '3.11'
  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-10-20 12:03:58 +02:00
dependabot[bot] a1f012805a Update pypdf requirement from ==6.19.* to ==6.20.* (#6670)
Updates the requirements on [pypdf](https://github.com/py-pdf/pypdf) to permit the latest version.
- [Release notes](https://github.com/py-pdf/pypdf/releases)
- [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/pypdf/compare/6.19.0...6.20.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-version: 6.20.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-10-20 12:03:47 +02:00
Martin Gross b5ae2ccb25 PayPal2: CCID3 messaging (#6679) 2026-10-20 11:01:37 +02:00
Richard Schreiber f196417005 Fix customer export for no attendee_name available (#6676) 2026-10-19 15:20:41 +02:00
Lukas Bockstaller 090a73d02a migrate to new peppol lookup endpoints (#6677)
https://openpeppol.atlassian.net/wiki/spaces/PTPUB/pages/5059608580/SML+Insourcing
2026-10-19 11:08:32 +02:00
Lukas Bockstaller e4b29f56a4 updates mark_oder_expired() docstring to match reality (#6675) 2026-10-16 16:45:15 +02:00
pajowu 531758d444 Save OutgoingMail guid generated by mail() (#6674) 2026-10-16 10:34:48 +02:00
pajowu b1ec585fc8 Add attendee_name_for_salutation-Placeholder (Z#23249905) (#6666) 2026-10-16 10:34:28 +02:00
pajowu a076461eae Add missing presale_has_ended_text to EventIndex context (Z#23249717) (#6663)
Seems to have been lost in merge conflict https://github.com/pretix/pretix/commit/5ffb1f469e8f066269b407a64502353792041786
2026-10-16 10:34:06 +02:00
pajowu 8048859fb4 Match typeahead event ordering to organizer event list (Z#23249032) (#6661) 2026-10-16 10:33:39 +02:00
Martin Gross 2e4e60db34 Stripe: Enable Wero 2026-10-14 10:10:44 +02:00
Raphael Michel 6b7f2ad684 API: Fix PATCH on item addon sets without min_count or max_count (fixes #6650) (#6651) 2026-10-10 09:53:49 +02:00
dependabot[bot] 54b249acf5 Update pycryptodome requirement from ==3.23.* to ==3.24.* (#6657)
Updates the requirements on [pycryptodome](https://github.com/Legrandin/pycryptodome) to permit the latest version.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/compare/v3.23.0...v3.24.0)

---
updated-dependencies:
- dependency-name: pycryptodome
  dependency-version: 3.24.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-10-09 22:56:43 +02:00
dependabot[bot] 0cf2ba763b Update sphinxcontrib-httpdomain requirement from ~=2.0.0 to ~=2.1.0 (#6660)
Updates the requirements on [sphinxcontrib-httpdomain](https://github.com/sphinx-contrib/httpdomain) to permit the latest version.
- [Release notes](https://github.com/sphinx-contrib/httpdomain/releases)
- [Changelog](https://github.com/sphinx-contrib/httpdomain/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/sphinx-contrib/httpdomain/compare/2.0.0...2.1.0)

---
updated-dependencies:
- dependency-name: sphinxcontrib-httpdomain
  dependency-version: 2.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-10-09 22:56:27 +02:00
dependabot[bot] b44c3d707e Update fakeredis requirement from ==2.38.* to ==2.39.* (#6654)
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.38.0...v2.39.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-version: 2.39.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-10-09 22:56:17 +02:00
dependabot[bot] 45603c6624 Update css-inline requirement from ==0.21.* to ==0.22.* (#6655)
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.21.0...c-v0.22.0)

---
updated-dependencies:
- dependency-name: css-inline
  dependency-version: 0.22.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-10-09 22:56:13 +02:00
Richard Schreiber ba36d649c6 Fix sendmail migration activating plugin on events (#6656) 2026-10-08 12:35:41 +02:00
Richard Schreiber ca90b2af09 Fix prefix for event notifications (#6653) 2026-10-08 10:09:38 +02:00
Phin Wolkwitz fe43862fa4 Sendmail: Handle scheduled mails in inactive plugin (Z#23243588) (#6587)
* Activate plugin where rules are currently active (migration)
* Check if plugin is active before sending mails
* Disable rules when the plugin gets (manually) uninstalled
2026-10-07 10:33:16 +02:00
Raphael MichelandRichard Schreiber 411cd764a6 Order changes: Log more information about taxes (#6637)
* Order changes: Log more information about taxes

* Apply batched suggestions from code review

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>

* Add to tax_rule change as well

---------

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
2026-10-07 09:28:35 +02:00
6618e3a039 Organizer-level notifications (#6462)
* Organizer-level notifications

* Update signals

* Update signal docs

* Add tests

* Apply batched suggestions from code review

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>

* Update src/pretix/base/models/organizer.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-10-07 09:28:15 +02:00
Richard Schreiber b7cd97c33e API: fix payment confirm if request.data is empty string (#6634)
* API: fix payment confirm if request.data is empty string

* fix fallback value
2026-10-07 09:21:30 +02:00
dependabot[bot] eae3229e68 Update pytest-mock requirement from ==3.15.* to ==3.16.* (#6628)
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.15.0...v3.16.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-version: 3.16.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-10-07 09:13:05 +02:00
Raphael Michel d291cd02e7 Docs: Fix invalid example of i18n string 2026-10-07 08:55:26 +02:00
dependabot[bot] e5dcb50229 Bump @vue/server-renderer and vue (#6646)
Bumps [@vue/server-renderer](https://github.com/vuejs/core/tree/HEAD/packages/server-renderer) to 3.5.43 and updates ancestor dependency [vue](https://github.com/vuejs/core). These dependencies need to be updated together.


Updates `@vue/server-renderer` from 3.5.30 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.43/packages/server-renderer)

Updates `vue` from 3.5.30 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.30...v3.5.43)

---
updated-dependencies:
- dependency-name: "@vue/server-renderer"
  dependency-version: 3.5.43
  dependency-type: indirect
- dependency-name: vue
  dependency-version: 3.5.43
  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-10-07 08:54:01 +02:00
dependabot[bot] 9d333bc2cf Update cryptography requirement from >=50.0.1 to >=50.0.2 (#6648)
Updates the requirements on [cryptography](https://github.com/pyca/cryptography) to permit the latest version.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/50.0.1...50.0.2)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.2
  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-10-06 22:33:33 +02:00
dependabot[bot] 45038d07f3 Bump django-filter from 26.1 to 26.2 (#6647)
Bumps [django-filter](https://github.com/carltongibson/django-filter) from 26.1 to 26.2.
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/26.1...26.2)

---
updated-dependencies:
- dependency-name: django-filter
  dependency-version: '26.2'
  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-10-06 22:33:30 +02:00
dependabot[bot] 00f0dfec88 Bump postcss-selector-parser from 7.1.5 to 7.1.6 (#6644)
Bumps [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/postcss/postcss-selector-parser/releases)
- [Changelog](https://github.com/postcss/postcss-selector-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-selector-parser/compare/7.1.5...7.1.6)

---
updated-dependencies:
- dependency-name: postcss-selector-parser
  dependency-version: 7.1.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:30:52 +02:00
dependabot[bot] 1975a9d891 Bump source-map-js from 1.2.1 to 1.2.2 (#6645)
Bumps [source-map-js](https://github.com/7rulnik/source-map-js) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/7rulnik/source-map-js/releases)
- [Changelog](https://github.com/7rulnik/source-map-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/7rulnik/source-map-js/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: source-map-js
  dependency-version: 1.2.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 22:30:42 +02:00
dependabot[bot] 02b9b6941d Bump smol-toml from 1.7.1 to 1.9.0 (#6643)
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.7.1 to 1.9.0.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.7.1...v1.9.0)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.9.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-10-06 22:30:34 +02:00
dependabot[bot] da2ac6c386 Update tlds requirement from >=2026072401 to >=2026093003 (#6642)
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: '2026093003'
  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-10-06 11:39:19 +02:00
Hijiri Umemoto b9fbe699ab Translations: Update Japanese
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Oksigenia 8e3f3cc8c8 Translations: Update Spanish
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Oksigenia 945f80b288 Translations: Update Spanish
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 25799da70d Translations: Update Russian
Currently translated at 25.9% (1672 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 6b01023d16 Translations: Update Thai
Currently translated at 100.0% (260 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 98644db976 Translations: Update Thai
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 814ae7f4d9 Translations: Update Russian
Currently translated at 44.6% (116 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 ae39a1e112 Translations: Update Russian
Currently translated at 25.9% (1670 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 61b94f1128 Translations: Update Russian
Currently translated at 41.9% (109 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 dbb586ad5a Translations: Update Russian
Currently translated at 24.3% (1569 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 295b0443fb Translations: Update Thai
Currently translated at 99.9% (6441 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 31031225d3 Translations: Update Thai
Currently translated at 97.7% (6299 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst d600115ea5 Translations: Update Thai
Currently translated at 95.2% (6134 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
CVZ-es 3d9ebb3072 Translations: Update Spanish
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
CVZ-es c9667e77c3 Translations: Update French
Currently translated at 100.0% (6442 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst b2b797f4d3 Translations: Update Thai
Currently translated at 83.2% (5366 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Nate Horst 693adbc727 Translations: Update Thai
Currently translated at 81.3% (5242 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
szurofkamarciidfbe08444ef04788 8d9840be8d Translations: Update Hungarian
Currently translated at 98.7% (6364 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Raphael Michel 73c0c61b6d Translations: Update Swedish
Currently translated at 98.4% (256 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Raphael Michel a813269d1f Translations: Update Swedish
Currently translated at 85.4% (5503 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 d6bc1fa977 Translations: Update Russian
Currently translated at 38.8% (101 of 260 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
Translate pretix user 594 249edfa2c4 Translations: Update Russian
Currently translated at 20.5% (1322 of 6442 strings)

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

powered by weblate
2026-10-06 11:04:53 +02:00
dependabot[bot] 0c28151d30 Update sentry-sdk requirement from ==2.70.* to ==2.71.* (#6632)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.70.0...2.71.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.71.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-10-06 10:59:40 +02:00
pajowu 5bb3b7e35e Fix help_text showing as 'None' in orderimport&permissions (#6631) 2026-10-06 10:12:27 +02:00
Richard Schreiber 4119d04fba Add border to and increase text-contrast on disabled button (#6622)
* Add border to and increase text-contrast on disabled button

* Do not add border to disabled btn-link

* Add background to disabled button to further distinguish
2026-10-02 09:15:05 +02:00
212 changed files with 11378 additions and 5290 deletions

No files matched your search

+1 -1
View File
@@ -173,7 +173,7 @@ Datetime String in ISO 8601 format ``"2017-12-27T10:00:00Z"``
with timezone (normally UTC) ``"2017-12-27T10:00:00.596934Z"``,
``"2017-12-27T10:00:00+02:00"``
Date String in ISO 8601 format ``2017-12-27``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de_Informal": "rot"}``
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de-informal": "rot"}``
Money String with decimal number ``"23.42"``
Currency String with ISO 4217 code ``"EUR"``, ``"USD"``
Relative datetime *either* String in ISO 8601 ``"2017-12-27T10:00:00.596934Z"``,
+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**:
+3 -2
View File
@@ -11,7 +11,8 @@ Core
----
.. automodule:: pretix.base.signals
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
:members: periodic_task, event_live_issues, event_copy_data, email_filter,
register_notification_types, event_notification_sent, organizer_notification_sent,
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
register_ticket_secret_generators, gift_card_transaction_display,
register_text_placeholders, register_mail_placeholders, device_info_updated,
@@ -83,7 +84,7 @@ Dashboards
.. automodule:: pretix.control.signals
:no-index:
:members: event_dashboard_statistics, user_dashboard_widgets, event_dashboard_top
:members: event_dashboard_widgets, user_dashboard_widgets, event_dashboard_top
Ticket designs
""""""""""""""
+2 -2
View File
@@ -1,6 +1,6 @@
sphinx==9.1.*
sphinx==9.0.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+2 -2
View File
@@ -1,7 +1,7 @@
-e ../
sphinx==9.1.*
sphinx==9.0.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+243 -299
View File
@@ -40,30 +40,30 @@
"license": "MIT"
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"version": "7.29.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
"integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
"@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -73,13 +73,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -92,40 +92,6 @@
"dev": true,
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -378,30 +344,11 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -441,13 +388,13 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"version": "0.155.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
"integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
"url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@parcel/watcher": {
@@ -771,10 +718,27 @@
"node": ">=14"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz",
"integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz",
"integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==",
"cpu": [
"arm64"
],
@@ -789,9 +753,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz",
"integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==",
"cpu": [
"arm64"
],
@@ -806,9 +770,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz",
"integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==",
"cpu": [
"x64"
],
@@ -823,9 +787,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz",
"integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==",
"cpu": [
"x64"
],
@@ -840,9 +804,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz",
"integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==",
"cpu": [
"arm"
],
@@ -857,9 +821,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz",
"integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==",
"cpu": [
"arm64"
],
@@ -877,9 +841,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz",
"integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==",
"cpu": [
"arm64"
],
@@ -897,9 +861,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz",
"integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==",
"cpu": [
"ppc64"
],
@@ -917,9 +881,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz",
"integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==",
"cpu": [
"s390x"
],
@@ -937,9 +901,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz",
"integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==",
"cpu": [
"x64"
],
@@ -957,9 +921,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz",
"integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==",
"cpu": [
"x64"
],
@@ -977,9 +941,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz",
"integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==",
"cpu": [
"arm64"
],
@@ -993,29 +957,10 @@
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.10.0",
"@emnapi/runtime": "1.10.0",
"@napi-rs/wasm-runtime": "^1.1.4"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz",
"integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==",
"cpu": [
"arm64"
],
@@ -1030,9 +975,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz",
"integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==",
"cpu": [
"x64"
],
@@ -1074,17 +1019,6 @@
"eslint": "^9.0.0 || ^10.0.0"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
@@ -1447,53 +1381,53 @@
"license": "MIT"
},
"node_modules/@vue/compiler-core": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
"integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz",
"integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/shared": "3.5.30",
"@babel/parser": "^7.29.8",
"@vue/shared": "3.5.43",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
"integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz",
"integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-core": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
"integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz",
"integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/compiler-core": "3.5.30",
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30",
"@babel/parser": "^7.29.8",
"@vue/compiler-core": "3.5.43",
"@vue/compiler-dom": "3.5.43",
"@vue/compiler-ssr": "3.5.43",
"@vue/shared": "3.5.43",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.8",
"postcss": "^8.5.28",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
"integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz",
"integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-dom": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/eslint-config-typescript": {
@@ -1537,53 +1471,51 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
"integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz",
"integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.30"
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
"integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz",
"integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/reactivity": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
"integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz",
"integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/runtime-core": "3.5.30",
"@vue/shared": "3.5.30",
"@vue/reactivity": "3.5.43",
"@vue/runtime-core": "3.5.43",
"@vue/shared": "3.5.43",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
"integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz",
"integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30"
},
"peerDependencies": {
"vue": "3.5.30"
"@vue/compiler-ssr": "3.5.43",
"@vue/runtime-dom": "3.5.43",
"@vue/shared": "3.5.43"
}
},
"node_modules/@vue/shared": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
"integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz",
"integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==",
"license": "MIT"
},
"node_modules/acorn": {
@@ -2810,9 +2742,9 @@
}
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
@@ -2826,23 +2758,23 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
"lightningcss-android-arm64": "1.33.0",
"lightningcss-darwin-arm64": "1.33.0",
"lightningcss-darwin-x64": "1.33.0",
"lightningcss-freebsd-x64": "1.33.0",
"lightningcss-linux-arm-gnueabihf": "1.33.0",
"lightningcss-linux-arm64-gnu": "1.33.0",
"lightningcss-linux-arm64-musl": "1.33.0",
"lightningcss-linux-x64-gnu": "1.33.0",
"lightningcss-linux-x64-musl": "1.33.0",
"lightningcss-win32-arm64-msvc": "1.33.0",
"lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
@@ -2861,9 +2793,9 @@
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
@@ -2882,9 +2814,9 @@
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
@@ -2903,9 +2835,9 @@
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
@@ -2924,9 +2856,9 @@
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
@@ -2945,13 +2877,16 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2966,13 +2901,16 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2987,13 +2925,16 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3008,13 +2949,16 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3029,9 +2973,9 @@
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
@@ -3050,9 +2994,9 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
@@ -3190,9 +3134,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"version": "3.3.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"funding": [
{
"type": "github",
@@ -3353,9 +3297,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3366,9 +3310,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"version": "8.5.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
"integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
"funding": [
{
"type": "opencollective",
@@ -3385,18 +3329,18 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.16",
"nanoid": "^3.3.20",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
"source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
"integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
"integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3642,13 +3586,13 @@
}
},
"node_modules/rolldown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz",
"integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.133.0",
"@oxc-project/types": "=0.155.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -3658,21 +3602,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.3"
"@rolldown/binding-android-arm-eabi": "1.2.15",
"@rolldown/binding-android-arm64": "1.2.15",
"@rolldown/binding-darwin-arm64": "1.2.15",
"@rolldown/binding-darwin-x64": "1.2.15",
"@rolldown/binding-freebsd-x64": "1.2.15",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.15",
"@rolldown/binding-linux-arm64-gnu": "1.2.15",
"@rolldown/binding-linux-arm64-musl": "1.2.15",
"@rolldown/binding-linux-ppc64-gnu": "1.2.15",
"@rolldown/binding-linux-s390x-gnu": "1.2.15",
"@rolldown/binding-linux-x64-gnu": "1.2.15",
"@rolldown/binding-linux-x64-musl": "1.2.15",
"@rolldown/binding-openharmony-arm64": "1.2.15",
"@rolldown/binding-win32-arm64-msvc": "1.2.15",
"@rolldown/binding-win32-x64-msvc": "1.2.15"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
@@ -4162,9 +4106,9 @@
}
},
"node_modules/smol-toml": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz",
"integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz",
"integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -4185,9 +4129,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -4497,16 +4441,16 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
"integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.15",
"rolldown": "1.0.3",
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.30",
"rolldown": "~1.2.14",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -4523,7 +4467,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.18",
"@vitejs/devtools": "^0.7.1 || ^0.8.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -4592,16 +4536,16 @@
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
"version": "3.5.43",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz",
"integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-sfc": "3.5.30",
"@vue/runtime-dom": "3.5.30",
"@vue/server-renderer": "3.5.30",
"@vue/shared": "3.5.30"
"@vue/compiler-dom": "3.5.43",
"@vue/compiler-sfc": "3.5.43",
"@vue/runtime-dom": "3.5.43",
"@vue/server-renderer": "3.5.43",
"@vue/shared": "3.5.43"
},
"peerDependencies": {
"typescript": "*"
+21 -24
View File
@@ -27,26 +27,26 @@ 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.1",
"css-inline==0.21.*",
"cryptography>=50.0.2",
"css-inline==0.23.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"dnspython==2.9.*",
"Django[argon2]==5.2.*,>=5.2.17",
"django-bootstrap3==26.2",
"django-bootstrap3==26.3",
"django-compressor==4.6.0",
"django-countries==9.1.*",
"django-filter==26.1",
"django-filter==26.2",
"django-formset-js-improved==0.5.0.5",
"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",
@@ -57,18 +57,15 @@ dependencies = [
"django-redis==7.0.*",
"django-scopes==2.1.*",
"django-statici18n==2.8.*",
"djangorestframework==3.17.*",
"dnspython==2.8.*",
"djangorestframework>=3.18.3",
"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", # 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.*",
@@ -77,28 +74,28 @@ dependencies = [
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.15.*",
"phonenumberslite==9.0.*",
"Pillow==12.3.*",
"Pillow==13.0.*",
"pretix-plugin-build",
"protobuf==7.36.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
"pycryptodome==3.23.*",
"pypdf==6.19.*",
"pycparser==3.12",
"pycryptodome==3.24.*",
"pypdf==6.20.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"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.70.*",
"sentry-sdk==2.71.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2026072401",
"tlds>=2026102900",
"tqdm==4.*",
"ua-parser==1.0.*",
"vobject==0.9.*",
@@ -112,17 +109,17 @@ dev = [
"aiohttp==3.14.*",
"coverage",
"coveralls",
"fakeredis==2.38.*",
"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.15.*",
"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.dev0"
__version__ = "2026.10.0.dev0"
+1 -1
View File
@@ -27,4 +27,4 @@ class PretixApiConfig(AppConfig):
label = 'pretixapi'
def ready(self):
from . import signals, webhooks # noqa
from . import monkeypatches, signals, webhooks # noqa
+1 -1
View File
@@ -76,7 +76,7 @@ class DeviceTokenAuthentication(TokenAuthentication):
except DeviceLastSeen.DoesNotExist:
# First request from device, create model, ignore result. Use get_or_create to be safe
# against concurrent create requests
DeviceLastSeen.objects.get_or_create(device=device, last_seen=now())
DeviceLastSeen.objects.get_or_create(device=device, defaults={"last_seen": now()})
else:
if now() - last_seen_obj.last_seen < timedelta(seconds=10):
# We don't need to know the last seen info of a device to more precision than this,
+5 -5
View File
@@ -19,12 +19,12 @@
# 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/>.
#
def get_session_key_for_api_auth(user, auth):
def get_session_key_for_api_auth(user, auth, prefix='api-upload'):
if user.is_authenticated:
return f'api-upload-User-{user.pk}'
return f'{prefix}-User-{user.pk}'
else:
return f'api-upload-{str(type(auth))}-{auth.pk}'
return f'{prefix}-{str(type(auth))}-{auth.pk}'
def get_session_key_for_api_request(request):
return get_session_key_for_api_auth(request.user, request.auth)
def get_session_key_for_api_request(request, prefix='api-upload'):
return get_session_key_for_api_auth(request.user, request.auth, prefix)
+27
View File
@@ -0,0 +1,27 @@
import datetime
from django.db import models
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class CustomDateField(serializers.DateField):
def to_internal_value(self, value):
val = super().to_internal_value(value)
if val < datetime.date(1900, 1, 1):
raise ValidationError("Do not pass dates before 1900-01-01")
return val
class CustomDateTimeField(serializers.DateTimeField):
def to_internal_value(self, value):
val = super().to_internal_value(value)
if val < datetime.datetime(1900, 1, 1, tzinfo=datetime.timezone.utc):
raise ValidationError("Do not pass dates before 1900-01-01")
return val
serializers.ModelSerializer.serializer_field_mapping.update({
models.DateField: CustomDateField,
models.DateTimeField: CustomDateTimeField,
})
+12 -3
View File
@@ -35,10 +35,13 @@
import logging
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.exceptions import (
PermissionDenied, ValidationError as DjangoValidationError,
)
from django.db import transaction
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.html import strip_tags
from django.utils.translation import gettext as _
from django_countries.serializers import CountryFieldMixin
from pytz import common_timezones
@@ -218,8 +221,6 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
if self.instance is None:
raise ValidationError(_('Events cannot be created as \'live\'. Quotas and payment must be added to the '
'event before sales can go live.'))
else:
self.instance.clean_live()
return value
@cached_property
@@ -429,6 +430,14 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
event.set_active_plugins(plugins)
event.save()
live = validated_data.get('live') or event.live
if live:
event.refresh_from_db()
try:
event.clean_live()
except DjangoValidationError as e:
raise ValidationError({'live': [strip_tags(m) for m in e.messages]})
return event
+4 -2
View File
@@ -261,7 +261,9 @@ class ItemAddOnSerializer(serializers.ModelSerializer):
def validate(self, data):
data = super().validate(data)
ItemAddOn.clean_max_min_count(data.get('max_count'), data.get('min_count'))
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
ItemAddOn.clean_max_min_count(full_data.get('max_count'), full_data.get('min_count'))
return data
@@ -376,7 +378,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
def validate_bundles(self, value):
if not self.instance:
for b_data in value:
ItemBundle.clean_itemvar(self.context['event'], b_data['bundled_item'], b_data['bundled_variation'])
ItemBundle.clean_itemvar(self.context['event'], b_data.get('bundled_item'), b_data.get('bundled_variation'))
return value
def validate_addons(self, value):
+17 -3
View File
@@ -1240,6 +1240,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
return None
if pp not in self.context['event'].get_payment_providers():
raise ValidationError('The given payment provider is not known.')
if pp == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment. Use `use_gift_cards` instead")
return pp
def validate_payment_info(self, info):
@@ -1960,10 +1963,15 @@ class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
model = OrderPayment
fields = ('state', 'amount', 'payment_date', 'provider', 'info')
def validate_provider(self, value):
if value == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment")
return value
def create(self, validated_data):
order = OrderPayment(order=self.context['order'], **validated_data)
order.save()
return order
payment = OrderPayment(order=self.context['order'], **validated_data)
payment.save()
return payment
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
@@ -1975,6 +1983,12 @@ class OrderRefundCreateSerializer(I18nAwareModelSerializer):
model = OrderRefund
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info', 'comment')
def validate_provider(self, value):
if value == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment")
return value
def create(self, validated_data):
pid = validated_data.pop('payment', None)
if pid:
+4 -13
View File
@@ -41,10 +41,9 @@ from pretix.api.serializers.settings import SettingsSerializer
from pretix.base.auth import get_auth_backends
from pretix.base.i18n import get_language_without_region
from pretix.base.models import (
Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance,
GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer,
ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
Customer, Device, EventMetaProperty, GiftCard, GiftCardTransaction,
Membership, MembershipType, OrderPosition, Organizer, ReusableMedium,
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.permissions import (
@@ -270,15 +269,7 @@ class GiftCardSerializer(I18nAwareModelSerializer):
data = super().validate(data)
if 'secret' in data:
s = data['secret']
qs = GiftCard.objects.filter(
secret=s
).filter(
Q(issuer=self.context["organizer"]) |
Q(issuer__in=GiftCardAcceptance.objects.filter(
acceptor=self.context["organizer"],
active=True,
).values_list('issuer', flat=True))
)
qs = self.context["organizer"].accepted_gift_cards.filter(secret=s)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
+1 -1
View File
@@ -75,7 +75,7 @@ class ExportersMixin:
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
def download(self, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
cf = get_object_or_404(CachedFile, id=kwargs['cfid'], web_download=True)
if not cf.allowed_for_session(self.request, "exporters-api"):
return Response(
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
+2 -2
View File
@@ -39,7 +39,7 @@ from pretix.api.serializers.media import (
)
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import (
Checkin, GiftCard, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
Checkin, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
ReusableMedium,
)
from pretix.base.models.orders import PrintLog
@@ -88,7 +88,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
),
Prefetch(
'linked_giftcard',
queryset=GiftCard.objects.annotate(
queryset=self.request.organizer.accepted_gift_cards.annotate(
cached_value=Coalesce(Subquery(s), Decimal('0.00'))
)
)
+1 -1
View File
@@ -1683,7 +1683,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
@action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs):
payment = self.get_object()
force = request.data.get('force', False)
force = request.data.get('force', False) if request.data else False
send_mail = request.data.get('send_email', True) if request.data else True
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
+25 -6
View File
@@ -33,6 +33,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework.reverse import reverse
from pretix.api.auth.utils import get_session_key_for_api_request
from pretix.api.serializers.shredders import (
JobRunSerializer, ShredderSerializer,
)
@@ -68,8 +69,14 @@ class ShreddersMixin:
cf = get_object_or_404(
CachedFile,
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise Http404()
if cf.file:
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
@@ -118,10 +125,16 @@ class ShreddersMixin:
)
try:
CachedFile.objects.get(
cf = CachedFile.objects.get(
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise CachedFile.DoesNotExist()
except CachedFile.DoesNotExist:
return Response(
{'status': 'gone', 'message': 'May have succeeded or timed out'},
@@ -140,8 +153,14 @@ class ShreddersMixin:
cf = get_object_or_404(
CachedFile,
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise Http404()
if cf.file:
async_result = self.do_shred(cf)
url_kwargs = {
@@ -184,7 +203,7 @@ class ShreddersMixin:
cf = CachedFile(web_download=False)
cf.date = now()
cf.expires = now() + timedelta(hours=2)
cf.session_key = f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
cf.session_key = get_session_key_for_api_request(self.request, prefix='api-shredder')
cf.save()
d = serializer.data
for k, v in d.items():
@@ -224,7 +243,7 @@ class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
return export.apply_async(args=(
self.request.event.id,
list(shredders),
f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}',
get_session_key_for_api_request(self.request, prefix='api-shredder'),
str(cf.pk)
))
+5 -2
View File
@@ -239,8 +239,9 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
)
)
if 'email_verified' in userinfo and not userinfo['email_verified']:
# todo: how universal is this, do we need to make this configurable?
if 'email_verified' in userinfo and userinfo['email_verified'] not in (True, 1, "1", "True", "true", "TRUE"):
# Documented criteria for using a provider with pretix:
# "The SSO provider only allows verified accounts to log in or the SSO provider sends the claim email_verified. "
raise ValidationError(_('The email address on this account is not yet verified. Please first confirm the '
'email address in your customer account.'))
@@ -263,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
+5 -1
View File
@@ -160,7 +160,11 @@ class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
]
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
row.append(obj.attendee_name_parts.get(k, ''))
row.append(
obj.attendee_name_parts.get(k, '')
if obj.attendee_name_parts
else ''
)
yield row
def get_filename(self):
+6 -5
View File
@@ -55,9 +55,10 @@ from openpyxl.comments import Comment
from openpyxl.styles import Font, PatternFill
from pretix.base.models import (
Checkin, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
Checkin, GiftCardTransaction, Invoice, InvoiceAddress, Order,
OrderPosition, Question,
)
from pretix.base.models.giftcards import GiftCard
from pretix.base.models.orders import (
OrderFee, OrderPayment, OrderRefund, Transaction,
)
@@ -1326,12 +1327,12 @@ class GiftcardRedemptionListExporter(ListExporter):
order__event__in=self.events,
provider='giftcard',
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED),
).order_by('created')
).order_by('created').select_related("order__event__organizer")
refunds = OrderRefund.objects.filter(
order__event__in=self.events,
provider='giftcard',
state=OrderRefund.REFUND_STATE_DONE
).order_by('created')
).order_by('created').select_related("order__event__organizer")
objs = sorted(list(payments) + list(refunds), key=lambda o: (o.order.code, o.created))
@@ -1348,9 +1349,9 @@ class GiftcardRedemptionListExporter(ListExporter):
obj.order.code,
obj.full_id,
obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
gc.secret,
gc.secret if gc.accepted_by(obj.order.event.organizer) else '?',
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
gc.issuer
gc.issuer.slug
]
yield row
+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 = ['edelivery.tech.ec.europa.eu', 'acc.edelivery.tech.ec.europa.eu']
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))
+20 -1
View File
@@ -42,7 +42,7 @@ from django.conf import settings
from django.core.mail import get_connection
from django.core.validators import MinLengthValidator, RegexValidator
from django.db import models
from django.db.models import Q
from django.db.models import Exists, OuterRef, Q
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
@@ -312,6 +312,25 @@ class Organizer(LoggedModel):
)
i += 1
def get_users_with_permission(self, permission):
"""
Returns a queryset of users who have a specific permission to this organizer.
:return: Iterable of User
"""
from .auth import User
if permission:
qs = Team.objects.with_organizer_permission(permission)
else:
qs = Team.objects.all()
team_with_perm = qs.filter(
members__pk=OuterRef('pk'),
organizer=self,
)
return User.objects.filter(Exists(team_with_perm))
def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
+34 -11
View File
@@ -35,12 +35,13 @@
import logging
from collections import OrderedDict, namedtuple
from itertools import groupby
from typing import Optional, Union
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import Event, LogEntry
from pretix.base.models import Event, LogEntry, Organizer
from pretix.base.signals import register_notification_types
from pretix.base.templatetags.money import money_filter
from pretix.helpers.urls import mainreverse_absolute
@@ -57,7 +58,8 @@ class Notification:
"""
Represents a notification that is sent/shown to a user. A notification consists of:
* one ``event`` reference
* one ``event`` reference (can be ``Ǹone``)
* one ``organizer`` reference
* one ``title`` text that is shown e.g. in the email subject or in a headline
* optionally one ``detail`` text that may or may not be shown depending on the notification method
* optionally one ``url`` that should be absolute and point to the context of an notification (e.g. an order)
@@ -67,8 +69,12 @@ class Notification:
each consisting of a button label and an absolute URL to point to.
"""
def __init__(self, event: Event, title: str, detail: str=None, url: str=None):
def __init__(self, event: Optional[Event], title: str, detail: Optional[str]=None, url: Optional[str]=None, organizer: Optional[Organizer]=None):
assert event or organizer, "You need to pass either event or organizer"
if event and organizer:
assert event.organizer_id == organizer.id, "Organizer and event mismatch"
self.title = title
self.organizer = organizer or event.organizer
self.event = event
self.detail = detail
self.url = url
@@ -91,8 +97,16 @@ class Notification:
class NotificationType:
def __init__(self, event: Event = None):
self.event = event
def __init__(self, event_or_organizer: Union[Event, Organizer] = None):
if isinstance(event_or_organizer, Event):
self.event = event_or_organizer
self.organizer = event_or_organizer.organizer
elif isinstance(event_or_organizer, Organizer):
self.event = None
self.organizer = event_or_organizer
else:
self.event = None
self.organizer = None
def __repr__(self):
return '<NotificationType: {}>'.format(self.action_type)
@@ -121,31 +135,39 @@ class NotificationType:
"""
raise NotImplementedError() # NOQA
@property
def is_event_level(self) -> bool:
"""
Return `True` if this notification type can be configured per event (the default).
"""
return True
def build_notification(self, logentry: LogEntry) -> Notification:
"""
This is the main function that you should override. It is supposed to turn a log entry
object into a notification object that can then be rendered e.g. into an email.
"""
return Notification(
logentry.event,
logentry.display()
event=logentry.event,
title=logentry.display(),
organizer=logentry.organizer,
)
def get_all_notification_types(event=None):
def get_all_notification_types(event_or_organizer=None):
global _ALL_TYPES
if event is None and _ALL_TYPES:
if event_or_organizer is None and _ALL_TYPES:
return _ALL_TYPES
types = OrderedDict()
for recv, ret in register_notification_types.send(event):
for recv, ret in register_notification_types.send(event_or_organizer):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.action_type] = r
else:
types[ret.action_type] = ret
if event is None:
if event_or_organizer is None:
_ALL_TYPES = types
return types
@@ -181,6 +203,7 @@ class ParametrizedOrderNotificationType(NotificationType):
n = Notification(
event=logentry.event,
organizer=logentry.organizer,
title=self._title.format(order=order, event=logentry.event),
url=order_url
)
+35 -29
View File
@@ -1527,42 +1527,37 @@ class GiftCardPayment(BasePaymentProvider):
})
def refund_control_render(self, request, refund) -> str:
from .models import GiftCard
if 'gift_card' in refund.info_data:
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card'))
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
'gc': gc,
}
try:
gc = self.event.organizer.accepted_gift_cards.get(pk=refund.info_data.get('gift_card'))
ctx['gc'] = gc
except GiftCard.DoesNotExist:
pass
return template.render(ctx)
def payment_control_render(self, request, payment) -> str:
from .models import GiftCard
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
**({'error': payment.info_data[
'error']} if 'error' in payment.info_data else {}),
**({'gift_card_secret': payment.info_data[
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
}
if any(key in payment.info_data for key in ('gift_card', 'error')):
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
**({'error': payment.info_data[
'error']} if 'error' in payment.info_data else {}),
**({'gift_card_secret': payment.info_data[
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
}
try:
ctx['gc'] = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
except GiftCard.DoesNotExist:
pass
try:
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
ctx = {
'gc': gc,
}
except GiftCard.DoesNotExist:
pass
finally:
return template.render(ctx)
if any(key in ctx for key in ('gc', 'gift_card_secret', 'error')):
return template.render(ctx)
def payment_control_render_short(self, payment: OrderPayment) -> str:
d = payment.info_data
@@ -1572,10 +1567,9 @@ class GiftCardPayment(BasePaymentProvider):
d = refund.info_data
return d.get('gift_card_secret', d.get('gift_card_code', self.public_name))
def api_payment_details(self, payment: OrderPayment):
from .models import GiftCard
def api_payment_details(self, payment: OrderPayment | OrderRefund):
try:
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
gc = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
except GiftCard.DoesNotExist:
return {
**({'error': payment.info_data[
@@ -1698,7 +1692,19 @@ class GiftCardPayment(BasePaymentProvider):
@transaction.atomic()
def execute_refund(self, refund: OrderRefund):
from .models import GiftCard
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'))
try:
gc = self.event.organizer.accepted_gift_cards.get(
pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'),
currency=self.event.currency
)
except GiftCard.DoesNotExist:
raise PaymentException(_("Gift card not found."))
if gc.testmode != refund.order.testmode:
raise PaymentException(_("Only test gift card can be used in test mode and vice versa."))
if gc.expires and gc.expires < time_machine_now():
raise PaymentException(_("This gift card is no longer valid."))
trans = gc.transactions.create(
value=refund.amount,
order=refund.order,
+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
+1
View File
@@ -316,6 +316,7 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
body_html = None
m = OutgoingMail.objects.create(
guid=guid,
organizer=organizer,
event=event,
order=order,
+37 -18
View File
@@ -34,7 +34,9 @@ from pretix.base.models import (
from pretix.base.notifications import Notification, get_all_notification_types
from pretix.base.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import notification
from pretix.base.signals import (
event_notification_sent, organizer_notification_sent,
)
from pretix.celery_app import app
from pretix.helpers.celery import get_task_priority
from pretix.helpers.urls import mainreverse_absolute
@@ -47,40 +49,47 @@ def notify(logentry_ids: list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer'
'event', 'event__organizer', 'organizer'
).order_by(
'action_type', 'event_id',
'action_type', 'event_id', 'organizer_id',
).filter(id__in=logentry_ids)
_event, _at, notify_specific, notify_global = None, None, None, None
_event, _organizer, _at, notify_specific, notify_global = None, None, None, None, None
for logentry in qs:
if not logentry.event:
break # Ignore, we only have event-related notifications right now
if not logentry.event and not logentry.organizer:
break # Ignore, we only have event- or organizer-related notifications right now
notification_type = logentry.notification_type
if not notification_type:
break # No suitable plugin
if _event != logentry.event or _at != logentry.action_type or notify_global is None:
if _event != logentry.event or _organizer != logentry.organizer or _at != logentry.action_type or notify_global is None:
_event = logentry.event
_organizer = logentry.organizer
_at = logentry.action_type
event_or_organizer = logentry.event or logentry.organizer
# All users that have the permission to get the notification
users = logentry.event.get_users_with_permission(
users = event_or_organizer.get_users_with_permission(
notification_type.required_permission
).filter(notifications_send=True, is_active=True)
if logentry.user:
users = users.exclude(pk=logentry.user.pk)
# Get all notification settings, both specific to this event as well as global
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
if logentry.event:
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
else:
notify_specific = {}
notify_global = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
@@ -106,7 +115,10 @@ def notify(logentry_ids: list):
priority=get_task_priority("notifications", logentry.organizer_id),
)
notification.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
if logentry.event:
event_notification_sent.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
elif logentry.organizer:
organizer_notification_sent.send(logentry.organizer, logentry_id=logentry.id, notification_type=notification_type.action_type)
@app.task(base=ProfiledTask, acks_late=True, max_retries=9, default_retry_delay=900)
@@ -158,13 +170,20 @@ def send_notification_mail(notification: Notification, user: User):
body_plain = tpl_plain.render(ctx)
guid = uuid.uuid4()
settings_holder = notification.event or notification.organizer
prefix = settings_holder.settings.mail_prefix
if not prefix:
if notification.event:
prefix = notification.event.slug.upper()
elif notification.organizer:
prefix = notification.organizer.name
m = OutgoingMail.objects.create(
guid=guid,
user=user,
to=[user.email],
subject='[{}] {}: {}'.format(
settings.PRETIX_INSTANCE_NAME,
notification.event.settings.mail_prefix or notification.event.slug.upper(),
prefix,
notification.title
),
body_plain=body_plain,
+40 -12
View File
@@ -359,7 +359,8 @@ def mark_order_refunded(order, user=None, auth=None, api_token=None):
def mark_order_expired(order, user=None, auth=None):
"""
Mark this order as expired. This sets the payment status and returns the order object.
Mark this order as expired. This sets the order status, generates an invoice cancellation
and returns the order object.
:param order: The order to change
:param user: The user that performed the change
"""
@@ -969,7 +970,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
gift_cards = [] # for backwards compatibility
for p in payment_requests:
if p['provider'] == 'giftcard':
gift_cards.append(GiftCard.objects.get(pk=p['info_data']['gift_card']))
gift_cards.extend(event.organizer.accepted_gift_cards.filter(pk=p['info_data']['gift_card']))
for recv, resp in order_fee_calculation.send(sender=event, invoice_address=address, total=total, payment_requests=payment_requests,
meta_info=meta_info, positions=positions, gift_cards=gift_cards):
@@ -2175,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
@@ -2474,7 +2476,11 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.feevalue', user=self.user, auth=self.auth, data={
'fee': fee.pk,
'old_price': fee.value,
'new_price': op.value.gross
'new_price': op.value.gross,
'old_tax_rate': fee.tax_rate,
'new_tax_rate': op.value.rate,
'old_tax_value': fee.tax_value,
'new_tax_value': op.value.tax,
})
fee.value = op.value.gross
fee._calculate_tax()
@@ -2484,9 +2490,13 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={
'position': position.pk,
'positionid': position.positionid,
'old_price': position.price,
'addon_to': position.addon_to_id,
'new_price': op.price.gross
'old_price': position.price,
'new_price': op.price.gross,
'old_tax_rate': position.tax_rate,
'new_tax_rate': op.price.rate,
'old_tax_value': position.tax_value,
'new_tax_value': op.price.tax,
})
position.price = op.price.gross
position.price_includes_rounding_correction = Decimal("0.00")
@@ -2501,25 +2511,43 @@ class OrderChangeManager:
elif isinstance(op, self.TaxRuleOperation):
if isinstance(op.position, OrderPosition):
position = position_cache.setdefault(op.position.pk, op.position)
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
log = {
'position': position.pk,
'positionid': position.positionid,
'addon_to': position.addon_to_id,
'old_taxrule': position.tax_rule.pk if position.tax_rule else None,
'new_taxrule': op.tax_rule.pk
})
'new_taxrule': op.tax_rule.pk,
'old_price': position.price,
'old_tax_rate': position.tax_rate,
'old_tax_value': position.tax_value,
}
position._calculate_tax(op.tax_rule)
position.save()
log.update({
'new_price': position.price,
'new_tax_rate': position.tax_rate,
'new_tax_value': position.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op.position, OrderFee):
fee = fee_cache.setdefault(op.position.pk, op.position)
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
log = {
'fee': fee.pk,
'fee_type': fee.fee_type,
'old_taxrule': fee.tax_rule.pk if fee.tax_rule else None,
'new_taxrule': op.tax_rule.pk
})
'new_taxrule': op.tax_rule.pk,
'old_price': fee.value,
'old_tax_rate': fee.tax_rate,
'old_tax_value': fee.tax_value,
}
fee._calculate_tax(op.tax_rule)
fee.save()
log.update({
'new_price': fee.value,
'new_tax_rate': fee.tax_rate,
'new_tax_value': fee.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op, self.CancelFeeOperation):
fee = fee_cache.setdefault(op.fee.pk, op.fee)
self.order.log_action('pretix.event.order.changed.cancelfee', user=self.user, auth=self.auth, data={
+5
View File
@@ -752,6 +752,11 @@ def base_placeholders(sender, **kwargs):
lambda position_or_address: concatenation_for_salutation(get_best_name(position_or_address, parts=True)),
lambda event: concatenation_for_salutation(name_scheme['sample']),
))
ph.append(SimpleFunctionalTextPlaceholder(
"attendee_name_for_salutation", ["position"],
lambda position: concatenation_for_salutation(position.attendee_name_parts),
lambda event: concatenation_for_salutation(name_scheme['sample']),
))
for f, l, w in name_scheme['fields']:
if f == 'full_name':
+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()
+17 -6
View File
@@ -605,15 +605,14 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_notification_types = EventPluginSignal()
register_notification_types = GlobalSignal()
"""
This signal is sent out to get all known notification types. Receivers should return an
instance of a subclass of pretix.base.notifications.NotificationType or a list of such
instances.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event,
however for this signal, the ``sender`` **may also be None** to allow creating the general
notification settings!
When called for actually sending notifications, ``sender`` will be the event or organizer,
depending on context.
"""
register_event_permission_groups = GlobalSignal()
@@ -628,7 +627,7 @@ This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.PermissionGroup or a list of such instances.
"""
notification = EventPluginSignal()
event_notification_sent = EventPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``
@@ -637,6 +636,15 @@ This signal is sent out when a notification is sent.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
organizer_notification_sent = OrganizerPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``
This signal is sent out when a notification is sent.
As with all organizer-plugin signals, the ``sender`` keyword argument will contain the organizer.
"""
register_sales_channel_types = GlobalSignal()
"""
This signal is sent out to get all known sales channels types. Receivers should return an
@@ -1191,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
+1 -1
View File
@@ -44,7 +44,7 @@ def timeline_for_event(event, subevent=None):
ev = subevent or event
if subevent:
ev_edit_url = reverse(
'control:event.subevent.edit', kwargs={
'control:event.subevent', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
'subevent': subevent.pk
+3 -1
View File
@@ -172,7 +172,7 @@ class CachedFileInput(forms.ClearableFileInput):
from ...base.models import CachedFile
v = super().value_from_datadict(data, files, name)
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
v = CachedFile.objects.filter(id=data[name + '-cachedfile'], web_download=True).first()
if not v.allowed_for_session(self.request):
v = None
return v
@@ -424,6 +424,8 @@ class SplitDateTimeField(forms.SplitDateTimeField):
return None
if data_list[1] in self.empty_values:
raise ValidationError(self.error_messages['invalid_date'], code='invalid_date')
if data_list[0] < datetime.date(1900, 1, 1):
raise ValidationError(self.error_messages['invalid_date'], code='invalid_date')
result = datetime.datetime.combine(*data_list)
return from_current_timezone(result)
return None
+1 -1
View File
@@ -66,7 +66,7 @@ class ProcessForm(forms.Form):
widget=forms.Select(
attrs={'data-static': 'true'}
),
help_text=conditional_escape(c.help_text),
help_text=conditional_escape(c.help_text or ""),
)
def get_columns(self):
+22 -20
View File
@@ -38,7 +38,6 @@ from urllib.parse import urlparse
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.forms import formset_factory, inlineformset_factory
from django.forms.utils import ErrorDict
from django.urls import reverse
@@ -70,9 +69,9 @@ from pretix.base.forms.widgets import (
SplitDateTimePickerWidget, format_placeholders_help_text,
)
from pretix.base.models import (
Customer, Device, Event, EventMetaProperty, Gate, GiftCard,
GiftCardAcceptance, Membership, MembershipType, OrderPosition, Organizer,
ReusableMedium, SalesChannel, Team,
Customer, Device, Event, EventMetaProperty, Gate, GiftCard, Membership,
MembershipType, OrderPosition, Organizer, ReusableMedium, SalesChannel,
Team,
)
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
from pretix.base.models.organizer import OrganizerFooterLink, TeamQuerySet
@@ -364,7 +363,7 @@ class TeamForm(forms.ModelForm):
for opt in pg.options
],
label=pg.label,
help_text=conditional_escape(pg.help_text),
help_text=conditional_escape(pg.help_text or ""),
initial=initial,
widget=forms.RadioSelect,
)
@@ -389,7 +388,7 @@ class TeamForm(forms.ModelForm):
for opt in pg.options
],
label=pg.label,
help_text=conditional_escape(pg.help_text),
help_text=conditional_escape(pg.help_text or ""),
initial=initial,
widget=forms.RadioSelect,
)
@@ -894,15 +893,7 @@ class GiftCardCreateForm(forms.ModelForm):
def clean_secret(self):
s = self.cleaned_data['secret']
if GiftCard.objects.filter(
secret__iexact=s
).filter(
Q(issuer=self.organizer) |
Q(issuer__in=GiftCardAcceptance.objects.filter(
acceptor=self.organizer,
active=True,
).values_list('issuer', flat=True))
).exists():
if self.organizer.accepted_gift_cards.filter(secret__iexact=s).exists():
raise ValidationError(
_('A gift card with the same secret already exists in your or an affiliated organizer account.')
)
@@ -1198,7 +1189,7 @@ OrganizerFooterLinkFormset = inlineformset_factory(
class SSOProviderForm(I18nModelForm):
config_oidc_base_url = forms.URLField(
label=pgettext_lazy('sso_oidc', 'Base URL'),
label=pgettext_lazy('sso_oidc', 'Base URL / issuer'),
required=False,
)
config_oidc_client_id = forms.CharField(
@@ -1295,6 +1286,14 @@ class SSOProviderForm(I18nModelForm):
class SSOClientForm(I18nModelForm):
disp_base_url = forms.CharField(
label=_('Base URL / issuer'),
disabled=True,
)
disp_client_id = forms.CharField(
label=_('Client ID'),
disabled=True,
)
regenerate_client_secret = forms.BooleanField(
label=_('Invalidate old client secret and generate a new one'),
required=False,
@@ -1302,7 +1301,8 @@ class SSOClientForm(I18nModelForm):
class Meta:
model = CustomerSSOClient
fields = ['is_active', 'name', 'client_id', 'client_type', 'authorization_grant_type', 'redirect_uris',
fields = ['is_active', 'name', 'disp_base_url', 'disp_client_id', 'regenerate_client_secret',
'client_type', 'authorization_grant_type', 'redirect_uris',
'allowed_scopes', 'require_pkce']
widgets = {
'authorization_grant_type': forms.RadioSelect,
@@ -1317,13 +1317,15 @@ class SSOClientForm(I18nModelForm):
help_text=self.fields['allowed_scopes'].help_text,
required=self.fields['allowed_scopes'].required,
initial=self.fields['allowed_scopes'].initial,
choices=CustomerSSOClient.SCOPE_CHOICES,
choices=[(val, format_html('<code>{}</code> &ndash; {}', val, label)) for val, label in CustomerSSOClient.SCOPE_CHOICES],
widget=forms.CheckboxSelectMultiple
)
organizer = kwargs['event']
self.initial['disp_base_url'] = eventreverse_absolute(organizer, 'presale:organizer.index', {}).strip('/')
if self.instance and self.instance.pk:
self.fields['client_id'].disabled = True
self.initial['disp_client_id'] = self.instance.client_id
else:
del self.fields['client_id']
self.initial['disp_client_id'] = _('(will be generated)')
del self.fields['regenerate_client_secret']
+21 -1
View File
@@ -153,7 +153,7 @@ class SubEventBulkEditForm(I18nModelForm):
self.fields[k].widget.is_required = False
self.fields[k].required = False
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end'):
for k in ('date_from', 'date_to'):
self.fields[k + '_day'] = forms.DateField(
label=self._meta.model._meta.get_field(k).verbose_name,
help_text=self._meta.model._meta.get_field(k).help_text,
@@ -167,6 +167,14 @@ class SubEventBulkEditForm(I18nModelForm):
required=False,
)
for k in ('date_admission', 'presale_start', 'presale_end'):
self.fields[k + '_rel'] = RelativeDateTimeField(
label=self._meta.model._meta.get_field(k).verbose_name,
help_text=self._meta.model._meta.get_field(k).help_text,
required=False,
limit_choices=('event__date_from',),
)
class Meta:
model = SubEvent
localized_fields = '__all__'
@@ -247,6 +255,18 @@ class SubEventBulkEditForm(I18nModelForm):
)
setattr(obj, k.replace('_time', ''), newval)
fields.add(k.replace('_time', ''))
elif k.endswith('_rel'):
target_key = k[:-4]
for obj in objs:
cval = self.cleaned_data[k]
if cval is None:
newval = None
if not self._meta.model._meta.get_field(target_key).null:
continue
else:
newval = cval.datetime(obj)
setattr(obj, target_key, newval)
fields.add(target_key)
else:
fields.add(k)
for obj in objs:
+11 -3
View File
@@ -133,14 +133,22 @@ This signal is sent out to include custom HTML in the top part of the the event
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
event_dashboard_statistics = EventPluginSignal()
event_dashboard_widgets = EventPluginSignal()
"""
This signal is sent out to include statistical content on the event dashboard.
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
This signal is sent out to include widgets in the event dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys:
* content (SafeString, containing HTML)
* display_size (str, one of "full" (whole row), "big" (half a row) or "small"
(quarter of a row). May be ignored on small displays, default is "small")
* priority (int, used for ordering, higher comes first, default is 1)
* url (str, optional, if the full widget should be a link)
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
user_dashboard_widgets = GlobalSignal()
@@ -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 %}
@@ -23,7 +24,6 @@
{% endif %}
{% compress js %}
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
<script type="text/javascript" src="{% static "htmx/htmx-2.0.10.min.js" %}"></script>
<script type="text/javascript" src="{% static "js/jquery.formset.js" %}"></script>
<script type="text/javascript" src="{% static "typeahead/typeahead.bundle.js" %}"></script>
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.js" %}"></script>
@@ -88,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 }}"
@@ -1,23 +0,0 @@
{% load i18n %}
{% load icon %}
{% load humanize %}
{% for cl in lists %}
<a class="quotabox quotabox-full availability"
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">
<strong>{{ cl.name }}</strong>
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
</div>
</div>
<div class="numbers">
{% icon "sign-in" %}
{{ cl.checkin_count|default_if_none:0|intcomma }} /
{{ cl.position_count|default_if_none:0|intcomma }}
<br>
{% icon "user" %}
{% blocktrans trimmed with n=cl.inside_count|intcomma %}
{{ n }} present
{% endblocktrans %}
</div>
</a>
{% endfor %}
@@ -1,17 +0,0 @@
{% load i18n %}
{% load icon %}
<div id="comment-form">
<strong>{% trans "Comment" %}:</strong>
{% if "event.settings.general:write" in request.eventpermset %}
<button type="button" class="btn btn-default btn-xs"
hx-get="{% url "control:event.index.comment" organizer=request.organizer.slug event=request.event.slug %}"
hx-target="#comment-form">
{% icon "edit" %}
</button>
{% endif %}
{% if request.event.comment %}
<p>
{{ request.event.comment|linebreaksbr }}
</p>
{% endif %}
</div>
@@ -1,16 +0,0 @@
{% load i18n %}
{% load bootstrap3 %}
<form class="form" method="post"
hx-post="{% url "control:event.index.comment" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %}
<div>
<p>
{% bootstrap_field form.comment layout="inline" show_help=True show_label=False horizontal_field_class="col-md-12" %}
</p>
<p class="text-right flip">
<button class="btn btn-default">
{% trans "Update comment" %}
</button>
</p>
</div>
</form>
@@ -1,4 +0,0 @@
{% load i18n %}
{% for q in quotas %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q full=1 %}
{% endfor %}
@@ -1,11 +0,0 @@
{% load i18n %}
{% load humanize %}
{% if count %}
<a href="{% url "control:event.orders.waitinglist" event=request.event.slug organizer=request.organizer.slug %}">
{% blocktrans trimmed with number=count|intcomma count count=count %}
{{ number }} person waiting
{% plural %}
{{ number }} persons waiting
{% endblocktrans %}
</a>
{% endif %}
@@ -1,5 +1,4 @@
{% load i18n %}
{% load eventsignal %}
{% if has_overpaid_orders %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
@@ -57,4 +56,3 @@
class="btn btn-primary">{% trans "Show sync problems" %}</a>
</div>
{% endif %}
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
@@ -1,19 +0,0 @@
{% load i18n %}
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Welcome to pretix!" %}</h2>
</div>
<div class="panel-body">
<div class="attentionline">{% trans "Get started with our setup tool" %}</div>
<p>
{% blocktrans trimmed %}
To start selling tickets, you need to create products or quotas. The fastest way to create
this is to use our setup tool.
{% endblocktrans %}
</p>
<a href="{% url "control:event.quick" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg">
{% trans "Set up event" %}
</a>
</div>
</div>
@@ -0,0 +1,12 @@
<div class="welcome-wizard">
<h3>{{ title }}</h3>
{% if subtitle %}
<div class="attentionline">{{ subtitle }}</div>
{% endif %}
{% if text %}
<p>{{ text }}</p>
{% endif %}
{% if button_text %}
<p><a href="{{ button_url }}" class="btn btn-primary btn-lg">{{ button_text }}</a></p>
{% endif %}
</div>
@@ -1,10 +1,10 @@
{% load i18n %}
<details class="panel panel-default items" open>
<summary class="panel-heading">
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Timeline" %}
{% trans "Your timeline" %}
</h3>
</summary>
</div>
<div class="panel-body timeline">
{% regroup timeline by date as tl_list %}
{% for day in tl_list %}
@@ -33,4 +33,4 @@
</div>
{% endfor %}
</div>
</details>
</div>
@@ -3,10 +3,10 @@
{% load eventurl %}
{% load bootstrap3 %}
{% load static %}
{% load icon %}
{% load eventsignal %}
{% block title %}{{ request.event.name }}{% endblock %}
{% block content %}
<h1 class="event-dashboard-header">
<h1>
{{ request.event.name }}
<small>
{% if request.event.has_subevents %}
@@ -14,43 +14,11 @@
{% else %}
{{ request.event.get_date_range_display }}
{% endif %}
<span id="warnings_indicator" class="htmx-indicator">{% icon "cog fa-spin" %}</span>
<span id="warnings_loading" class="fa fa-cog fa-spin"></span>
</small>
<div class="pull-right flip">
<a href="{% url "control:event.live" organizer=request.organizer.slug event=request.event.slug %}"
data-toggle="tooltip"
title="{% trans "Click to change shop status" %}">
{% icon "pencil" %}</a>
<a href="{% url "control:event.live" organizer=request.organizer.slug event=request.event.slug %}"
data-toggle="tooltip"
title="{% trans "Click to change shop status" %}">
{% if request.event.live and request.event.testmode %}
<span class="label label-warning">
<span class="fa fa-warning"></span>
{% trans "public test mode" %}
</span>
{% elif request.event.live %}
<span class="label label-success">
<span class="fa fa-check"></span>
{% trans "live" %}
</span>
{% elif request.event.testmode %}
<span class="label label-warning">
<span class="fa fa-power-off"></span>
{% trans "private test mode" %}
</span>
{% else %}
<span class="label label-danger">
<span class="fa fa-power-off"></span>
{% trans "offline" %}
</span>
{% endif %}
</a>
</div>
<div class="clearfix"></div>
</h1>
<div class="helper-space-below">
<strong>{% trans "Shop URL:" %}</strong>
{% trans "Shop URL:" %}
<span id="shop_url" class="text-muted">{% abseventurl request.event "presale:event.index" %}</span>
<button type="button" class="btn btn-default btn-xs btn-clipboard js-only" data-clipboard-target="#shop_url">
<i class="fa fa-clipboard" aria-hidden="true"></i>
@@ -63,80 +31,75 @@
{% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=0 %}
</div>
<div class="clearfix"></div>
{% include "pretixcontrol/event/dashboard_partial_comment.html" with url=0 %}
</div>
<div id="warnings_target"></div>
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
{% endif %}
{% if not request.event.has_subevents or subevent %}
{% include "pretixcontrol/event/fragment_timeline.html" %}
{% endif %}
<div class="dashboard">
{% for w in widgets %}
<div class="widget-container widget-{{ w.display_size|default:"small" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
{% if w.url %}{# backwards compatibility #}
<a href="{{ w.url }}" class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x"></span>
{% else %}
{{ w.content }}
{% endif %}
</a>
{% elif w.link %}
<a href="{{ w.link }}" class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x´"></span>
{% else %}
{{ w.content }}
{% endif %}
</a>
{% else %}
<div class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x"></span>
{% else %}
{{ w.content }}
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% if not request.event.items.exists %}
{% include "pretixcontrol/event/dashboard_partial_welcome.html" %}
{% endif %}
<div hx-get="{% url "control:event.index.warnings" organizer=request.organizer.slug event=request.event.slug %}"
{# loading indicator is somewhere else because the happy case is "no warnings" and shouldn't make the page jump #}
hx-indicator="#warnings_indicator"
hx-trigger="load"></div>
{% if stats %}
{{ stats }}
{% endif %}
<p>&nbsp;</p>
{% if not request.event.items.exists %}
{# pass #}
{% elif not request.event.has_subevents %}
{% include "pretixcontrol/event/fragment_timeline.html" %}
<div class="row">
<div class="col-md-6 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<div hx-get="{% url "control:event.index.waiting" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
<span class="loading-mock loading-mock-text-short"></span>
</div>
</div>
<h2 class="panel-title">{% trans "Quotas" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.quotas" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
</div>
</div>
</div>
</div>
<div class="col-md-6 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Check-in lists" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.checkin" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
</div>
</div>
</div>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Internal comment" %}
</h3>
</div>
{% elif has_checkin_widgets %}
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Check-in lists" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.checkin" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
<div class="panel-body">
<form class="form" method="post"
action="{% url "control:event.comment" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %}
<div class="row">
{% bootstrap_field comment_form.comment layout="horizontal" show_help=True show_label=False horizontal_field_class="col-md-12" %}
</div>
</div>
{% if not comment_form.readonly %}
<p class="text-right flip">
<br>
<button class="btn btn-default">
{% trans "Update comment" %}
</button>
</p>
{% endif %}
</form>
</div>
{% endif %}
</div>
{% if "event.orders:read" in request.eventpermset or "event.orders:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset or "event.items:write" in request.eventpermset %}
<div class="panel panel-default">
<div class="panel-heading">
@@ -144,27 +107,10 @@
{% trans "Event logs" %}
</h3>
</div>
<ul class="list-group"
hx-get="{% url "control:event.index.logs" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12345" %}
<li class="list-group-item logentry">
<div class="row">
<div class="col-lg-2 col-sm-6 col-xs-12">
<span class="loading-mock loading-mock-text-medium"></span>
</div>
<div class="col-lg-2 col-sm-6 col-xs-12">
<span class="loading-mock loading-mock-text-short"></span>
</div>
<div class="col-lg-2 col-sm-12 col-xs-12">
<span class="loading-mock loading-mock-text-medium"></span>
</div>
<div class="col-lg-6 col-sm-12 col-xs-12">
<span class="loading-mock loading-mock-text-long"></span>
</div>
</div>
</li>
{% endfor %}
<ul class="list-group" id="logs_target">
<div class="logs-lazy-loading">
<span class="fa fa-cog fa-4x"></span>
</div>
</ul>
<div class="panel-footer">
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
@@ -86,7 +86,7 @@
<a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Quota utilization" %}
{% trans "Paid tickets per quota" %}
</th>
<th>
{% trans "Status" %}
@@ -151,7 +151,7 @@
</td>
<td>
{% for q in e.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% include "pretixcontrol/fragment_quota_box_paid.html" with quota=q %}
{% endfor %}
{% if e.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=e.organizer.slug event=e.slug %}"
@@ -1,26 +1,18 @@
{% load i18n %}
{% load humanize %}
<a class="quotabox {% if full %}quotabox-full{% endif %}" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
href="{% url "control:event.items.quotas.show" event=q.event.slug organizer=q.event.organizer.slug quota=q.pk %}">
{% if full %}
<strong>{{ q.name }}</strong>
{% endif %}
<div class="quotabox availability" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-100">
</div>
</div>
{% else %}
<div class="progress">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-{{ q.percent_paid }}">
</div>
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-unconfirmed progress-bar-{{ q.percent_other }}">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 <= 10 or q.cached_avail.0 >= 100 %}danger{% else %}warning{% endif %} progress-bar-{{ q.inv_percent }}">
</div>
</div>
{% endif %}
<div class="numbers">
{{ q.used|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}<br>
{% blocktrans trimmed with n=q.cached_availability_paid_orders %}
{{ n }} paid
{% endblocktrans %}
{{ q.cached_avail.1|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}
</div>
</a>
</div>
@@ -1,10 +0,0 @@
<a class="quotabox quotabox-full">
<strong>
<span class="loading-mock loading-mock-text-short"></span>
</strong>
<div class="progress loading-mock"></div>
<div class="numbers">
<span class="loading-mock loading-mock-text-short"></span>
<br><span class="loading-mock loading-mock-text-short"></span>
</div>
</a>
@@ -0,0 +1,17 @@
{% load i18n %}
<a class="quotabox" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
href="{% url "control:event.items.quotas.show" event=q.event.slug organizer=q.event.organizer.slug quota=q.pk %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
</div>
{% else %}
<div class="progress">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-{{ q.percent_paid }}">
</div>
</div>
{% endif %}
<div class="numbers">
{{ q.cached_availability_paid_orders|default_if_none:"?" }} / {{ q.size|default_if_none:"∞" }}
</div>
</a>
@@ -1,22 +1,24 @@
{% load i18n %}
<dl class="dl-horizontal">
<dt>{% trans "Gift card code" %}</dt>
<dd>
{% if gc %}
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
{{ gc.secret }}
</a>
{% if gc.issuer.slug != request.organizer %}
<span class="text-muted">
<br>
<span class="fa fa-group"></span> {{ gc.issuer }}
</span>
{% if gc or gift_card_secret %}
<dt>{% trans "Gift card code" %}</dt>
<dd>
{% if gc %}
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
{{ gc.secret }}
</a>
{% if gc.issuer.slug != request.organizer %}
<span class="text-muted">
<br>
<span class="fa fa-group"></span> {{ gc.issuer }}
</span>
{% endif %}
{% elif gift_card_secret %}
{{ gift_card_secret }}
{% endif %}
{% elif gift_card_secret %}
{{ gift_card_secret }}
{% endif %}
</dd>
</dd>
{% endif %}
{% if gc %}
<dt>{% trans "Issuer" %}</dt>
<dd>{{ gc.issuer }}</dd>
@@ -25,4 +27,4 @@
<dt>{% trans "Error" %}</dt>
<dd>{{ error }}</dd>
{% endif %}
</dl>
</dl>
@@ -1,12 +1,29 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}
{% if client %}
{% trans "SSO client:" %} {{ client.name }}
{% else %}
{% trans "Create credentials for a new SSO client" %}
{% endif %}
{% endblock %}
{% block inner %}
{% if client %}
<h1>{% trans "SSO client:" %} {{ client.name }}</h1>
{% else %}
<h1>{% trans "Create a new SSO client" %}</h1>
<h1>{% trans "Create credentials for a new SSO client" %}</h1>
<p>
{% blocktrans trimmed %}
After submitting this form, client credentials for your SSO client will be generated.
{% endblocktrans %}
</p>
{% endif %}
<p>
{% blocktrans trimmed with url="https://docs.pretix.eu/guides/customer-accounts/#using-pretix-as-an-sso-provider" %}
For more information, refer to our documentation on <a href="{{ url }}">Using pretix as an SSO provider with external SSO clients</a>.
{% endblocktrans %}
</p>
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_form form layout="control" %}
@@ -3,16 +3,23 @@
{% load bootstrap3 %}
{% block title %}{% trans "SSO clients" %}{% endblock %}
{% block inner %}
<h1>{% trans "SSO clients" %}</h1>
<h1>{% trans "SSO clients" %} <small> &mdash; {% blocktrans %}
"Login with pretix" in external systems
{% endblocktrans %}</small></h1>
<p>
{% blocktrans trimmed %}
You can allow your customers to log into other systems using their customer account credentials by setting up
your other systems as a Single-Sign-On (SSO) client based on OpenID Connect.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed with url="https://docs.pretix.eu/guides/customer-accounts/#using-pretix-as-an-sso-provider" %}
For more information, refer to our documentation on <a href="{{ url }}">Using pretix as an SSO provider with external SSO clients</a>.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.ssoclient.add" organizer=request.organizer.slug %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new SSO client" %}
{% trans "Create credentials for a new SSO client" %}
</a>
<table class="table table-condensed table-hover">
<thead>
@@ -1,12 +1,24 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}
{% if provider %}
{% trans "External SSO provider:" %} {{ provider.name }}
{% else %}
{% trans "Connect to an external SSO provider" %}
{% endif %}
{% endblock %}
{% block inner %}
{% if provider %}
<h1>{% trans "SSO provider:" %} {{ provider.name }}</h1>
<h1>{% trans "External SSO provider:" %} {{ provider.name }}</h1>
{% else %}
<h1>{% trans "Create a new SSO provider" %}</h1>
<h1>{% trans "Connect to an external SSO provider" %}</h1>
{% endif %}
<p>
{% blocktrans trimmed with url="https://docs.pretix.eu/guides/customer-accounts/#using-pretix-as-an-sso-client" %}
For more information, refer to our documentation on <a href="{{ url }}" target="_blank">Using pretix as an SSO client</a>.
{% endblocktrans %}
</p>
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_form form layout="control" %}
@@ -3,16 +3,23 @@
{% load bootstrap3 %}
{% block title %}{% trans "SSO providers" %}{% endblock %}
{% block inner %}
<h1>{% trans "SSO providers" %}</h1>
<h1>{% trans "SSO providers" %} <small> &mdash; {% blocktrans %}
Connect to an external login provider
{% endblocktrans %}</small></h1>
<p>
{% blocktrans trimmed %}
You can connect existing Single-Sign-On (SSO) providers to allow your customers to log in using your own
account system.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed with url="https://docs.pretix.eu/guides/customer-accounts/#using-pretix-as-an-sso-client" %}
For more information, refer to our documentation on <a href="{{ url }}" target="_blank">Using pretix as an SSO client</a>.
{% endblocktrans %}
</p>
<a href="{% url "control:organizer.ssoprovider.add" organizer=request.organizer.slug %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new SSO provider" %}
{% trans "Connect to an external SSO provider" %}
</a>
<table class="table table-condensed table-hover">
<thead>
@@ -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">
@@ -116,39 +116,9 @@
{% bootstrap_field form.date_to_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="{{ form.date_admission_day.id_for_label }}">
{{ form.date_admission_day.label }}
</label>
<div class="col-md-5">
{% bootstrap_field form.date_admission_day layout="bulkedit_inline" form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.date_admission_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="{{ form.presale_start_day.id_for_label }}">
{{ form.presale_start_day.label }}
</label>
<div class="col-md-5">
{% bootstrap_field form.presale_start_day layout="bulkedit_inline" form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.presale_start_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="{{ form.presale_end_day.id_for_label }}">
{{ form.presale_end_day.label }}
</label>
<div class="col-md-5">
{% bootstrap_field form.presale_end_day layout="bulkedit_inline" form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.presale_end_time layout="bulkedit_inline" form_group_class="" %}
</div>
</div>
{% bootstrap_field form.date_admission_rel layout="bulkedit" %}
{% bootstrap_field form.presale_start_rel layout="bulkedit" %}
{% bootstrap_field form.presale_end_rel layout="bulkedit" %}
</fieldset>
<fieldset>
<legend>{% trans "Item prices" %}</legend>
@@ -72,7 +72,6 @@
{% endif %}
</dl>
</fieldset>
{% include "pretixcontrol/event/fragment_timeline.html" %}
<fieldset>
<legend>{% trans "Quotas" %}</legend>
<div class="table-responsive">
@@ -102,7 +102,7 @@
<a href="?{% url_replace request 'filter-ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Quota utilization" %}
{% trans "Paid tickets per quota" %}
</th>
<th>
{% trans "Status" %}
@@ -155,7 +155,7 @@
</td>
<td>
{% for q in s.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% include "pretixcontrol/fragment_quota_box_paid.html" with quota=q %}
{% endfor %}
{% if s.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}"
+3 -4
View File
@@ -273,16 +273,15 @@ urlpatterns = [
re_path(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
re_path(r'^$', dashboards.event_index, name='event.index'),
re_path(r'^qrcode.(?P<filetype>(png|jpeg|gif|svg))$', event.EventQRCode.as_view(), name='event.qrcode'),
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
re_path(r'^dashboard/partials/logs$', dashboards.event_index_log_lazy, name='event.index.logs'),
re_path(r'^dashboard/partials/warnings$', dashboards.event_index_warnings_lazy, name='event.index.warnings'),
re_path(r'^dashboard/partials/quotas$', dashboards.event_index_quotas_lazy, name='event.index.quotas'),
re_path(r'^dashboard/partials/waiting$', dashboards.event_index_waiting_lazy, name='event.index.waiting'),
re_path(r'^dashboard/partials/checkin$', dashboards.event_index_checkin_lazy, name='event.index.checkin'),
re_path(r'^dashboard/partials/comment$', event.EventComment.as_view(), name='event.index.comment'),
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
re_path(r'^transfer_session/$', event.EventTransferSession.as_view(), name='event.transfer_session'),
re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'),
re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
re_path(r'^comment/$', event.EventComment.as_view(),
name='event.comment'),
re_path(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'),
re_path(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
re_path(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
+333 -63
View File
@@ -33,48 +33,317 @@
# License for the specific language governing permissions and limitations under the License.
from datetime import timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import intcomma
from django.db.models import (
Count, IntegerField, Max, Min, OuterRef, Q, Subquery,
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum,
)
from django.db.models.functions import Coalesce, Greatest
from django.http import Http404, JsonResponse
from django.dispatch import receiver
from django.http import JsonResponse
from django.shortcuts import render
from django.template.loader import get_template
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.html import (
conditional_escape, escape, format_html, format_html_join,
)
from django.utils.html import conditional_escape, escape, format_html
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, ngettext, pgettext
from pretix.base.decimal import round_decimal
from pretix.base.models import (
Item, ItemCategory, Order, OrderRefund, Question, Quota, Voucher,
WaitingListEntry,
Item, ItemCategory, ItemVariation, Order, OrderPosition, OrderRefund,
Question, Quota, SubEvent, Voucher, WaitingListEntry,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timeline import timeline_for_event
from pretix.control.signals import (
event_dashboard_statistics, user_dashboard_widgets,
event_dashboard_widgets, user_dashboard_widgets,
)
from pretix.helpers.daterange import daterange
from ...base.models.orders import CancellationRequest
from ...base.models.organizer import TeamQuerySet
from ...base.templatetags.money import money_filter
from ..logdisplay import OVERVIEW_BANLIST
from .utils import prepare_quotas_for_boxes
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
def event_index_waiting_lazy(request, organizer, event):
wles = WaitingListEntry.objects.filter(event=request.event, voucher__isnull=True)
return render(
request,
'pretixcontrol/event/dashboard_partial_waiting.html',
@receiver(signal=event_dashboard_widgets)
def base_widgets(sender, subevent=None, lazy=False, **kwargs):
if not lazy:
prodc = Item.objects.filter(
event=sender, active=True,
).filter(
(Q(available_until__isnull=True) | Q(available_until__gte=now())) &
(Q(available_from__isnull=True) | Q(available_from__lte=now()))
).count()
if subevent:
opqs = OrderPosition.objects.filter(subevent=subevent)
else:
opqs = OrderPosition.objects
tickc = opqs.filter(
order__event=sender, item__admission=True,
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
).count()
paidc = opqs.filter(
order__event=sender, item__admission=True,
order__status=Order.STATUS_PAID,
).count()
if subevent:
rev = opqs.filter(
order__event=sender, order__status=Order.STATUS_PAID
).aggregate(
sum=Sum('price')
)['sum'] or Decimal('0.00')
else:
rev = Order.objects.filter(
event=sender,
status=Order.STATUS_PAID
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
return [
{
'count': wles.count,
}
)
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(tickc), text=_('Attendees (ordered)')),
'lazy': 'attendees-ordered',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(paidc), text=_('Attendees (paid)')),
'lazy': 'attendees-paid',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders.overview', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(
NUM_WIDGET,
num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
text=_('Total revenue ({currency})').format(currency=sender.currency)
),
'lazy': 'total-revenue',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders.overview', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(NUM_WIDGET, num=prodc, text=_('Active products')),
'lazy': 'active-products',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.items', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
})
},
]
@receiver(signal=event_dashboard_widgets)
def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets = []
wles = WaitingListEntry.objects.filter(event=sender, subevent=subevent, voucher__isnull=True)
if wles.exists():
if not lazy:
quota_cache = {}
happy = 0
tuples = wles.values('item', 'variation').order_by().annotate(cnt=Count('id'))
items = {
i.pk: i for i in sender.items.filter(id__in=[t['item'] for t in tuples]).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)
}
vars = {
i.pk: i for i in ItemVariation.objects.filter(
item__event=sender, id__in=[t['variation'] for t in tuples if t['variation']]
).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)
}
for wlt in tuples:
item = items.get(wlt['item'])
variation = vars.get(wlt['variation'])
if not item:
continue
quotas = (
variation._get_quotas(subevent=subevent)
if variation
else item._get_quotas(subevent=subevent)
)
row = (
variation.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
if variation
else item.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
)
if row[1] is None:
happy += wlt['cnt']
elif row[1] > 0:
happy += min(wlt['cnt'], row[1])
for q in quotas:
if q.size is not None:
quota_cache[q.pk] = (quota_cache[q.pk][0], quota_cache[q.pk][1] - min(wlt['cnt'], row[1]))
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET, num=intcomma(happy), text=_('available to give to people on waiting list')
),
'lazy': 'waitinglist-avail',
'priority': 50,
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
})
})
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET, num=intcomma(wles.count()), text=_('total waiting list length')
),
'lazy': 'waitinglist-length',
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets = []
quotas = sender.quotas.filter(subevent=subevent)
qa = QuotaAvailability()
if quotas:
qa.queue(*quotas)
qa.compute(allow_cache=True)
for q in quotas:
if not lazy:
status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET,
num='{}/{}'.format(intcomma(left), intcomma(q.size)) if q.size is not None else '\u221e',
text=format_html(_('{quota} left'), quota=q.name)
),
'lazy': 'quota-{}'.format(q.pk),
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.items.quotas.show', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
'quota': q.id
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def shop_state_widget(sender, **kwargs):
return [{
'display_size': 'small',
'priority': 1000,
'content': format_html(
'<div class="shopstate">{t1}<br><span class="{cls}"><span class="fa {icon}"></span> {state}</span>{t2}</div>',
t1=_('Your ticket shop is'), t2=_('Click here to change'),
state=_('live') if sender.live and not sender.testmode else (
_('live and in test mode') if sender.live else (
_('not yet public') if not sender.testmode else (
_('in private test mode')
)
)
),
icon='fa-check-circle' if sender.live and not sender.testmode else (
'fa-warning' if sender.live else (
'fa-times-circle' if not sender.testmode else (
'fa-times-circle'
)
)
),
cls='live' if sender.live else 'off'
),
'url': reverse('control:event.live', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
})
}]
@receiver(signal=event_dashboard_widgets)
def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
widgets = []
qs = sender.checkin_lists.filter(subevent=subevent)
for cl in qs:
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET,
num='{}/{}'.format(intcomma(cl.inside_count), intcomma(cl.position_count)),
text=format_html(_('Present – {list}'), list=cl.name)
),
'lazy': 'checkin-{}'.format(cl.pk),
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.orders.checkinlists.show', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
'list': cl.pk
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def welcome_wizard_widget(sender, **kwargs):
template = get_template('pretixcontrol/event/dashboard_widget_welcome.html')
ctx = {
'title': _('Welcome to pretix!')
}
kwargs = {'event': sender.slug, 'organizer': sender.organizer.slug}
if not sender.items.exists():
ctx.update({
'subtitle': _('Get started with our setup tool'),
'text': _('To start selling tickets, you need to create products or quotas. The fastest way to create '
'this is to use our setup tool.'),
'button_text': _('Set up event'),
'button_url': reverse('control:event.quick', kwargs=kwargs)
})
else:
return []
return [{
'display_size': 'full',
'priority': 2000,
'content': template.render(ctx)
}]
def build_json_response(widgets):
@@ -84,38 +353,65 @@ def build_json_response(widgets):
def event_index(request, organizer, event):
can_view_orders = request.user.has_event_permission(
request.organizer,
request.event,
'event.orders:read',
request=request
)
from pretix.control.forms.event import CommentForm
stats = []
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
can_change_event_settings = request.user.has_event_permission(request.organizer, request.event,
'event.settings.general:write', request=request)
widgets = []
if can_view_orders:
for r, result in event_dashboard_statistics.send(sender=request.event, request=request):
stats.append(result)
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True):
widgets.extend(result)
ctx = {
'stats': format_html_join("", "{}", [(s,) for s in stats]),
'widgets': rearrange(widgets),
'subevent': subevent,
'comment_form': CommentForm(initial={'comment': request.event.comment}, readonly=not can_change_event_settings),
}
if not request.event.has_subevents:
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, None)
]
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, subevent)
]
ctx['today'] = now().astimezone(request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(request.event.timezone) - timedelta(seconds=20)
ctx['has_checkin_widgets'] = not request.event.has_subevents or request.event.checkin_lists.filter(subevent=None).exists()
resp = render(request, 'pretixcontrol/event/index.html', ctx)
return resp
def event_index_widgets_lazy(request, organizer, event):
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
widgets = []
if can_view_orders:
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
return build_json_response(widgets)
def event_index_warnings_lazy(request, organizer, event):
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
@@ -152,32 +448,6 @@ def event_index_warnings_lazy(request, organizer, event):
)
def event_index_quotas_lazy(request, organizer, event):
if request.event.has_subevents:
raise Http404()
quotas = request.event.quotas.filter(subevent=None)[:10]
prepare_quotas_for_boxes(quotas)
return render(
request,
'pretixcontrol/event/dashboard_partial_quotas.html',
{
'quotas': quotas,
}
)
def event_index_checkin_lazy(request, organizer, event):
lists = request.event.checkin_lists.filter(subevent=None)[:10]
return render(
request,
'pretixcontrol/event/dashboard_partial_checkin.html',
{
'lists': lists,
}
)
def event_index_log_lazy(request, organizer, event):
qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application',
'device').order_by('-datetime')
+18 -16
View File
@@ -57,9 +57,10 @@ from django.db import transaction
from django.db.models import Count, ProtectedError
from django.forms import inlineformset_factory
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
JsonResponse,
)
from django.shortcuts import redirect, render
from django.shortcuts import redirect
from django.urls import NoReverseMatch, reverse
from django.utils.functional import cached_property
from django.utils.html import conditional_escape, format_html
@@ -1298,23 +1299,24 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
return LogFilterForm(data=self.request.GET, organizer=self.request.organizer)
class EventComment(EventPermissionRequiredMixin, UpdateView):
class EventComment(EventPermissionRequiredMixin, View):
permission = 'event.settings.general:write'
form_class = CommentForm
template_name = 'pretixcontrol/event/dashboard_partial_comment_form.html'
def get_object(self, queryset=None):
return self.request.event
def post(self, *args, **kwargs):
form = CommentForm(self.request.POST)
if form.is_valid():
self.request.event.comment = form.cleaned_data.get('comment')
self.request.event.save()
self.request.event.log_action('pretix.event.comment', user=self.request.user, data={
'new_comment': form.cleaned_data.get('comment')
})
messages.success(self.request, _('The comment has been updated.'))
else:
messages.error(self.request, _('Could not update the comment.'))
return redirect(self.get_success_url())
def form_valid(self, form):
form.save()
self.request.event.log_action('pretix.event.comment', user=self.request.user, data={
'new_comment': form.cleaned_data.get('comment')
})
return render(
self.request,
'pretixcontrol/event/dashboard_partial_comment.html',
)
def get(self, *args, **kwargs):
return HttpResponseNotAllowed(['POST'])
def get_success_url(self) -> str:
return reverse('control:event.index', kwargs={
+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
+14 -2
View File
@@ -52,13 +52,13 @@ from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language
from pretix.base.models import Event, EventMetaValue, Organizer, Quota, Team
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.services.quotas import QuotaAvailability
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
)
from pretix.control.forms.filter import EventFilterForm
from pretix.control.permissions import OrganizerPermissionRequiredMixin
from pretix.control.views import PaginationMixin
from pretix.control.views.utils import prepare_quotas_for_boxes
class EventList(PaginationMixin, ListView):
@@ -117,7 +117,19 @@ class EventList(PaginationMixin, ListView):
s.first_quotas = s.first_quotas[:4]
quotas += list(s.first_quotas)
prepare_quotas_for_boxes(quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
return ctx
@cached_property
+15 -6
View File
@@ -2817,10 +2817,15 @@ class SSOProviderCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
return get_object_or_404(CustomerSSOProvider, organizer=self.request.organizer, pk=self.kwargs.get('provider'))
def get_success_url(self):
return reverse('control:organizer.ssoproviders', kwargs={
'organizer': self.request.organizer.slug,
return reverse('control:organizer.ssoprovider.edit', kwargs={
'organizer': self.request.organizer.slug, 'provider': self.object.pk,
})
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['redirect_uri'] = _('(will be generated)')
return ctx
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.organizer
@@ -2950,8 +2955,11 @@ class SSOClientCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
secret = form.instance.set_client_secret()
messages.success(
self.request,
_('The SSO client has been created. Please note down the following client secret, it will never be shown '
'again: {secret}').format(secret=secret)
_('The SSO client has been created.') + ' ' +
_('Please note down the following client secret, it will never be shown again.') + '\n\n' +
_('Base URL / issuer') + f': {form.initial["disp_base_url"]}\n\n' +
_('Client ID') + f': {form.instance.client_id}\n\n' +
_('Client secret') + f': {secret}'
)
form.instance.organizer = self.request.organizer
ret = super().form_valid(form)
@@ -3000,8 +3008,9 @@ class SSOClientUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
secret = form.instance.set_client_secret()
messages.success(
self.request,
_('Your changes have been saved. Please note down the following client secret, it will never be shown '
'again: {secret}').format(secret=secret)
_('Your changes have been saved.') + ' ' +
_('Please note down the following client secret, it will never be shown again.') + '\n\n' +
_('Client secret') + f': {secret}'
)
else:
messages.success(
+5 -1
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):
@@ -250,6 +251,8 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
if request.POST.get("background", "").strip():
try:
cf = CachedFile.objects.get(id=request.POST.get("background"), web_download=True)
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
cf = None
except CachedFile.DoesNotExist:
pass
@@ -290,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
@@ -305,7 +309,7 @@ class FontsCSSView(TemplateView):
class PdfView(TemplateView):
def get(self, request, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf", web_download=True)
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
raise PermissionDenied()
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
+75 -36
View File
@@ -69,7 +69,6 @@ from pretix.base.models.orders import CancellationRequest
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services import tickets
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timeline import timeline_for_event
from pretix.base.views.tasks import AsyncFormView
from pretix.control.forms.checkin import SimpleCheckinListForm
from pretix.control.forms.filter import SubEventFilterForm
@@ -84,9 +83,9 @@ from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import subevent_forms
from pretix.control.views import PaginationMixin
from pretix.control.views.event import MetaDataEditorMixin
from pretix.control.views.utils import prepare_quotas_for_boxes
from pretix.helpers import GroupConcat
from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.database import UnixTimestamp
from pretix.helpers.i18n import get_format_without_seconds
from pretix.helpers.models import modelcopy
@@ -146,7 +145,19 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
s.first_quotas = s.first_quotas[:4]
quotas += list(s.first_quotas)
prepare_quotas_for_boxes(quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
return ctx
@@ -556,17 +567,6 @@ class SubEventDetail(EventPermissionRequiredMixin, DetailView):
for quota in ctx["quotas"]:
quota.cached_avail = qa.results[quota]
ctx['timeline'] = [
{
'date': t.datetime.astimezone(self.request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(self.request.event.timezone)
}
for t in timeline_for_event(self.request.event, self.object)
]
ctx['today'] = now().astimezone(self.request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(self.request.event.timezone) - timedelta(seconds=20)
return super().get_context_data(
**kwargs,
**ctx,
@@ -1584,21 +1584,6 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
mixed_values = set()
qs = self.get_queryset()
qs = qs.annotate(
**{
# TODO: Once we're on Django 3.2, pass a tzinfo parameter
# Before Django 3.2, it uses the current timezone, which is hopefully fine
# as well in all cases we are concerned about
# See also: https://code.djangoproject.com/ticket/31948
k + '_day': TruncDate(k)
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
},
**{
k + '_time': TruncTime(k)
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
},
)
fields = {
'name',
'location',
@@ -1612,21 +1597,75 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
'date_from_time',
'date_to_day',
'date_to_time',
'date_admission_day',
'date_admission_time',
'presale_start_day',
'presale_start_time',
'presale_end_day',
'presale_end_time',
}
rel_fields = {'date_admission', 'presale_start', 'presale_end'}
qs = qs.annotate(
**{
k + '_day': TruncDate(k, tzinfo=self.request.event.timezone)
for k in ('date_from', 'date_to', 'date_admission')
},
**{
k + '_time': TruncTime(k, tzinfo=self.request.event.timezone)
for k in ('date_from', 'date_to', 'date_admission')
},
**{
k + '_rel_min': (UnixTimestamp('date_from') - UnixTimestamp(k)) / 60
for k in rel_fields
},
**{
k + '_rel_days': TruncDate('date_from', tzinfo=self.request.event.timezone) - TruncDate(k, tzinfo=self.request.event.timezone)
for k in rel_fields
}
)
for k in fields:
existing_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
existing_values = list(qs.order_by(k).values(k).distinct())
if len(existing_values) == 1:
initial[k] = existing_values[0][k]
elif len(existing_values) > 1:
mixed_values.add(k)
initial[k] = None
for k in rel_fields:
existing_fixed_values = list(qs.order_by(k).values(k).distinct())
existing_rel_days_values = list(qs.order_by(f'{k}_rel_days').values(f'{k}_rel_days').distinct())
existing_rel_min_values = list(qs.order_by(f'{k}_rel_min').values(f'{k}_rel_min').distinct())
if len(existing_fixed_values) == 1:
if existing_fixed_values[0][k]:
initial[f"{k}_rel"] = RelativeDateWrapper(existing_fixed_values[0][k])
else:
initial[f"{k}_rel"] = None
elif len(existing_rel_min_values) == 1:
if rel_min := existing_rel_min_values[0][f'{k}_rel_min']:
initial[f"{k}_rel"] = RelativeDateWrapper(RelativeDate(
days=0,
base_date_name='event__date_from',
time=None,
minutes=abs(rel_min),
is_after=rel_min < 0,
))
else:
initial[f"{k}_rel"] = None
elif len(existing_rel_days_values) == 1:
if rel_days := existing_rel_days_values[0][f'{k}_rel_days']:
initial[f"{k}_rel"] = RelativeDateWrapper(RelativeDate(
days=abs(rel_days.days),
base_date_name='event__date_from',
time=None,
minutes=None,
is_after=rel_days.days < 0,
))
else:
initial[f"{k}_rel"] = None
elif len(existing_fixed_values) > 1 or len(existing_rel_days_values) > 1 or len(existing_rel_min_values) > 1:
mixed_values.add(k)
initial[k] = None
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
kwargs['prefix'] = 'bulkedit'
+1 -1
View File
@@ -342,7 +342,7 @@ def nav_context_list(request):
max_to=Max('subevents__date_to'),
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate(
order_from=Coalesce('min_from', 'date_from'),
order_from=Coalesce('max_from', 'date_from'),
).order_by('-order_from', 'slug')
if request.user.has_active_staff_session(request.session.session_key):
+1
View File
@@ -761,6 +761,7 @@ class UserNotificationsEditView(TemplateView):
{k: a.get(t) for k, a in self.global_set.items()},
)
for t, tv in self.types.items()
if tv.is_event_level or not self.event
]
ctx['event'] = self.event
if self.event:
-51
View File
@@ -1,51 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from pretix.base.services.quotas import QuotaAvailability
def prepare_quotas_for_boxes(quotas):
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
q.used = (
qa.count_paid_orders.get(q, 0) +
qa.count_pending_orders.get(q, 0) +
qa.count_exited_orders.get(q, 0) +
qa.count_vouchers.get(q, 0) +
qa.count_waitinglist.get(q, 0) +
qa.count_cart.get(q, 0)
)
if q.size is not None:
other_blocked = q.size - q.cached_availability_paid_orders - q.cached_avail[1]
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
q.percent_other = min(
100,
round(other_blocked / q.size * 100) if q.size > 0 else 100
)
+12 -1
View File
@@ -26,7 +26,8 @@ from django.contrib.postgres.indexes import BrinIndex
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db import connection, transaction
from django.db.models import (
Aggregate, Expression, F, Field, JSONField, Lookup, OrderBy, Value,
Aggregate, Expression, F, Field, Func, IntegerField, JSONField, Lookup,
OrderBy, Value,
)
from django.utils.functional import lazy
@@ -313,3 +314,13 @@ class IgnoreOnSQLiteMixin:
class BrinIndexIgnoredOnSQLite(IgnoreOnSQLiteMixin, BrinIndex):
pass
class UnixTimestamp(Func):
output_field = IntegerField()
def as_postgresql(self, compiler, connection):
return self.as_sql(compiler, connection, template="EXTRACT(epoch from %(expressions)s)")
def as_sqlite(self, compiler, connection):
return self.as_sql(compiler, connection, template="CAST(strftime('%%%%s', datetime(%(expressions)s)) AS INT)")
+76 -17
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-07-15 14:20+0000\n"
"Last-Translator: Llewelyn Barkhuizen <llewelyn@akademia.ac.za>\n"
"Language-Team: Afrikaans <https://translate.pretix.eu/projects/pretix/pretix/"
@@ -198,11 +198,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr ""
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr ""
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr ""
@@ -7005,6 +7006,14 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid "Gift card not found."
msgstr ""
#: pretix/base/payment.py
msgid "Only test gift card can be used in test mode and vice versa."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
@@ -14553,7 +14562,7 @@ msgstr ""
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
@@ -14619,10 +14628,18 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "(will be generated)"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr ""
@@ -15769,7 +15786,7 @@ msgstr ""
msgid "The SSO provider has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr ""
@@ -23111,7 +23128,29 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -23134,12 +23173,20 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgid "External SSO provider:"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
@@ -23147,6 +23194,13 @@ msgctxt "sso"
msgid "Redirection URL"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -26235,17 +26289,8 @@ msgid "The provider has been created."
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -28493,6 +28538,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr ""
@@ -29290,6 +29345,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr ""
+76 -17
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -196,11 +196,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr ""
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr ""
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr ""
@@ -7003,6 +7004,14 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid "Gift card not found."
msgstr ""
#: pretix/base/payment.py
msgid "Only test gift card can be used in test mode and vice versa."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
@@ -14551,7 +14560,7 @@ msgstr ""
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
@@ -14617,10 +14626,18 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "(will be generated)"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr ""
@@ -15767,7 +15784,7 @@ msgstr ""
msgid "The SSO provider has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr ""
@@ -23109,7 +23126,29 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -23132,12 +23171,20 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgid "External SSO provider:"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
@@ -23145,6 +23192,13 @@ msgctxt "sso"
msgid "Redirection URL"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -26233,17 +26287,8 @@ msgid "The provider has been created."
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -28491,6 +28536,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr ""
@@ -29288,6 +29343,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr ""
+110 -20
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-07-20 08:01+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix/ar/"
@@ -201,11 +201,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr "قائمة العناوين المسموح بها بعد تسجيل الخروج، مفصولة بمسافة"
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr "هوية العميل"
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr "سر العميل"
@@ -7461,6 +7462,18 @@ msgstr "لم يتم قبول بطاقة الهدايا هذه من قبل منظ
msgid "This gift card was used in the meantime. Please try again."
msgstr "تم استخدام بطاقة الهدايا هذه في هذه الأثناء. حاول مرة اخرى."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card not found."
msgstr "رمز بطاقة الهدية"
#: 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 "يمكن استخدام بطاقات الهدايا التجريبية فقط في وضع الاختبار."
#: pretix/base/payment.py
#, fuzzy
#| msgid ""
@@ -16327,8 +16340,11 @@ msgid "Phone"
msgstr "الهاتف"
#: pretix/control/forms/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr "عنوان URL الأساسي"
#: pretix/control/forms/organizer.py
@@ -16401,10 +16417,23 @@ msgstr ""
"معلمات الاستعلام الاختيارية، التي ستُضاف إلى استدعاءات نقطة نهاية التفويض. "
"أدخلها على النحو التالي: {example}"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgid "Base URL / issuer"
msgstr "عنوان URL الأساسي"
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr "إلغاء صلاحية سر العميل القديم وإنشاء سر جديد"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "will be retried"
msgid "(will be generated)"
msgstr "سيتم إعادة محاكمته"
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr "الاسم المختصر للمنظم"
@@ -17643,7 +17672,7 @@ msgstr "تم تغيير مزود خدمة SSO."
msgid "The SSO provider has been deleted."
msgstr "تم حذف مزود خدمة SSO."
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr "تم إنشاء عميل SSO."
@@ -25794,9 +25823,33 @@ msgstr "عميل SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
#, fuzzy
#| msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr "إنشاء عميل SSO جديد"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"You can allow your customers to log into other systems using their customer "
@@ -25820,19 +25873,38 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr "لا يمكن حذف مزود SSO هذا لأنه تم استخدامه بالفعل."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgstr "مزود خدمة تسجيل الدخول الموحد (SSO):"
#, fuzzy
#| msgid "Delete SSO provider:"
msgid "External SSO provider:"
msgstr "حذف مزود SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
#, fuzzy
#| msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr "إنشاء مزود جديد لخدمة تسجيل الدخول الموحد (SSO)"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgctxt "sso"
msgid "Redirection URL"
msgstr "إعادة توجيه عنوان URL"
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -29328,19 +29400,12 @@ msgid "The provider has been created."
msgstr "تم إنشاء المزود."
#: pretix/control/views/organizer.py
#, python-brace-format
#, fuzzy
#| msgid ""
#| "Your changes have been saved. Please note down the following client "
#| "secret, it will never be shown again: {secret}"
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
"تم إنشاء عميل SSO. يرجى تدوين «السر الخاص بالعميل» التالي، حيث لن يتم عرضه "
"مرة أخرى أبدًا: {secret}"
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
"تم حفظ التغييرات التي أجريتها. يرجى تدوين «السر الخاص بالعميل» التالي، حيث "
"لن يتم عرضه مرة أخرى أبدًا: {secret}"
@@ -31797,6 +31862,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr "تحديد البحث بالمنتجات"
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr "تصنيف حالة التسجيل"
@@ -32712,6 +32787,10 @@ msgstr ""
msgid "MobilePay"
msgstr "MobilePay"
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr "وجهة"
@@ -36684,6 +36763,17 @@ msgstr "الوصول إلى الكتابة"
msgid "Kosovo"
msgstr "كوسوفو"
#~ msgid "SSO provider:"
#~ msgstr "مزود خدمة تسجيل الدخول الموحد (SSO):"
#, python-brace-format
#~ msgid ""
#~ "The SSO client has been created. Please note down the following client "
#~ "secret, it will never be shown again: {secret}"
#~ msgstr ""
#~ "تم إنشاء عميل SSO. يرجى تدوين «السر الخاص بالعميل» التالي، حيث لن يتم "
#~ "عرضه مرة أخرى أبدًا: {secret}"
#~ msgid ""
#~ "This option currently only affects the calendar of this event series, not "
#~ "the organizer-wide calendar."
+76 -17
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2024-12-11 01:00+0000\n"
"Last-Translator: Neriman Memmedli <nerim4n@pm.me>\n"
"Language-Team: Azerbaijani <https://translate.pretix.eu/projects/pretix/"
@@ -198,11 +198,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr ""
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr ""
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr ""
@@ -7005,6 +7006,14 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid "Gift card not found."
msgstr ""
#: pretix/base/payment.py
msgid "Only test gift card can be used in test mode and vice versa."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
@@ -14553,7 +14562,7 @@ msgstr ""
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
@@ -14619,10 +14628,18 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "(will be generated)"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr ""
@@ -15769,7 +15786,7 @@ msgstr ""
msgid "The SSO provider has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr ""
@@ -23111,7 +23128,29 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -23134,12 +23173,20 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgid "External SSO provider:"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
@@ -23147,6 +23194,13 @@ msgctxt "sso"
msgid "Redirection URL"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -26235,17 +26289,8 @@ msgid "The provider has been created."
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -28495,6 +28540,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr ""
@@ -29292,6 +29347,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr ""
+91 -19
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-09-20 14:00+0000\n"
"Last-Translator: Rita Gimenez <barcelonamusictech@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix/"
@@ -206,11 +206,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr "Llista de URIs permeses separades per espais"
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr "ID del client"
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr "Secret del client"
@@ -7960,6 +7961,18 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr "Aquesta targeta regal ja s'ha fet servir. Si us plau torneu a provar."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card not found."
msgstr "Codi del val regal"
#: 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 mode de proves només es poden fer servir targetes regal de proves."
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
@@ -17269,7 +17282,7 @@ msgstr ""
#, fuzzy
#| msgid "Target URL"
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr "URL objectiu"
#: pretix/control/forms/organizer.py
@@ -17343,10 +17356,22 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "Target URL"
msgid "Base URL / issuer"
msgstr "URL objectiu"
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "Amount not refunded"
msgid "(will be generated)"
msgstr "Import no reemborsat"
#: pretix/control/forms/organizer.py
#, fuzzy
#| msgid "Organizer name"
@@ -18651,7 +18676,7 @@ msgstr "S'ha completat la tasca."
msgid "The SSO provider has been deleted."
msgstr "El producte seleccionat ha estat esborrat."
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "The task has been completed."
msgid "The SSO client has been created."
@@ -26862,9 +26887,31 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, fuzzy
#| msgid "Create a new event"
msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr "Crear un nou esdeveniment"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"You can allow your customers to log into other systems using their customer "
@@ -26890,17 +26937,25 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#, fuzzy
#| msgid "Payment provider"
msgid "SSO provider:"
msgstr "Proveïdor de pagament"
#| msgid "Delete order"
msgid "External SSO provider:"
msgstr "Esborrar comanda"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, fuzzy
#| msgid "Create a new product"
msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr "Crear un nou producte"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#, fuzzy
#| msgid "Redirection URIs"
@@ -26908,6 +26963,13 @@ msgctxt "sso"
msgid "Redirection URL"
msgstr "URIs de la redirecció"
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -30249,17 +30311,8 @@ msgid "The provider has been created."
msgstr "S'ha completat la tasca."
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -32796,6 +32849,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr "Restringeix al països"
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
#, fuzzy
#| msgid "Filter by status"
@@ -33690,6 +33753,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
#, fuzzy
#| msgid "Description"
@@ -37907,6 +37974,11 @@ msgstr "Accés d'escriptura"
msgid "Kosovo"
msgstr "Kosovo"
#, fuzzy
#~| msgid "Payment provider"
#~ msgid "SSO provider:"
#~ msgstr "Proveïdor de pagament"
#, python-format
#~ msgid "from %(minprice)s"
#~ msgstr "des de %(minprice)s"
+98 -18
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2026-01-29 19:42+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
@@ -200,11 +200,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr "Seznam povolených URI po odhlášení, oddělených mezerou"
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr "Klientské ID"
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr "Klientské heslo"
@@ -7581,6 +7582,18 @@ msgstr "Tato dárková karta není organizátorem akce akceptována."
msgid "This gift card was used in the meantime. Please try again."
msgstr "Tato dárková karta byla mezitím použita. Zkuste to prosím znovu."
#: pretix/base/payment.py
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card not found."
msgstr "kód dárkové poukázky"
#: 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 "V testovacím režimu lze používat pouze testovací dárkové poukázky."
#: pretix/base/payment.py
#, fuzzy
#| msgid ""
@@ -16506,8 +16519,11 @@ msgid "Phone"
msgstr "Telefon"
#: pretix/control/forms/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr "Základní adresa URL"
#: pretix/control/forms/organizer.py
@@ -16578,10 +16594,23 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgctxt "sso_oidc"
#| msgid "Base URL"
msgid "Base URL / issuer"
msgstr "Základní adresa URL"
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr "Zneplatnit staré klientské tajemství a vygenerovat nové"
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#, fuzzy
#| msgid "You will be refunded"
msgid "(will be generated)"
msgstr "Bude vám vrácena částka"
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr "Krátký název organizátora"
@@ -17872,7 +17901,7 @@ msgstr "Poskytovatel SSO byl změněn."
msgid "The SSO provider has been deleted."
msgstr "Poskytovatel SSO byl odstraněn."
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr "Klient SSO byl vytvořen."
@@ -25870,9 +25899,33 @@ msgstr "Klient SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
#, fuzzy
#| msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr "Vytvořit nového klienta SSO"
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"You can allow your customers to log into other systems using their customer "
@@ -25897,19 +25950,38 @@ msgstr ""
"Tohoto zprostředkovatele SSO nelze odstranit, protože již byl používán."
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgstr "Poskytovatel SSO:"
#, fuzzy
#| msgid "Delete SSO provider:"
msgid "External SSO provider:"
msgstr "Odstranění poskytovatele SSO:"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
#, fuzzy
#| msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr "Vytvořit nového poskytovatele SSO"
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgctxt "sso"
msgid "Redirection URL"
msgstr "Přesměrování URL"
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -29180,17 +29252,8 @@ msgid "The provider has been created."
msgstr "Poskytovatel byl vytvořen."
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -31580,6 +31643,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr "Omezit na produkty"
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr ""
@@ -32404,6 +32477,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr "Destinace"
@@ -36435,6 +36512,9 @@ msgstr "Přístup k zápisu"
msgid "Kosovo"
msgstr "Kosovo"
#~ msgid "SSO provider:"
#~ msgstr "Poskytovatel SSO:"
#~ msgid ""
#~ "This option currently only affects the calendar of this event series, not "
#~ "the organizer-wide calendar."
+76 -17
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-30 07:26+0000\n"
"POT-Creation-Date: 2026-10-28 10:48+0000\n"
"PO-Revision-Date: 2023-11-14 11:00+0000\n"
"Last-Translator: Charliecoleg <DM229135@colegsirgar.ac.uk>\n"
"Language-Team: Welsh <https://translate.pretix.eu/projects/pretix/pretix/cy/"
@@ -203,11 +203,12 @@ msgid "Allowed Post Logout URIs list, space separated"
msgstr "Rhestr URIs a ganiateir, gofod wedi'i wahanu"
#: pretix/api/models.py pretix/base/models/customers.py
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
#: pretix/plugins/paypal2/payment.py
msgid "Client ID"
msgstr "ID cleient"
#: pretix/api/models.py
#: pretix/api/models.py pretix/control/views/organizer.py
msgid "Client secret"
msgstr "Cyfrinach y cleient"
@@ -7043,6 +7044,14 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid "Gift card not found."
msgstr ""
#: pretix/base/payment.py
msgid "Only test gift card can be used in test mode and vice versa."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
@@ -14643,7 +14652,7 @@ msgstr "Ffôn"
#: pretix/control/forms/organizer.py
msgctxt "sso_oidc"
msgid "Base URL"
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
@@ -14709,10 +14718,18 @@ msgid ""
"endpoint. Enter as: {example}"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "Base URL / issuer"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Invalidate old client secret and generate a new one"
msgstr ""
#: pretix/control/forms/organizer.py pretix/control/views/organizer.py
msgid "(will be generated)"
msgstr ""
#: pretix/control/forms/organizer.py
msgid "Organizer short name"
msgstr ""
@@ -15861,7 +15878,7 @@ msgstr ""
msgid "The SSO provider has been deleted."
msgstr ""
#: pretix/control/logdisplay.py
#: pretix/control/logdisplay.py pretix/control/views/organizer.py
msgid "The SSO client has been created."
msgstr ""
@@ -23298,7 +23315,29 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid "Create a new SSO client"
msgid "Create credentials for a new SSO client"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
msgid ""
"After submitting this form, client credentials for your SSO client will be "
"generated."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclient_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a "
"href=\"%(url)s\">Using pretix as an SSO provider with external SSO clients</"
"a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
msgid ""
"\n"
" \t\"Login with pretix\" in external systems\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoclients.html
@@ -23321,12 +23360,20 @@ msgid "This SSO provider cannot be deleted since it has already been used."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
msgid "SSO provider:"
msgid "External SSO provider:"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid "Create a new SSO provider"
msgid "Connect to an external SSO provider"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
#, python-format
msgid ""
"For more information, refer to our documentation on <a href=\"%(url)s\" "
"target=\"_blank\">Using pretix as an SSO client</a>."
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoprovider_edit.html
@@ -23334,6 +23381,13 @@ msgctxt "sso"
msgid "Redirection URL"
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"\n"
" \tConnect to an external login provider\n"
" "
msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/ssoproviders.html
msgid ""
"You can connect existing Single-Sign-On (SSO) providers to allow your "
@@ -26454,17 +26508,8 @@ msgid "The provider has been created."
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"The SSO client has been created. Please note down the following client "
"secret, it will never be shown again: {secret}"
msgstr ""
#: pretix/control/views/organizer.py
#, python-brace-format
msgid ""
"Your changes have been saved. Please note down the following client secret, "
"it will never be shown again: {secret}"
"Please note down the following client secret, it will never be shown again."
msgstr ""
#: pretix/control/views/organizer.py
@@ -28729,6 +28774,16 @@ msgctxt "sendmail_form"
msgid "Restrict to products"
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgctxt "sendmail_form"
msgid ""
"There may be multiple mails sent out to the same mail address if one order "
"contains multiple attendee products for it, if you restrict to products "
"while also restricting mails to attendees only. This is intended, as every "
"one of those get linked to their own separate order page restricted to only "
"that product."
msgstr ""
#: pretix/plugins/sendmail/forms.py
msgid "Filter check-in status"
msgstr ""
@@ -29526,6 +29581,10 @@ msgstr ""
msgid "MobilePay"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py
msgid "Destination"
msgstr ""
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more