Compare commits

...

81 Commits

Author SHA1 Message Date
Mira Weller 10bc49bc3a Use correct LogEntry type for changes to product categories 2024-11-07 17:16:02 +01:00
Raphael Michel 5aba1f9a23 Do not try to delete cart positions twice (PRETIXEU-ARN) 2024-11-07 14:28:35 +01:00
Damiano a4eed87396 Translations: Update Italian
Currently translated at 23.7% (1370 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Damiano 08879d0d55 Translations: Update Italian
Currently translated at 23.5% (1361 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Christiaan de Die le Clercq c276a19bcc Translations: Update Dutch
Currently translated at 87.7% (5067 of 5774 strings)

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

powered by weblate
2024-11-07 11:27:00 +01:00
Raphael Michel 1e3c6e0b68 Order detail: Do not show expire date for pending approval (Z#23171168) (#4613) 2024-11-07 09:26:10 +01:00
CVZ-es 4e283eb560 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-06 12:32:10 +01:00
CVZ-es 52a1983630 Translations: Update French
Currently translated at 94.8% (5475 of 5774 strings)

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

powered by weblate
2024-11-06 12:32:10 +01:00
Raphael Michel 3d85d9d865 Remove deprecated auto_checkin_sales_channels (#4587)
* Remove deprecated auto_checkin_sales_channels

* Fix Query count
2024-11-06 12:30:41 +01:00
Raphael Michel 4ca9a43890 Fix performance issue in filtering checkin list (Z#23170917) (#4607)
* Fix performance issue in filtering checkin list

* remove test
2024-11-06 12:30:37 +01:00
mscherer d8bac7db65 Remove slimit dependencies (#4609)
It doesn't work anymore on python 3.13, since
2to3 is deprecated and removed (PEP 594).

Trying to build on Fedora 41 result in:

	Downloading slimit-0.8.1.zip (88 kB)
	Installing build dependencies: started
	Installing build dependencies: finished with status 'done'
	Getting requirements to build wheel: started
	Getting requirements to build wheel: finished with status 'error'
	error: subprocess-exited-with-error
	× Getting requirements to build wheel did not run successfully.
	│ exit code: 1
	╰─> [1 lines of output]
	Python 3.X support requires the 2to3 tool.
	[end of output]

And slimit is not used in the code base nor anywhere in git (no single
match)
2024-11-06 12:28:43 +01:00
Mira 91de0f93e6 Allow organizers to manually add fees to an existing order (#4590) 2024-11-05 14:37:50 +01:00
Richard Schreiber 901565203b Hide input dependencies recursively if input will be disabled (#4599)
---------

Co-authored-by: Mira <weller@rami.io>
2024-11-05 14:34:42 +01:00
dependabot[bot] 14c6c9c0d7 Update sentry-sdk requirement from ==2.17.* to ==2.18.* (#4608)
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.17.0...2.18.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 09:55:13 +01:00
CVZ-es 6de6cf6c08 Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 29306b3a4d Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Katrine Tella ca69996611 Translations: Update Danish
Currently translated at 48.6% (2809 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Casper Schack 16419b6ae4 Translations: Update Danish
Currently translated at 48.6% (2809 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Damiano d6258b9b54 Translations: Update Italian
Currently translated at 23.3% (1351 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Christiaan de Die le Clercq 6f75608196 Translations: Update Dutch
Currently translated at 87.7% (5064 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Mika Andrianarijaona 6ef88e009b Translations: Update French
Currently translated at 94.8% (5475 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 957100a195 Translations: Update Spanish
Currently translated at 100.0% (232 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 112ef0908f Translations: Update Spanish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 91aaff7359 Translations: Update Spanish
Currently translated at 96.5% (5575 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 8ab61e2c38 Translations: Update French
Currently translated at 91.8% (213 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es c8ba5cc427 Translations: Update French
Currently translated at 94.8% (5474 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 5ebad31b7d Translations: Update Spanish
Currently translated at 96.4% (5567 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es 0429377f7d Translations: Update French
Currently translated at 91.3% (212 of 232 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Mossroy 76e4b797a1 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
Jonathan Berger 5f0009c996 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
cpoisnel de63a4be01 Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
CVZ-es f3432139cb Translations: Update French
Currently translated at 94.6% (5466 of 5774 strings)

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

powered by weblate
2024-11-05 09:54:50 +01:00
dependabot[bot] 0b82ac9115 Bump @babel/preset-env in /src/pretix/static/npm_dir (#4604)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.4 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  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>
2024-11-04 20:55:31 +01:00
Martin Gross eb685b5141 API: Expose cancellation_date on order endpoint (Z#23170733) (#4606)
Co-authored-by: robbi5 <richt@rami.io>
2024-11-04 16:41:44 +01:00
dependabot[bot] 5f7f0bd8f1 Bump @babel/core from 7.25.2 to 7.26.0 in /src/pretix/static/npm_dir
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.25.2 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 14:44:32 +01:00
Raphael Michel 9fcef2dcaa API: Fix deleting checkin-lists (Z#23170525) (PRETIXEU-AS5) (#4600) 2024-10-31 17:44:04 +01:00
Raphael Michel fc3b186b93 Badge export: Allow to filter and sort by order date (Z#23168742) (#4588) 2024-10-31 14:42:44 +01:00
Tinna Sandström a406884575 Translations: Update Swedish
Currently translated at 98.3% (5679 of 5774 strings)

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

powered by weblate
2024-10-31 13:02:34 +01:00
Anarion Dunedain 57ccd5f289 Translations: Update Polish
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-31 13:02:34 +01:00
Mira f4ac7e7f65 Add warning that cross-selling can't prevent unwanted combinations (#4596)
* Add warning that cross-selling can't prevent unwanted combinations

* Update src/pretix/control/templates/pretixcontrol/items/category.html

Co-authored-by: Raphael Michel <michel@rami.io>

---------

Co-authored-by: Raphael Michel <michel@rami.io>
2024-10-31 12:59:28 +01:00
Raphael Michel 81d7045b31 Public event filter: Make "all" option clearer (Z#23169843) (#4585)
* Public event filter: Make "all" option clearer

* Fix widget tests

* Update src/tests/presale/test_widget.py

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

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-29 17:31:51 +01:00
Richard Schreiber f9502a3212 Fix testutils redis mock (#4586) 2024-10-29 16:37:06 +01:00
Richard Schreiber a31f624417 Dev: add support for asyncio_default_fixture_loop_scope in pytest-asyncio (#4589) 2024-10-29 16:36:43 +01:00
Raphael Michel 3f99e0bece Bump version to 2024.11.0.dev0 2024-10-29 11:47:44 +01:00
Raphael Michel 7e64f2b38a Bump version to 2024.10.0 2024-10-29 11:47:10 +01:00
Raphael Michel ee2bc93608 Fix handling of device_info_updated signal 2024-10-29 11:30:35 +01:00
Raphael Michel fb4bed9d0d Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-29 10:08:27 +01:00
Raphael Michel aec75e4d0c Translations: Update German
Currently translated at 100.0% (5774 of 5774 strings)

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

powered by weblate
2024-10-29 10:08:27 +01:00
Raphael Michel e7e41470fb Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-10-29 09:54:05 +01:00
Raphael Michel 0aa9dda90a Cross-selling: Use different label if there are no add-on products (#4577)
* Cross-selling: Use different label if there are no add-on products

* Update src/pretix/presale/templates/pretixpresale/event/checkout_addons.html

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

* Fix a11y problem

* Fix headline order

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-10-29 09:53:36 +01:00
Mira d97c983b6f Fix API serializer for auto_checkin_rules to accept sales channel identifiers (Z#23170095) (#4579)
* Fix API serializer for auto_checkin_rules to accept sales channel identifiers instead of PKs

* False -> false in json
2024-10-29 09:14:22 +01:00
dependabot[bot] 6c957f31ca Update django-hijack requirement from ==3.6.* to ==3.7.* (#4581)
Updates the requirements on [django-hijack](https://github.com/django-hijack/django-hijack) to permit the latest version.
- [Release notes](https://github.com/django-hijack/django-hijack/releases)
- [Changelog](https://github.com/django-hijack/django-hijack/blob/master/docs/release-button.png)
- [Commits](https://github.com/django-hijack/django-hijack/compare/3.6.0...3.7.0)

---
updated-dependencies:
- dependency-name: django-hijack
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 09:14:04 +01:00
dependabot[bot] 8e6b4b3ec7 Update pypdf requirement from ==5.0.* to ==5.1.* (#4582)
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/5.0.0...5.1.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 09:13:58 +01:00
arjan-s b24de62f73 Translations: Update Dutch
Currently translated at 85.5% (4938 of 5773 strings)

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

powered by weblate
2024-10-29 09:13:45 +01:00
Raphael Michel cdbd220a12 Fix time zone of parsed print log 2024-10-28 17:49:47 +01:00
Raphael Michel 2f11aee512 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel 8ea475ce39 Translations: Update German
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel b29bc9db96 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel 6bd6694132 Translations: Update German
Currently translated at 100.0% (5773 of 5773 strings)

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

powered by weblate
2024-10-28 13:04:02 +01:00
Raphael Michel 110e6e248e Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2024-10-28 12:31:20 +01:00
Davide Manzella 985f4d969d Translations: Update Italian
Currently translated at 23.1% (1333 of 5762 strings)

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

powered by weblate
2024-10-28 12:30:56 +01:00
Mira 826bd07b01 Add action buttons to "missing quota" warnings (#4533)
* Add action buttons to "missing quota" warnings

* Update item.py
2024-10-28 12:25:03 +01:00
Raphael Michel 3e4e86742a Add print logs (#4475)
* Add print logs

* Add attribute successful

* Rebase migration

* Fix tests on postgres
2024-10-28 12:24:23 +01:00
Raphael Michel ef5fcde5d9 Seating plans: Validate duplicate seat IDs (#4564) 2024-10-28 11:27:27 +01:00
Raphael Michel 8f1d53d016 Tests: Add separation of database lock indices by pytest worker (#4553)
* Add separation of database lock indices by pytest worker

* Fix ID derivation

* Update conftest.py
2024-10-25 22:05:02 +02:00
Raphael Michel 9ca1573fcf Tests: Remove pytest-rerunfailures (#4572) 2024-10-25 22:00:14 +02:00
Raphael Michel 5795aa6492 Docs: Fix various sphinx warnings 2024-10-25 21:58:14 +02:00
Raphael Michel 6e0613a2af CI: Remove warnings from PostgreSQL about health check (#4571)
* CI: Remove warnings from PostgreSQL about health check

* Update tests.yml

* Update tests.yml
2024-10-25 21:45:34 +02:00
Raphael Michel b43ed38483 CI: Do not fail if codecov upload fails (#4570)
This happens all the time with external PRs until https://github.com/codecov/engineering-team/issues/1574 is solved and is doing more harm than good. We still see if there is no coverage attached.
2024-10-25 21:40:03 +02:00
Raphael Michel f0fedf0001 Tests: Resolve deprecation warning for pytest-asyncio (#4569) 2024-10-25 21:27:38 +02:00
Raphael Michel 19373b8f91 CI: Update codecov-action (#4568) 2024-10-25 21:19:48 +02:00
dependabot[bot] 45fd13786a Update pillow requirement from ==10.4.* to ==11.0.* (#4566)
Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.4.0...11.0.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 21:14:30 +02:00
dependabot[bot] ae5111ee7e Update zeep requirement from ==4.2.* to ==4.3.* (#4567)
Updates the requirements on [zeep](https://github.com/mvantellingen/python-zeep) to permit the latest version.
- [Release notes](https://github.com/mvantellingen/python-zeep/releases)
- [Changelog](https://github.com/mvantellingen/python-zeep/blob/main/CHANGES)
- [Commits](https://github.com/mvantellingen/python-zeep/compare/4.2.0...4.3.1)

---
updated-dependencies:
- dependency-name: zeep
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 20:53:11 +02:00
dependabot[bot] d8bf3065d0 Update redis requirement from ==5.1.* to ==5.2.* (#4565)
Updates the requirements on [redis](https://github.com/redis/redis-py) to permit the latest version.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.1.0a1...v5.2.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 20:51:30 +02:00
Raphael Michel 54f077665c CI: Remove mentions of travis CI, we have migrated away years ago (#4562) 2024-10-25 17:47:04 +02:00
Raphael Michel 482a66c546 CI: Run PostgreSQL as a native service (#4560)
* GH Actions: Fix PostgreSQL issues

* Map PostgreSQL port
2024-10-25 17:18:42 +02:00
Raphael Michel e4cef6e46b CI: Make sure apt is non-interactive (to not break act) (#4559) 2024-10-25 17:06:11 +02:00
Raphael Michel cbee1b71fe CI: Install dependencies with uv for speedup (#4558) 2024-10-25 17:05:58 +02:00
Raphael Michel 0cd1290624 Tests: Fix two flaky tests (#4557)
* Test: Fix test that relies on unstable database result order

* Tests: Fix test that fails between 0am and 1am
2024-10-25 17:04:16 +02:00
Raphael Michel 565f5e2ea7 Tests: Run fakeredis on different virtual ports per pytest worker (#4555) 2024-10-25 16:44:22 +02:00
Raphael Michel b46c0eba0c pytest: Fix failure with fakeserver and xdist 2024-10-25 14:32:28 +02:00
134 changed files with 34314 additions and 31031 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext unzip
run: sudo apt update && sudo apt install -y gettext unzip
- name: Install Python dependencies
run: pip3 install -U setuptools build pip check-manifest
- name: Run check-manifest
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system packages
run: sudo apt update && sudo apt install enchant-2 hunspell aspell-en
run: sudo apt update && sudo apt install -y enchant-2 hunspell aspell-en
- name: Install Dependencies
run: pip3 install -Ur requirements.txt
working-directory: ./doc
+3 -3
View File
@@ -35,9 +35,9 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system packages
run: sudo apt update && sudo apt install gettext
run: sudo apt update && sudo apt -y install gettext
- name: Install Dependencies
run: pip3 install -e ".[dev]"
run: pip3 install uv && uv pip install --system -e ".[dev]"
- name: Compile messages
run: python manage.py compilemessages
working-directory: ./src
@@ -62,7 +62,7 @@ jobs:
- name: Install system packages
run: sudo apt update && sudo apt install enchant-2 hunspell hunspell-de-de aspell-en aspell-de
- name: Install Dependencies
run: pip3 install -e ".[dev]"
run: pip3 install uv && uv pip install --system -e ".[dev]"
- name: Spellcheck translations
run: potypo
working-directory: ./src
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -e ".[dev]" psycopg2-binary
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run isort
run: isort -c .
working-directory: ./src
@@ -55,7 +55,7 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: pip3 install -e ".[dev]" psycopg2-binary
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run flake8
run: flake8 .
working-directory: ./src
+19 -13
View File
@@ -30,15 +30,21 @@ jobs:
python-version: "3.9"
- database: sqlite
python-version: "3.10"
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: pretix
options: >-
--health-cmd "pg_isready -U postgres -d pretix"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: harmon758/postgresql-action@v1
with:
postgresql version: '15'
postgresql db: 'pretix'
postgresql user: 'postgres'
postgresql password: 'postgres'
if: matrix.database == 'postgres'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@@ -50,9 +56,9 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install gettext
run: sudo apt update && sudo apt install -y gettext
- name: Install Python dependencies
run: pip3 install --ignore-requires-python -e ".[dev]" psycopg2-binary # We ignore that flake8 needs newer python as we don't run flake8 during tests
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Run checks
run: python manage.py check
working-directory: ./src
@@ -64,15 +70,15 @@ jobs:
run: make all compress
- name: Run tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/travis_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml --reruns 3 tests --maxfail=100
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --maxfail=100
- name: Run concurrency tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/travis_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reruns 0 --reuse-db
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reuse-db
if: matrix.database == 'postgres'
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
fail_ci_if_error: false
if: matrix.database == 'postgres' && matrix.python-version == '3.11'
+1 -1
View File
@@ -10,7 +10,7 @@ tests:
- cd src
- python manage.py check
- make all compress
- PRETIX_CONFIG_FILE=tests/travis_sqlite.cfg py.test --reruns 3 -n 3 tests --maxfail=100
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --maxfail=100
except:
- pypi
pypi:
+5 -5
View File
@@ -71,7 +71,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -113,7 +113,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -146,7 +146,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -167,7 +167,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
@@ -216,7 +216,7 @@ Endpoints
"mode": "placed",
"all_sales_channels": false,
"limit_sales_channels": ["web"],
"all_products": False,
"all_products": false,
"limit_products": [2, 3],
"limit_variations": [456],
"all_payment_methods": true,
+6 -23
View File
@@ -31,8 +31,6 @@ subevent integer ID of the date
position_count integer Number of tickets that match this list (read-only).
checkin_count integer Number of check-ins performed on this list (read-only).
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
**Deprecated, will be removed in pretix 2024.10.** Use :ref:`rest-autocheckinrules`: instead.
allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in.
allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan.
rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged.
@@ -91,10 +89,7 @@ Endpoints
"allow_entry_after_exit": true,
"exit_all_at": null,
"rules": {},
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
]
}
@@ -146,10 +141,7 @@ Endpoints
"allow_entry_after_exit": true,
"exit_all_at": null,
"rules": {},
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -246,10 +238,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
**Example response**:
@@ -271,10 +260,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
@@ -326,10 +312,7 @@ Endpoints
"subevent": null,
"allow_multiple_entries": false,
"allow_entry_after_exit": true,
"addon_match": false,
"auto_checkin_sales_channels": [
"pretixpos"
]
"addon_match": false
}
:param organizer: The ``slug`` field of the organizer to modify
@@ -342,7 +325,7 @@ Endpoints
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
Delete a check-in list. Note that this also deletes the information on all check-ins performed via this list.
Delete a check-in list. **Note that this also deletes the information on all check-ins performed via this list.**
**Example request**:
+133 -5
View File
@@ -104,6 +104,10 @@ url string The full URL to
payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below)
last_modified datetime Last modification of this object
cancellation_date datetime Time of order cancellation (or ``null``). **Note**:
Will not be set for partial cancellations and is not
reliable for orders that have been cancelled,
reactivated and cancelled again.
===================================== ========================== =======================================================
@@ -151,6 +155,9 @@ last_modified datetime Last modificati
The ``expires`` attribute can now be passed during order creation.
.. versionchanged:: 2024.11
The ``cancellation_date`` attribute has been added and can also be used as an ordering key.
.. _order-position-resource:
@@ -206,6 +213,17 @@ checkins list of objects List of **succe
├ device integer Internal ID of the device. Can be ``null``. **Deprecated**, since this ID is not otherwise used in the API and is therefore not very useful.
├ device_id integer Attribute ``device_id`` of the device. Can be ``null``.
└ auto_checked_in boolean Indicates if this check-in been performed automatically by the system
print_logs list of objects List of print jobs recorded e.g. by the pretix apps
├ id integer Internal ID of the print job
├ successful boolean Whether the print job successfully resulted in a print.
This is not expected to be 100 % reliable information (since
printer feedback is never perfect) and there is no guarantee
that unsuccessful jobs will be logged.
├ device_id integer Attribute ``device_id`` of the device that recorded the print. Can be ``null``.
├ datetime datetime Time of printing
├ source string Source of print job, e.g. name of the app used.
├ type string Type of print (currently ``badge``, ``ticket``, ``certificate``, or ``other``)
└ info object Additional data with client-dependent structure.
downloads list of objects List of ticket download options
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
@@ -233,6 +251,10 @@ pdf_data object Data object req
The attributes ``blocked``, ``valid_from`` and ``valid_until`` have been added.
.. versionchanged:: 2024.9
The attribute ``print_logs`` has been added.
.. _order-payment-resource:
Order payment resource
@@ -399,10 +421,21 @@ List of all orders
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -438,14 +471,15 @@ List of all orders
"provider": "banktransfer"
}
],
"refunds": []
"refunds": [],
"cancellation_date": null
}
]
}
: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 ``datetime``, ``code``,
``last_modified``, and ``status``. Default: ``datetime``
``last_modified``, ``status`` and ``cancellation_date``. Default: ``datetime``
:query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above)
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
@@ -626,10 +660,22 @@ Fetching individual orders
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -665,7 +711,8 @@ Fetching individual orders
"provider": "banktransfer"
}
],
"refunds": []
"refunds": [],
"cancellation_date": null
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -977,8 +1024,8 @@ Creating orders
* ``internal_reference``
* ``vat_id``
* ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``positions``
@@ -1581,10 +1628,22 @@ List of all order positions
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -1695,10 +1754,22 @@ Fetching individual positions
"type": "entry",
"gate": null,
"device": 2,
"device_id": 1,
"datetime": "2017-12-25T12:45:23Z",
"auto_checked_in": false
}
],
"print_logs": [
{
"id": 1,
"type": "badge",
"successful": true,
"datetime": "2017-12-25T12:45:23Z",
"device_id": 1,
"source": "pretixSCAN",
"info": {}
}
],
"answers": [
{
"question": 12,
@@ -1795,6 +1866,10 @@ Manipulating individual positions
The endpoints to manage blocks have been added.
.. versionchanged:: 2024.9
The API now supports logging ticket and badge prints.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
Updates specific fields on an order position. Currently, only the following fields are supported:
@@ -2054,6 +2129,59 @@ Manipulating individual positions
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/printlog/
Creates a print log, stating that this ticket has been printed.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/printlog/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"datetime": "2024-09-19T13:37:00+02:00",
"source": "pretixPOS",
"type": "badge",
"info": {
"cashier": 1234
}
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/pdf
{
"id": 1234,
"device_id": null,
"datetime": "2024-09-19T13:37:00+02:00",
"source": "pretixPOS",
"type": "badge",
"info": {
"cashier": 1234
}
}
:param organizer: The ``slug`` field of the organizer to create a log for
:param event: The ``slug`` field of the event to create a log for
:param id: The ``id`` field of the order position to create a log for
:statuscode 201: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource
**or** downloads are not available for this order position at this time. The response content will
contain more details.
:statuscode 404: The requested order position or download provider does not exist.
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
seconds.
Changing order contents
-----------------------
+2 -2
View File
@@ -313,7 +313,7 @@ Endpoints for event exports
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
Endpoints for organizer exports
---------------------------
-------------------------------
.. http:get:: /api/v1/organizers/(organizer)/scheduled_exports/
@@ -553,4 +553,4 @@ Endpoints for organizer exports
:statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource.
.. _RFC 5545: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3
.. _RFC 5545: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3
+1 -1
View File
@@ -1,4 +1,4 @@
.. _`rest-reusablemedia`:
.. _`rest-seats`:
Seats
=====
+1
View File
@@ -17,6 +17,7 @@ First, you need to declare that you are using non-essential cookies by respondin
signal:
.. automodule:: pretix.presale.signals
:no-index:
:members: register_cookie_providers
You are expected to return a list of ``CookieProvider`` objects instantiated from the following class:
+10
View File
@@ -22,12 +22,14 @@ Order events
There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:no-index:
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins
"""""""""
.. automodule:: pretix.base.signals
:no-index:
:members: checkin_created
@@ -39,18 +41,21 @@ Frontend
.. automodule:: pretix.presale.signals
:no-index:
:members: order_info, order_info_top, order_meta_from_request, order_api_meta_from_request
Request flow
""""""""""""
.. automodule:: pretix.presale.signals
:no-index:
:members: process_request, process_response
Vouchers
""""""""
.. automodule:: pretix.presale.signals
:no-index:
:members: voucher_redeem_info
Backend
@@ -62,24 +67,28 @@ Backend
item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:no-index:
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display, customer_created, customer_signed_in
Vouchers
""""""""
.. automodule:: pretix.control.signals
:no-index:
:members: item_forms, voucher_form_class, voucher_form_html, voucher_form_validation
Dashboards
""""""""""
.. automodule:: pretix.control.signals
:no-index:
:members: event_dashboard_widgets, user_dashboard_widgets, event_dashboard_top
Ticket designs
""""""""""""""
.. automodule:: pretix.base.signals
:no-index:
:members: layout_text_variables, layout_image_variables
.. automodule:: pretix.plugins.ticketoutputpdf.signals
@@ -89,4 +98,5 @@ API
---
.. automodule:: pretix.base.signals
:no-index:
:members: validate_event_settings, api_event_settings_fields
+1
View File
@@ -60,6 +60,7 @@ that we'll provide in this plugin:
Similar signals exist for other objects:
.. automodule:: pretix.base.signals
:no-index:
:members: voucher_import_columns
-2
View File
@@ -84,8 +84,6 @@ convenient to you:
.. automethod:: _register_fonts
.. automethod:: _register_event_fonts
.. automethod:: _on_first_page
.. automethod:: _on_other_page
+3
View File
@@ -86,7 +86,10 @@ Signals
-------
.. automodule:: pretix.base.signals
:no-index:
:members: register_text_placeholders
.. automodule:: pretix.base.signals
:no-index:
:members: register_mail_placeholders
+1 -1
View File
@@ -1,5 +1,5 @@
KulturPass
=========
==========
.. note::
+1 -1
View File
@@ -158,7 +158,7 @@ expects and - more importantly - supports.
for a sample configuration in an academic context.
Note, that you can have multiple attributes with the same ``friendlyName``
but different ``name``s. This is often used in systems, where the same
but different ``name`` value. This is often used in systems, where the same
information (for example a persons name) is saved in different fields -
for example because one institution is returning SAML 1.0 and other
institutions are returning SAML 2.0 style attributes. Typically, this only
+7 -9
View File
@@ -43,7 +43,7 @@ dependencies = [
"django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1",
"django-hierarkey==1.2.*",
"django-hijack==3.6.*",
"django-hijack==3.7.*",
"django-i18nfield==1.9.*,>=1.9.4",
"django-libsass==0.9",
"django-localflavor==4.0",
@@ -74,26 +74,25 @@ dependencies = [
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.9.*",
"phonenumberslite==8.13.*",
"Pillow==10.4.*",
"Pillow==11.0.*",
"pretix-plugin-build",
"protobuf==5.28.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.22",
"pycryptodome==3.21.*",
"pypdf==5.0.*",
"pypdf==5.1.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"pytz",
"pytz-deprecation-shim==0.1.*",
"pyuca",
"qrcode==8.0",
"redis==5.1.*",
"redis==5.2.*",
"reportlab==4.2.*",
"requests==2.31.*",
"sentry-sdk==2.17.*",
"sentry-sdk==2.18.*",
"sepaxml==2.6.*",
"slimit",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2020041600",
@@ -102,7 +101,7 @@ dependencies = [
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*",
"webauthn==2.2.*",
"zeep==4.2.*"
"zeep==4.3.*"
]
[project.optional-dependencies]
@@ -117,12 +116,11 @@ dev = [
"isort==5.13.*",
"pep8-naming==0.14.*",
"potypo",
"pytest-asyncio",
"pytest-asyncio>=0.24",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.14.*",
"pytest-rerunfailures==14.*",
"pytest-sugar",
"pytest-xdist==3.6.*",
"pytest==8.3.*",
+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__ = "2024.10.0.dev0"
__version__ = "2024.11.0.dev0"
+4
View File
@@ -77,6 +77,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:order-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -112,6 +113,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -147,6 +149,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
@@ -188,6 +191,7 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:orderposition-list'),
('GET', 'api-v1:orderposition-answer'),
('GET', 'api-v1:orderposition-pdf_image'),
('POST', 'api-v1:orderposition-printlog'),
('POST', 'api-v1:order-mark-canceled'),
('POST', 'api-v1:orderpayment-list'),
('POST', 'api-v1:orderrefund-list'),
+2 -11
View File
@@ -26,31 +26,22 @@ from rest_framework.exceptions import ValidationError
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import Checkin, CheckinList, SalesChannel
from pretix.base.models import Checkin, CheckinList
class CheckinListSerializer(I18nAwareModelSerializer):
checkin_count = serializers.IntegerField(read_only=True)
position_count = serializers.IntegerField(read_only=True)
auto_checkin_sales_channels = serializers.SlugRelatedField(
slug_field="identifier",
queryset=SalesChannel.objects.none(),
required=False,
allow_empty=True,
many=True,
)
class Meta:
model = CheckinList
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
'include_pending', 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit',
'include_pending', 'allow_multiple_entries', 'allow_entry_after_exit',
'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['auto_checkin_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
if 'subevent' in self.context['request'].query_params.getlist('expand'):
self.fields['subevent'] = SubEventSerializer(read_only=True)
+29 -7
View File
@@ -55,7 +55,7 @@ from pretix.base.models import (
)
from pretix.base.models.orders import (
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
RevokedTicketSecret,
PrintLog, RevokedTicketSecret,
)
from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages
@@ -284,6 +284,26 @@ class CheckinSerializer(I18nAwareModelSerializer):
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class PrintLogSerializer(serializers.ModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = PrintLog
fields = (
"id",
"successful",
"datetime",
"source",
"type",
"device_id",
"info",
)
class FailedCheckinSerializer(I18nAwareModelSerializer):
error_reason = serializers.ChoiceField(choices=Checkin.REASONS, required=True, allow_null=False)
raw_barcode = serializers.CharField(required=True, allow_null=False)
@@ -476,6 +496,7 @@ class OrderPositionListSerializer(serializers.ListSerializer):
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = CheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True)
answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*', read_only=True)
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
@@ -490,7 +511,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
@@ -577,9 +598,9 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
'order__status', 'order__valid_if_pending', 'order__require_approval', 'valid_from', 'valid_until',
'blocked')
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat',
'require_attention', 'order__status', 'order__valid_if_pending', 'order__require_approval',
'valid_from', 'valid_until', 'blocked')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -732,12 +753,12 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending', 'api_meta'
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date'
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
)
def __init__(self, *args, **kwargs):
@@ -1494,6 +1515,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.answers = answers
pos.pseudonymization_id = "PREVIEW"
pos.checkins = []
pos.print_logs = []
pos_map[pos.positionid] = pos
else:
if pos.voucher:
+9 -3
View File
@@ -62,6 +62,7 @@ from pretix.base.models import (
CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition,
Question, ReusableMedium, RevokedTicketSecret, TeamAPIToken,
)
from pretix.base.models.orders import PrintLog
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
)
@@ -115,7 +116,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
if 'subevent' in self.request.query_params.getlist('expand'):
qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values', 'auto_checkin_sales_channels'
'subevent__seat_category_mappings', 'subevent__meta_values',
)
return qs
@@ -142,7 +143,9 @@ class CheckinListViewSet(viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic
def perform_destroy(self, instance):
instance.checkins.all().delete()
instance.log_action(
'pretix.event.checkinlist.deleted',
user=self.request.user,
@@ -365,8 +368,9 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists])
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
@@ -378,6 +382,7 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
'positions',
OrderPosition.objects.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
@@ -389,8 +394,9 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists])
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')
+13 -2
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import base64
import copy
import logging
from cryptography.hazmat.backends.openssl.backend import Backend
@@ -146,6 +147,8 @@ class InitializeView(APIView):
permission_classes = ()
def post(self, request, format=None):
from pretix.base.signals import device_info_updated
serializer = InitializationRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -160,6 +163,8 @@ class InitializeView(APIView):
if device.revoked:
raise ValidationError({'token': ['This initialization token has been revoked.']})
old_instance = copy.copy(device)
device.initialized = now()
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
@@ -174,6 +179,10 @@ class InitializeView(APIView):
device.log_action('pretix.device.initialized', data=serializer.validated_data, auth=device)
device_info_updated.send(
sender=Device, old_device=old_instance, new_device=device
)
serializer = DeviceSerializer(device)
return Response(serializer.data)
@@ -182,9 +191,12 @@ class UpdateView(APIView):
authentication_classes = (DeviceTokenAuthentication,)
def post(self, request, format=None):
from pretix.base.signals import device_info_updated
serializer = UpdateRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
device = request.auth
old_instance = copy.copy(device)
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.os_name = serializer.validated_data.get('os_name')
@@ -200,9 +212,8 @@ class UpdateView(APIView):
device.save()
device.log_action('pretix.device.updated', data=serializer.validated_data, auth=device)
from ...base.signals import device_info_updated
device_info_updated.send(
sender=Device, old_device=request.auth, new_device=device
sender=Device, old_device=old_instance, new_device=device
)
serializer = DeviceSerializer(device)
+2
View File
@@ -42,6 +42,7 @@ from pretix.base.models import (
Checkin, GiftCard, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
ReusableMedium,
)
from pretix.base.models.orders import PrintLog
from pretix.helpers import OF_SELF
from pretix.helpers.dicts import merge_dicts
@@ -79,6 +80,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
'order', 'order__event', 'order__event__organizer', 'seat',
).prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
)
),
+36 -3
View File
@@ -57,7 +57,8 @@ from pretix.api.serializers.order import (
OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
PrintLogSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer,
)
from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer,
@@ -75,7 +76,7 @@ from pretix.base.models import (
TeamAPIToken, generate_secret,
)
from pretix.base.models.orders import (
BlockedTicketSecret, QuestionAnswer, RevokedTicketSecret,
BlockedTicketSecret, PrintLog, QuestionAnswer, RevokedTicketSecret,
)
from pretix.base.payment import PaymentException
from pretix.base.pdf import get_images
@@ -214,7 +215,7 @@ class OrderViewSetMixin:
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
ordering_fields = ('datetime', 'code', 'status', 'last_modified', 'cancellation_date')
filterset_class = OrderFilter
lookup_field = 'code'
@@ -259,6 +260,7 @@ class OrderViewSetMixin:
'positions',
opq.all().prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
Prefetch('item', queryset=self.request.event.items.prefetch_related(
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), to_attr='meta_values_cached')
)),
@@ -280,6 +282,7 @@ class OrderViewSetMixin:
'positions',
opq.all().prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related('device')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'item', 'variation',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question').order_by('question__position')),
'seat',
@@ -1093,6 +1096,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
)
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
Prefetch('item', queryset=self.request.event.items.prefetch_related(
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'),
to_attr='meta_values_cached')
@@ -1136,6 +1140,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
else:
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
@@ -1254,6 +1259,34 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
)
return resp
@action(detail=True, url_name="printlog", url_path="printlog", methods=["POST"])
def printlog(self, request, **kwargs):
pos = self.get_object()
serializer = PrintLogSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
serializer.save(
position=pos,
device=request.auth if isinstance(request.auth, Device) else None,
user=request.user if request.user.is_authenticated else None,
api_token=request.auth if isinstance(request.auth, TeamAPIToken) else None,
oauth_application=request.auth.application if isinstance(request.auth, OAuthAccessToken) else None,
)
pos.order.log_action(
"pretix.event.order.print",
data={
"position": pos.pk,
"positionid": pos.positionid,
**serializer.validated_data,
},
auth=request.auth,
user=request.user,
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
@action(detail=True, url_name='pdf_image', url_path=r'pdf_image/(?P<key>[^/]+)')
def pdf_image(self, request, key, **kwargs):
pos = self.get_object()
@@ -0,0 +1,79 @@
# Generated by Django 4.2.16 on 2024-09-19 10:41
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
("pretixbase", "0271_itemcategory_cross_selling"),
]
operations = [
migrations.CreateModel(
name="PrintLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("datetime", models.DateTimeField(default=django.utils.timezone.now)),
("created", models.DateTimeField(auto_now_add=True, null=True)),
("successful", models.BooleanField(default=True)),
("source", models.CharField(max_length=255)),
("type", models.CharField(max_length=255)),
("info", models.JSONField(default=dict)),
(
"api_token",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="pretixbase.teamapitoken",
),
),
(
"device",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="print_logs",
to="pretixbase.device",
),
),
(
"oauth_application",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL,
),
),
(
"position",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="print_logs",
to="pretixbase.orderposition",
),
),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="print_logs",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ("-datetime",),
},
),
]
@@ -0,0 +1,48 @@
# Generated by Django 4.2.16 on 2024-10-29 15:03
from django.db import migrations
def migrate_autocheckin(apps, schema_editor):
CheckinList = apps.get_model("pretixbase", "CheckinList")
AutoCheckinRule = apps.get_model("autocheckin", "AutoCheckinRule")
for cl in CheckinList.objects.filter(auto_checkin_sales_channels__isnull=False).select_related("event", "event__organizer"):
sales_channels = cl.auto_checkin_sales_channels.all()
all_sales_channels = cl.event.organizer.sales_channels.all()
if "pretix.plugins.autocheckin" not in cl.event.plugins:
cl.event.plugins = cl.event.plugins + ",pretix.plugins.autocheckin"
cl.event.save()
r = AutoCheckinRule.objects.get_or_create(
list=cl,
event=cl.event,
all_products=True,
all_payment_methods=True,
defaults=dict(
mode="placed",
all_sales_channels=len(sales_channels) == len(all_sales_channels),
)
)[0]
if len(sales_channels) != len(all_sales_channels):
r.limit_sales_channels.set(sales_channels)
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0272_printlog"),
("autocheckin", "0001_initial"),
]
operations = [
migrations.RunPython(
migrate_autocheckin,
migrations.RunPython.noop,
),
migrations.RemoveField(
model_name="checkinlist",
name="auto_checkin_sales_channels",
),
]
+7 -11
View File
@@ -99,14 +99,6 @@ class CheckinList(LoggedModel):
verbose_name=_('Automatically check out everyone at'),
null=True, blank=True
)
auto_checkin_sales_channels = models.ManyToManyField(
"SalesChannel",
verbose_name=_('Sales channels to automatically check in'),
help_text=_('This option is deprecated and will be removed in the next months. As a replacement, our new plugin '
'"Auto check-in" can be used. When we remove this option, we will automatically migrate your event '
'to use the new plugin.'),
blank=True,
)
rules = models.JSONField(default=dict, blank=True)
objects = ScopedManager(organizer='event__organizer')
@@ -141,7 +133,7 @@ class CheckinList(LoggedModel):
return self.positions_query(ignore_status=False)
@scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None):
def _filter_positions_inside(self, qs, at_time=None):
if at_time is None:
c_q = []
else:
@@ -149,7 +141,7 @@ class CheckinList(LoggedModel):
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
# Use a simple approach that works on all databases
qs = self.positions_query(ignore_status=ignore_status).annotate(
qs = qs.annotate(
last_entry=Subquery(
Checkin.objects.filter(
*c_q,
@@ -202,7 +194,7 @@ class CheckinList(LoggedModel):
.values("position_id", "type", "datetime", "cnt_exists_after")
.query.sql_with_params()
)
return self.positions_query(ignore_status=ignore_status).filter(
return qs.filter(
pk__in=RawSQL(
f"""
SELECT "position_id"
@@ -214,6 +206,10 @@ class CheckinList(LoggedModel):
)
)
@scopes_disabled()
def positions_inside_query(self, ignore_status=False, at_time=None):
return self._filter_positions_inside(self.positions_query(ignore_status=ignore_status), at_time=at_time)
@property
def positions_inside(self):
return self.positions_inside_query(None)
+1 -4
View File
@@ -1024,10 +1024,9 @@ class Event(EventMixin, LoggedModel):
checkin_list_map = {}
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related(
'limit_products', 'auto_checkin_sales_channels'
'limit_products'
):
items = list(cl.limit_products.all())
auto_checkin_sales_channels = list(cl.auto_checkin_sales_channels.all())
checkin_list_map[cl.pk] = cl
cl.pk = None
cl._prefetched_objects_cache = {}
@@ -1039,8 +1038,6 @@ class Event(EventMixin, LoggedModel):
cl.log_action('pretix.object.cloned')
for i in items:
cl.limit_products.add(item_map[i.pk])
if auto_checkin_sales_channels:
cl.auto_checkin_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in auto_checkin_sales_channels]))
if other.seating_plan:
if other.seating_plan.organizer_id == self.organizer_id:
+1 -2
View File
@@ -1118,13 +1118,12 @@ class ItemVariation(models.Model):
:param original_price: The item's "original" price. Will not be used for any calculations, will just be shown.
:type original_price: decimal.Decimal
:param require_approval: If set to ``True``, orders containing this variation can only be processed and paid after
approval by an administrator
approval by an administrator
:type require_approval: bool
:param all_sales_channels: A flag indicating that this variation is available on all channels and limit_sales_channels will be ignored.
:type all_sales_channels: bool
:param limit_sales_channels: A list of sales channel identifiers, that this variation is available for sale on.
:type limit_sales_channels: list
"""
item = models.ForeignKey(
Item,
+68
View File
@@ -3391,6 +3391,74 @@ class BlockedTicketSecret(models.Model):
unique_together = (('event', 'secret'),)
class PrintLog(models.Model):
"""
A print log object is created when a ticket or badge is printed with our apps.
"""
TYPE_BADGE = 'badge'
TYPE_TICKET = 'ticket'
TYPE_CERTIFICATE = 'certificate'
TYPE_OTHER = 'other'
PRINT_TYPES = (
(TYPE_BADGE, _('Badge')),
(TYPE_TICKET, _('Ticket')),
(TYPE_CERTIFICATE, _('Certificate')),
(TYPE_OTHER, _('Other')),
)
position = models.ForeignKey(
'pretixbase.OrderPosition',
related_name='print_logs',
on_delete=models.CASCADE,
)
successful = models.BooleanField(
default=True,
)
# Datetime of checkin, might be different from created if past scans are uploaded
datetime = models.DateTimeField(default=now)
# Datetime of creation on server
created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
# Who printed?
device = models.ForeignKey('Device', related_name='print_logs', null=True, blank=True, on_delete=models.PROTECT)
user = models.ForeignKey('User', related_name='print_logs', null=True, blank=True, on_delete=models.PROTECT)
api_token = models.ForeignKey('TeamAPIToken', null=True, blank=True, on_delete=models.PROTECT)
oauth_application = models.ForeignKey('pretixapi.OAuthApplication', null=True, blank=True, on_delete=models.PROTECT)
# Source = Tag field with undefined values, e.g. name of app ("pretixscan")
source = models.CharField(max_length=255)
# Type = Type of object printed ("badge", "ticket")
type = models.CharField(max_length=255, choices=PRINT_TYPES)
info = models.JSONField(default=dict)
objects = ScopedManager(organizer='position__order__event__organizer')
class Meta:
ordering = (('-datetime'),)
def __repr__(self):
return "<PrintLog: pos {} at {} from {}>".format(
self.position, self.datetime, self.source
)
def save(self, **kwargs):
super().save(**kwargs)
if self.position:
self.position.order.touch()
def delete(self, **kwargs):
super().delete(**kwargs)
self.position.order.touch()
@property
def is_late_upload(self):
return self.created and abs(self.created - self.datetime) > timedelta(minutes=2)
@receiver(post_delete, sender=CachedTicket)
def cachedticket_delete(sender, instance, **kwargs):
if instance.file:
+24
View File
@@ -53,6 +53,30 @@ class SeatingPlanLayoutValidator:
e = str(e).replace('%', '%%')
raise ValidationError(_('Your layout file is not a valid seating plan. Error message: {}').format(e))
try:
seat_guids = set()
for z in val["zones"]:
for r in z["rows"]:
for s in r["seats"]:
if not s.get("seat_guid"):
raise ValidationError(
_("Seat with zone {zone}, row {row}, and number {number} has no seat ID.").format(
zone=z["name"],
row=r["row_number"],
number=s["seat_number"],
)
)
elif s["seat_guid"] in seat_guids:
raise ValidationError(
_("Multiple seats have the same ID: {id}").format(
id=s["seat_guid"],
)
)
seat_guids.add(s["seat_guid"])
except ValidationError as e:
raise ValidationError(_('Your layout file is not a valid seating plan. Error message: {}').format(", ".join(e.message for e in e.error_list)))
class SeatingPlan(LoggedModel):
"""
+3
View File
@@ -343,11 +343,13 @@ class CartManager:
err = error_messages['some_subevent_not_started']
cp.addons.all().delete()
cp.delete()
continue
if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end:
err = error_messages['some_subevent_ended']
cp.addons.all().delete()
cp.delete()
continue
if cp.subevent:
tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
@@ -360,6 +362,7 @@ class CartManager:
err = error_messages['some_subevent_ended']
cp.addons.all().delete()
cp.delete()
continue
return err
def _update_subevents_cache(self, se_ids: List[int]):
+1 -18
View File
@@ -57,7 +57,7 @@ from pretix.base.models import (
Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order,
OrderPosition, QuestionOption,
)
from pretix.base.signals import checkin_created, order_placed, periodic_task
from pretix.base.signals import checkin_created, periodic_task
from pretix.helpers import OF_SELF
from pretix.helpers.jsonlogic import Logic
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
@@ -1154,23 +1154,6 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
)
@receiver(order_placed, dispatch_uid="legacy_autocheckin_order_placed")
def order_placed(sender, **kwargs):
order = kwargs['order']
event = sender
cls = list(event.checkin_lists.filter(auto_checkin_sales_channels=order.sales_channel).prefetch_related(
'limit_products'))
if not cls:
return
for op in order.positions.all():
for cl in cls:
if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}:
if not cl.subevent_id or cl.subevent_id == op.subevent_id:
ci = Checkin.objects.create(position=op, list=cl, auto_checked_in=True, type=Checkin.TYPE_ENTRY)
checkin_created.send(event, checkin=ci)
@receiver(periodic_task, dispatch_uid="autocheckout_exit_all")
@scopes_disabled()
def process_exit_all(sender, **kwargs):
+1 -1
View File
@@ -367,7 +367,7 @@ validate_cart_addons = EventPluginSignal()
Arguments: ``addons``, ``base_position``, ``iao``
This signal is sent when a user tries to select a combination of addons. In contrast to
``validate_cart``, this is executed before the cart is actually modified. You are passed
``validate_cart``, this is executed before the cart is actually modified. You are passed
an argument ``addons`` containing a dict of ``(item, variation or None) count`` tuples as well
as the ``ItemAddOn`` object as the argument ``iao`` and the base cart position as
``base_position``.
+1 -9
View File
@@ -33,9 +33,7 @@ from django_scopes.forms import (
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import Gate
from pretix.base.models.checkin import Checkin, CheckinList
from pretix.control.forms import (
ItemMultipleChoiceField, SalesChannelCheckboxSelectMultiple,
)
from pretix.control.forms import ItemMultipleChoiceField
from pretix.control.forms.widgets import Select2
@@ -67,10 +65,6 @@ class CheckinListForm(forms.ModelForm):
kwargs.pop('locales', None)
super().__init__(**kwargs)
self.fields['limit_products'].queryset = self.event.items.all()
self.fields['auto_checkin_sales_channels'].queryset = self.event.organizer.sales_channels.all()
self.fields['auto_checkin_sales_channels'].widget = SalesChannelCheckboxSelectMultiple(
self.event, choices=self.fields['auto_checkin_sales_channels'].widget.choices
)
if not self.event.organizer.gates.exists():
del self.fields['gates']
@@ -102,7 +96,6 @@ class CheckinListForm(forms.ModelForm):
'limit_products',
'subevent',
'include_pending',
'auto_checkin_sales_channels',
'allow_multiple_entries',
'allow_entry_after_exit',
'rules',
@@ -125,7 +118,6 @@ class CheckinListForm(forms.ModelForm):
'limit_products': ItemMultipleChoiceField,
'gates': SafeModelMultipleChoiceField,
'subevent': SafeModelChoiceField,
'auto_checkin_sales_channels': SafeModelMultipleChoiceField,
'exit_all_at': NextTimeField,
}
+1 -1
View File
@@ -1967,7 +1967,7 @@ class CheckinListAttendeeFilterForm(FilterForm):
if s == '1':
qs = qs.filter(last_entry__isnull=False)
elif s == '2':
qs = qs.filter(pk__in=self.list.positions_inside.values_list('pk'))
qs = self.list._filter_positions_inside(qs)
elif s == '3':
qs = qs.filter(last_entry__isnull=False).filter(
Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry'))
+43
View File
@@ -609,6 +609,49 @@ class OrderFeeChangeForm(forms.Form):
change_decimal_field(self.fields['value'], instance.order.event.currency)
class OrderFeeAddForm(forms.Form):
fee_type = forms.ChoiceField(choices=OrderFee.FEE_TYPES)
value = forms.DecimalField(
max_digits=13, decimal_places=2,
localize=True,
label=_('Price'),
help_text=_("including all taxes"),
)
tax_rule = forms.ModelChoiceField(
TaxRule.objects.none(),
required=False,
)
description = forms.CharField(required=False)
def __init__(self, *args, **kwargs):
order = kwargs.pop('order')
super().__init__(*args, **kwargs)
self.fields['tax_rule'].queryset = order.event.tax_rules.all()
change_decimal_field(self.fields['value'], order.event.currency)
class OrderFeeAddFormset(forms.BaseFormSet):
def __init__(self, *args, **kwargs):
self.order = kwargs.pop('order', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['order'] = self.order
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
use_required_attribute=False,
order=self.order,
)
self.add_fields(form, None)
return form
class OrderContactForm(forms.ModelForm):
regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'),
help_text=_('Regenerates the order and ticket secrets. You will '
+11
View File
@@ -51,6 +51,7 @@ from pretix.base.models import (
Checkin, CheckinList, Event, ItemVariation, LogEntry, OrderPosition,
TaxRule,
)
from pretix.base.models.orders import PrintLog
from pretix.base.signals import logentry_display, orderposition_blocked_display
from pretix.base.templatetags.money import money_filter
@@ -639,6 +640,16 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if sender and logentry.action_type.startswith('pretix.event.checkin'):
return _display_checkin(sender, logentry)
if logentry.action_type == 'pretix.event.order.print':
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
posid=data.get('positionid'),
datetime=date_format(
dateutil.parser.parse(data["datetime"]).astimezone(sender.timezone),
"SHORT_DATETIME_FORMAT"
),
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
)
if logentry.action_type == 'pretix.control.views.checkin':
# deprecated
dt = dateutil.parser.parse(data.get('datetime'))
@@ -67,7 +67,6 @@
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
{% bootstrap_field form.addon_match layout="control" %}
{% bootstrap_field form.exit_all_at layout="control" %}
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
{% if form.gates %}
{% bootstrap_field form.gates layout="control" %}
{% endif %}
@@ -101,7 +101,6 @@
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
</th>
{% endif %}
<th class="iconcol">{% trans "Automated check-in" %}</th>
<th>{% trans "Products" %}</th>
<th class="action-col-2"></th>
</tr>
@@ -137,17 +136,6 @@
</td>
{% endif %}
{% endif %}
<td>
{% for channel in cl.auto_checkin_sales_channels.all %}
{% if "." in channel.icon %}
<img src="{% static channel.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ channel.label }}">
{% else %}
<span class="fa fa-{{ channel.icon }} text-muted"
data-toggle="tooltip" title="{{ channel.label }}"></span>
{% endif %}
{% endfor %}
</td>
<td>
{% if cl.all_products %}
<em>{% trans "All" %}</em>
@@ -12,10 +12,22 @@
{% endif %}
{% if object.id and not object.quotas.exists %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
Please note that your product will <strong>not</strong> be available for sale until you have added your
item to an existing or newly created quota.
{% endblocktrans %}
<div class="row">
<div class="col-lg-8">
{% blocktrans trimmed %}
Please note that your product will <strong>not</strong> be available for sale until you have added your
item to an existing or newly created quota.
{% endblocktrans %}
</div>
<div class="col-lg-4 text-right">
<a class="btn btn-default btn-sm" href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}">
<i class="fa fa-wrench"></i> {% trans "Manage quotas" %}
</a>
<a class="btn btn-default btn-sm" href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?{% if object.has_variations %}{% for var in object.variations.all %}product={{ object.pk }}-{{ var.pk }}&{% endfor %}{% else %}product={{ object.pk }}{% endif %}">
<i class="fa fa-plus"></i> {% trans "Create a new quota" %}
</a>
</div>
</div>
</div>
{% elif object.pk and not object.is_available_by_time %}
<div class="alert alert-warning">
@@ -67,10 +67,22 @@
<div class="panel-body form-horizontal">
{% if form.instance.pk and not form.instance.quotas.exists %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
Please note that your variation will <strong>not</strong> be available for sale
until you have added it to an existing or newly created quota.
{% endblocktrans %}
<div class="row">
<div class="col-lg-8">
{% blocktrans trimmed %}
Please note that your variation will <strong>not</strong> be available for sale
until you have added it to an existing or newly created quota.
{% endblocktrans %}
</div>
<div class="col-lg-4 text-right">
<a class="btn btn-default btn-xs" href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}">
<i class="fa fa-wrench"></i> {% trans "Manage quotas" %}
</a>
<a class="btn btn-default btn-xs" href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?product={{ form.instance.item.pk }}-{{ form.instance.pk }}">
<i class="fa fa-plus"></i> {% trans "Create a new quota" %}
</a>
</div>
</div>
</div>
{% endif %}
{% bootstrap_form_errors form %}
@@ -16,8 +16,18 @@
{% bootstrap_field form.internal_name layout="control" %}
</div>
{% bootstrap_field form.description layout="control" %}
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-lg-9" %}
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-lg-9" %}
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-md-9" %}
<div class="row" data-display-dependency="#id_category_type_2">
<div class="col-md-offset-3 col-md-9">
<div class="alert alert-info">
{% blocktrans trimmed %}
Please note that cross-selling categories are intended as a marketing feature and are not
suitable for strictly ensuring that products are only available in certain combinations.
{% endblocktrans %}
</div>
</div>
</div>
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-md-9" %}
{% bootstrap_field form.cross_selling_match_products layout="control" %}
</fieldset>
</div>
@@ -296,11 +296,11 @@
{% endfor %}
<div class="formset" data-formset data-formset-prefix="{{ add_formset.prefix }}">
{{ add_formset.management_form }}
{% bootstrap_formset_errors add_formset %}
<div class="formset" data-formset data-formset-prefix="{{ add_position_formset.prefix }}">
{{ add_position_formset.management_form }}
{% bootstrap_formset_errors add_position_formset %}
<div data-formset-body>
{% for add_form in add_formset %}
{% for add_form in add_position_formset %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading">
<h3 class="panel-title">
@@ -351,25 +351,25 @@
</button>
{% trans "Add product" %}
<div class="sr-only">
{{ add_formset.empty_form.id }}
{% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %}
{{ add_position_formset.empty_form.id }}
{% bootstrap_field add_position_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field add_formset.empty_form.itemvar layout="control" %}
{% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %}
{% if add_formset.empty_form.addon_to %}
{% bootstrap_field add_formset.empty_form.addon_to layout="control" %}
{% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %}
{% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %}
{% if add_position_formset.empty_form.addon_to %}
{% bootstrap_field add_position_formset.empty_form.addon_to layout="control" %}
{% endif %}
{% if add_formset.empty_form.subevent %}
{% bootstrap_field add_formset.empty_form.subevent layout="control" %}
{% if add_position_formset.empty_form.subevent %}
{% bootstrap_field add_position_formset.empty_form.subevent layout="control" %}
{% endif %}
{% if add_formset.empty_form.used_membership %}
{% bootstrap_field add_formset.empty_form.used_membership layout="control" %}
{% if add_position_formset.empty_form.used_membership %}
{% bootstrap_field add_position_formset.empty_form.used_membership layout="control" %}
{% endif %}
{% bootstrap_field add_formset.empty_form.seat layout="control" %}
{% bootstrap_field add_position_formset.empty_form.seat layout="control" %}
</div>
</div>
</div>
@@ -431,13 +431,77 @@
{% bootstrap_field fee.form.operation_cancel layout='inline' %}
{% if fee.fee_type == "payment" %}
<em class="text-danger">
{% trans "Manually modifying payment fees is discouraged since they might automatically be on subsequent order changes or when choosing a different payment method." %}
{% trans "Manually modifying payment fees is discouraged since they might automatically be updated on subsequent order changes or when choosing a different payment method." %}
</em>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<div class="formset" data-formset data-formset-prefix="{{ add_fee_formset.prefix }}">
{{ add_fee_formset.management_form }}
{% bootstrap_formset_errors add_fee_formset %}
<div data-formset-body>
{% for add_form in add_fee_formset %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"
data-formset-delete-button>
<i class="fa fa-trash"></i>
</button>
{% trans "Add fee" %}
<div class="sr-only">
{{ add_form.id }}
{% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %}
</div>
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field add_form.fee_type layout='control' %}
{% bootstrap_field add_form.value addon_after=request.event.currency layout='control' %}
{% bootstrap_field add_form.tax_rule layout='control' %}
{% bootstrap_field add_form.description layout='control' %}
</div>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"
data-formset-delete-button>
<i class="fa fa-trash"></i>
</button>
{% trans "Add fee" %}
<div class="sr-only">
{{ add_fee_formset.empty_form.id }}
{% bootstrap_field add_fee_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field add_fee_formset.empty_form.fee_type layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.value addon_after=request.event.currency layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.tax_rule layout='control' %}
{% bootstrap_field add_fee_formset.empty_form.description layout='control' %}
</div>
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-primary" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add fee" %}</button>
</p>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
@@ -198,7 +198,7 @@
</a>
{% endif %}
</dd>
{% if order.status == "n" %}
{% if order.status == "n" and not order.require_approval %}
<dt>{% trans "Expiry date" %}</dt>
<dd>
{{ order.expires|date:"SHORT_DATETIME_FORMAT" }}
+1 -1
View File
@@ -299,7 +299,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
def get_queryset(self):
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related(
"limit_products", "auto_checkin_sales_channels"
"limit_products",
)
if self.filter_form.is_valid():
+5 -2
View File
@@ -257,7 +257,7 @@ class CategoryUpdate(EventPermissionRequiredMixin, UpdateView):
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed():
self.object.log_action(
'pretix.event.category.reordered', user=self.request.user, data={
'pretix.event.category.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
@@ -920,16 +920,19 @@ class QuotaCreate(EventPermissionRequiredMixin, CreateView):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.setdefault('initial', {})
if self.copy_from:
i = modelcopy(self.copy_from)
i.pk = None
kwargs['instance'] = i
kwargs.setdefault('initial', {})
kwargs['initial']['itemvars'] = [str(i.pk) for i in self.copy_from.items.all()] + [
'{}-{}'.format(v.item_id, v.pk) for v in self.copy_from.variations.all()
]
else:
kwargs['instance'] = Quota(event=self.request.event)
if 'product' in self.request.GET:
kwargs['initial']['itemvars'] = self.request.GET.getlist('product')
return kwargs
def form_invalid(self, form):
+55 -14
View File
@@ -122,10 +122,11 @@ from pretix.control.forms.filter import (
)
from pretix.control.forms.orders import (
CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm,
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeChangeForm,
OrderLocaleForm, OrderMailForm, OrderPositionAddForm,
OrderPositionAddFormset, OrderPositionChangeForm, OrderPositionMailForm,
OrderRefundForm, OtherOperationsForm, ReactivateOrderForm,
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeAddForm,
OrderFeeAddFormset, OrderFeeChangeForm, OrderLocaleForm, OrderMailForm,
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
OrderPositionMailForm, OrderRefundForm, OtherOperationsForm,
ReactivateOrderForm,
)
from pretix.control.forms.rrule import RRuleForm
from pretix.control.permissions import EventPermissionRequiredMixin
@@ -1874,18 +1875,30 @@ class OrderChange(OrderView):
data=self.request.POST if self.request.method == "POST" else None)
@cached_property
def add_formset(self):
def add_position_formset(self):
ff = formset_factory(
OrderPositionAddForm, formset=OrderPositionAddFormset,
can_order=False, can_delete=True, extra=0
)
return ff(
prefix='add',
prefix='add_position',
order=self.order,
items=self.items,
data=self.request.POST if self.request.method == "POST" else None
)
@cached_property
def add_fee_formset(self):
ff = formset_factory(
OrderFeeAddForm, formset=OrderFeeAddFormset,
can_order=False, can_delete=True, extra=0
)
return ff(
prefix='add_fee',
order=self.order,
data=self.request.POST if self.request.method == "POST" else None
)
@cached_property
def items(self):
return self.request.event.items.prefetch_related('variations', 'tax_rule').all()
@@ -1914,7 +1927,8 @@ class OrderChange(OrderView):
ctx = super().get_context_data(**kwargs)
ctx['positions'] = self.positions
ctx['fees'] = self.fees
ctx['add_formset'] = self.add_formset
ctx['add_position_formset'] = self.add_position_formset
ctx['add_fee_formset'] = self.add_fee_formset
ctx['other_form'] = self.other_form
ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list
return ctx
@@ -1929,12 +1943,35 @@ class OrderChange(OrderView):
)
return True
def _process_add(self, ocm):
if not self.add_formset.is_valid():
def _process_add_fees(self, ocm):
if not self.add_fee_formset.is_valid():
return False
else:
for f in self.add_formset.forms:
if f in self.add_formset.deleted_forms or not f.has_changed():
for f in self.add_fee_formset.forms:
if f in self.add_fee_formset.deleted_forms or not f.has_changed():
continue
f = OrderFee(
fee_type=f.cleaned_data['fee_type'],
value=f.cleaned_data['value'],
order=ocm.order,
tax_rule=f.cleaned_data['tax_rule'],
description=f.cleaned_data['description'],
)
f._calculate_tax()
try:
ocm.add_fee(f)
except OrderError as e:
f.custom_error = str(e)
return False
return True
def _process_add_positions(self, ocm):
if not self.add_position_formset.is_valid():
return False
else:
for f in self.add_position_formset.forms:
if f in self.add_position_formset.deleted_forms or not f.has_changed():
continue
if '-' in f.cleaned_data['itemvar']:
@@ -1959,7 +1996,7 @@ class OrderChange(OrderView):
return False
return True
def _process_fees(self, ocm):
def _process_change_fees(self, ocm):
for f in self.fees:
if not f.form.is_valid():
return False
@@ -1980,7 +2017,7 @@ class OrderChange(OrderView):
return False
return True
def _process_change(self, ocm):
def _process_change_positions(self, ocm):
for p in self.positions:
if not p.form.is_valid():
return False
@@ -2061,7 +2098,11 @@ class OrderChange(OrderView):
notify=notify,
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
)
form_valid = self._process_add(ocm) and self._process_fees(ocm) and self._process_change(ocm) and self._process_other(ocm)
form_valid = (self._process_add_fees(ocm) and
self._process_add_positions(ocm) and
self._process_change_fees(ocm) and
self._process_change_positions(ocm) and
self._process_other(ocm))
if not form_valid:
messages.error(self.request, _('An error occurred. Please see the details below.'))
+2 -2
View File
@@ -156,7 +156,7 @@ def event_list(request):
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate(
order_from=Coalesce('min_from', 'date_from'),
).order_by('-order_from')
).order_by('-order_from', 'slug')
total = qs.count()
pagesize = 20
@@ -318,7 +318,7 @@ def nav_context_list(request):
max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from'))
).annotate(
order_from=Coalesce('min_from', 'date_from'),
).order_by('-order_from')
).order_by('-order_from', 'slug')
if request.user.has_active_staff_session(request.session.session_key):
qs_orga = Organizer.objects.all()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:14+0000\n"
"POT-Creation-Date: 2024-10-29 08:54+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -10
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
"PO-Revision-Date: 2024-10-22 17:00+0000\n"
"PO-Revision-Date: 2024-11-01 19:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
"pretix-js/es/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -326,13 +326,13 @@ msgstr "Tickets válidos"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
msgid "Currently inside"
msgstr "Actualmente adentro"
msgstr "Actualmente en el interior"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
#: pretix/static/pretixcontrol/js/ui/question.js:137
#: pretix/static/pretixpresale/js/ui/questions.js:270
msgid "Yes"
msgstr "Sí"
msgstr "Si"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:72
#: pretix/static/pretixcontrol/js/ui/question.js:138
@@ -570,10 +570,8 @@ msgid "Group of objects"
msgstr "Grupo de objetos"
#: pretix/static/pretixcontrol/js/ui/editor.js:899
#, fuzzy
#| msgid "Text object"
msgid "Text object (deprecated)"
msgstr "Objeto de texto"
msgstr "Objeto texto (obsoleto)"
#: pretix/static/pretixcontrol/js/ui/editor.js:901
msgid "Text box"
@@ -704,12 +702,12 @@ msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Los elementos en su carrito de compras ya no se encuentran reservados. "
"Puedes seguir añadiendo más productos mientras estén disponibles."
"Los elementos en su cesta de compras ya no se encuentran reservados. Puedes "
"seguir añadiendo más productos mientras estén disponibles."
#: pretix/static/pretixpresale/js/ui/cart.js:45
msgid "Cart expired"
msgstr "El carrito de compras ha expirado"
msgstr "La cesta de compra ha expirado"
#: pretix/static/pretixpresale/js/ui/cart.js:50
msgid "The items in your cart are reserved for you for one minute."
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
"PO-Revision-Date: 2024-06-12 03:00+0000\n"
"Last-Translator: simonD <simon.dalvy@gmail.com>\n"
"PO-Revision-Date: 2024-11-01 02:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n"
"Language: fr\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.5.5\n"
"X-Generator: Weblate 5.8.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -293,7 +293,7 @@ msgstr "Entrée non autorisée"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
msgid "Ticket code revoked/changed"
msgstr "Code du billet révoqué/modifié"
msgstr "Code ticket révoqué/modifié"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
msgid "Ticket blocked"
@@ -452,7 +452,6 @@ msgid "Product variation"
msgstr "Variation du produit"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
#, fuzzy
msgid "Gate"
msgstr "Pont"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More