mirror of
https://github.com/pretix/pretix.git
synced 2026-06-24 03:26:14 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91926cffd7 | |||
| d14dc4c5ff | |||
| 5db1a5b8af | |||
| 86a51afe9e | |||
| ea9c85a1b4 | |||
| 226ff9b044 | |||
| d8991d8138 | |||
| 83642ec9f3 | |||
| 9f0ce28ce4 | |||
| 28722fecbd | |||
| 10a5d4ac68 | |||
| ba36f6d2ce | |||
| 1e301da26c | |||
| d0307b9936 | |||
| c083ce904a | |||
| 694b915d89 | |||
| ea928ea7d4 | |||
| 36d49fbd77 | |||
| f0cb451c34 | |||
| 2c7fcd0599 | |||
| 034722fa39 | |||
| 5a6870fce1 | |||
| de28425993 | |||
| f3eb0d2dba | |||
| 0630e05d50 | |||
| f868507670 | |||
| 04032078c1 | |||
| 8af2714a04 | |||
| c4b9cc4143 | |||
| 8c132d8342 | |||
| 8e63fafc62 | |||
| 63ebe16fd3 | |||
| 775fdd1ccb | |||
| 784577d86f | |||
| 07d27e66d1 | |||
| b404316dfd | |||
| edf97a13cd | |||
| c384bc2e7a | |||
| f16034d0cc |
+1
-2
@@ -10,8 +10,7 @@ tests:
|
||||
- cd src
|
||||
- python manage.py check
|
||||
- make all compress
|
||||
- playwright install
|
||||
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --maxfail=100
|
||||
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --ignore=tests/e2e --maxfail=100
|
||||
except:
|
||||
- '/^v.*$/'
|
||||
pypi:
|
||||
|
||||
+1
-2
@@ -57,8 +57,7 @@ COPY vite.config.ts /pretix/vite.config.ts
|
||||
|
||||
RUN pip3 install -U \
|
||||
pip \
|
||||
setuptools \
|
||||
wheel && \
|
||||
setuptools && \
|
||||
cd /pretix && \
|
||||
PRETIX_DOCKER_BUILD=TRUE pip3 install \
|
||||
-e ".[memcached]" \
|
||||
|
||||
@@ -46,12 +46,14 @@ Checking a ticket in
|
||||
this request twice with the same nonce, the second request will also succeed but will always
|
||||
create only one check-in object even when the previous request was successful as well. This
|
||||
allows for a certain level of idempotency and enables you to re-try after a connection failure.
|
||||
:<json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
|
||||
:<json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
|
||||
:<json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
|
||||
order) when building texts (currently only the ``reason_explanation`` response field).
|
||||
Defaults to ``false`` in which case the server will determine the language (currently
|
||||
the event default language, might change in the future with support for the
|
||||
``Accept-Language`` header).
|
||||
:>json string status: ``"ok"``, ``"incomplete"``, or ``"error"``
|
||||
:>json string status: ``"ok"``, ``"incomplete"``, ``"exchange"``, or ``"error"``
|
||||
:>json string reason: Reason code, only set on status ``"error"``, see below for possible values.
|
||||
:>json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null.
|
||||
:>json object position: Copy of the matching order position (if any was found). The contents are the same as the
|
||||
@@ -67,6 +69,8 @@ Checking a ticket in
|
||||
:>json object list: Excerpt of information about the matching :ref:`check-in list <rest-checkinlists>` (if any was found),
|
||||
including the attributes ``id``, ``name``, ``event``, ``subevent``, and ``include_pending``.
|
||||
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
|
||||
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
|
||||
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -224,6 +228,9 @@ Checking a ticket in
|
||||
* ``ambiguous`` - Multiple tickets match scan, rejected.
|
||||
* ``revoked`` - Ticket code has been revoked.
|
||||
* ``unapproved`` - Order has not yet been approved.
|
||||
* ``already_exchanged`` - Ticket already has been exchanged for a reusable medium that must now be used for check-in.
|
||||
* ``medium_invalid`` - Reusable medium identifier given was not found or is not valid.
|
||||
* ``medium_exists`` - Reusable medium identifier already exists, but expected to be new.
|
||||
* ``error`` - Internal error.
|
||||
|
||||
In case of reason ``rules`` and ``invalid_time``, there might be an additional response field ``reason_explanation``
|
||||
|
||||
@@ -602,7 +602,8 @@ Order position endpoints
|
||||
|
||||
We no longer recommend using this API if you're building a ticket scanning application, as it has a few design
|
||||
flaws that can lead to `security issues`_ or compatibility issues due to barcode content characters that are not
|
||||
URL-safe. We recommend to use our new :ref:`check-in API <rest-checkin>` instead.
|
||||
URL-safe. We recommend to use our new :ref:`check-in API <rest-checkin>` instead. Advanced features like medium
|
||||
exchange are only supported on the new API.
|
||||
|
||||
:query boolean untrusted_input: If set to true, the lookup parameter is **always** interpreted as a ``secret``, never
|
||||
as an ``id``. This should be always set if you are passing through untrusted, scanned
|
||||
@@ -741,6 +742,9 @@ Order position endpoints
|
||||
* ``ambiguous`` - Multiple tickets match scan, rejected.
|
||||
* ``revoked`` - Ticket code has been revoked.
|
||||
* ``unapproved`` - Order has not yet been approved.
|
||||
* ``already_exchanged`` - Ticket already has been exchanged for a reusable medium that must now be used for check-in.
|
||||
* ``medium_invalid`` - Reusable medium identifier given was not found and could not be automatically created.
|
||||
* ``medium_exists`` - Reusable medium identifier already exists, but expected to be new.
|
||||
|
||||
In case of reason ``rules`` or ``invalid_time``, there might be an additional response field ``reason_explanation``
|
||||
with a human-readable description of the violated rules. However, that field can also be missing or be ``null``.
|
||||
|
||||
@@ -131,7 +131,7 @@ allow_waitinglist boolean If ``false``,
|
||||
product when it is sold out.
|
||||
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
||||
media_policy string Policy on how to handle reusable media (experimental feature).
|
||||
Possible values are ``null``, ``"new"``, ``"reuse"``, and ``"reuse_or_new"``.
|
||||
Possible values are ``null``, ``"new"``, ``"reuse"``, ``"reuse_or_new"``, ``"append"``, and ``"append_or_new"``.
|
||||
media_type string Type of reusable media to work on (experimental feature). See :ref:`rest-reusablemedia` for possible choices.
|
||||
show_quota_left boolean Publicly show how many tickets are still available.
|
||||
If this is ``null``, the event default is used.
|
||||
|
||||
@@ -1069,8 +1069,7 @@ Creating orders
|
||||
* ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
|
||||
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
|
||||
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
|
||||
* ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID)
|
||||
* ``add_to_reusable_medium`` (optional, causes the new ticket to be added to the given reusable medium, identified by its ID)
|
||||
* ``use_reusable_medium`` (optional, causes the new ticket to be connected to the given reusable medium, identified by its ID)
|
||||
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
|
||||
* ``answers``
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ is a python method that emulates a behavior similar to ``reverse``:
|
||||
|
||||
If you need to communicate the URL externally, you can use a different method to ensure that it is always an absolute URL:
|
||||
|
||||
.. autofunction:: pretix.multidomain.urlreverse.build_absolute_uri
|
||||
.. autofunction:: pretix.multidomain.urlreverse.eventreverse_absolute
|
||||
|
||||
In addition, there is a template tag that works similar to ``url`` but takes an event or organizer object
|
||||
as its first argument and can be used like this::
|
||||
|
||||
Generated
+109
-91
@@ -370,14 +370,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
|
||||
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
"@tybys/wasm-util": "^0.10.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -427,9 +427,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.129.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
|
||||
"integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -758,9 +758,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -775,9 +775,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -792,9 +792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -809,9 +809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -826,9 +826,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
|
||||
"integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -843,13 +843,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -860,13 +863,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
|
||||
"integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -877,13 +883,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -894,13 +903,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -911,13 +923,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -928,13 +943,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
|
||||
"integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -945,9 +963,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -962,9 +980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
|
||||
"integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -981,9 +999,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
|
||||
"integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -998,9 +1016,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
|
||||
"integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3158,9 +3176,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3334,9 +3352,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -3353,7 +3371,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@@ -3610,14 +3628,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
|
||||
"integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.129.0",
|
||||
"@rolldown/pluginutils": "1.0.0"
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -3626,27 +3644,27 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0"
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
|
||||
"integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4325,9 +4343,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4465,17 +4483,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
|
||||
"integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.14",
|
||||
"rolldown": "1.0.0",
|
||||
"tinyglobby": "^0.2.16"
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
||||
+7
-9
@@ -29,18 +29,18 @@ classifiers = [
|
||||
dependencies = [
|
||||
"arabic-reshaper==3.0.1", # Support for Arabic in reportlab
|
||||
"babel",
|
||||
"BeautifulSoup4==4.14.*",
|
||||
"bleach==6.3.*",
|
||||
"BeautifulSoup4==4.15.*",
|
||||
"bleach==6.4.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=48.0.0",
|
||||
"cryptography>=49.0.0",
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=3.0.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==5.2.*",
|
||||
"django-bootstrap3==26.1",
|
||||
"django-compressor==4.6.0",
|
||||
"django-countries==8.2.*",
|
||||
"django-countries==9.0.*",
|
||||
"django-filter==25.1",
|
||||
"django-formset-js-improved==0.5.0.5",
|
||||
"django-formtools==2.6.1",
|
||||
@@ -93,7 +93,7 @@ dependencies = [
|
||||
"redis==7.4.*",
|
||||
"reportlab==4.5.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.61.*",
|
||||
"sentry-sdk==2.62.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
@@ -101,7 +101,7 @@ dependencies = [
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.7.*",
|
||||
"webauthn==2.8.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
@@ -125,7 +125,7 @@ dev = [
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.8.*",
|
||||
"pytest-playwright",
|
||||
"pytest==9.0.*",
|
||||
"pytest==9.1.*",
|
||||
"playwright",
|
||||
"responses",
|
||||
]
|
||||
@@ -139,8 +139,6 @@ build-backend = "backend"
|
||||
backend-path = ["_build"]
|
||||
requires = [
|
||||
"setuptools",
|
||||
"setuptools-rust",
|
||||
"wheel",
|
||||
"importlib_metadata",
|
||||
"tomli",
|
||||
]
|
||||
|
||||
@@ -110,6 +110,8 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
('POST', 'api-v1:checkinrpc.redeem'),
|
||||
('GET', 'api-v1:checkinrpc.search'),
|
||||
('GET', 'api-v1:reusablemedium-list'),
|
||||
('POST', 'api-v1:reusablemedium-lookup'),
|
||||
('PATCH', 'api-v1:reusablemedium-detail')
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -88,11 +88,19 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer):
|
||||
nonce = serializers.CharField(required=False, allow_null=True)
|
||||
datetime = serializers.DateTimeField(required=False, allow_null=True)
|
||||
answers = serializers.JSONField(required=False, allow_null=True)
|
||||
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
|
||||
exchange_medium_identifier = serializers.CharField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
||||
|
||||
def validate(self, attrs):
|
||||
exchange_fields = ["exchange_medium_type", "exchange_medium_identifier"]
|
||||
if any(attrs.get(k) is None for k in exchange_fields) and not all(attrs.get(k) is None for k in exchange_fields):
|
||||
raise ValidationError("If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.")
|
||||
return attrs
|
||||
|
||||
|
||||
class MiniCheckinListSerializer(I18nAwareModelSerializer):
|
||||
event = serializers.SlugRelatedField(slug_field='slug', read_only=True)
|
||||
|
||||
@@ -73,7 +73,7 @@ from pretix.base.settings import (
|
||||
LazyI18nStringList, validate_event_settings,
|
||||
)
|
||||
from pretix.base.signals import api_event_settings_fields
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -173,7 +173,7 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
)
|
||||
|
||||
def get_event_url(self, event):
|
||||
return build_absolute_uri(event, 'presale:event.index')
|
||||
return eventreverse_absolute(event, 'presale:event.index')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@@ -871,6 +871,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'og_image',
|
||||
'name_scheme',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -885,6 +886,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
readonly_fields = [
|
||||
# These are read-only since they are currently only settable on organizers, not events
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -970,6 +972,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'reusable_media_type_nfc_uid',
|
||||
'reusable_media_type_nfc_mf0aes',
|
||||
'reusable_media_type_nfc_mf0aes_random_uid',
|
||||
'reusable_media_usage_enforced',
|
||||
'system_question_order',
|
||||
'tax_rule_payment',
|
||||
'tax_rule_cancellation',
|
||||
|
||||
@@ -76,7 +76,7 @@ from pretix.base.settings import (
|
||||
)
|
||||
from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -757,7 +757,7 @@ class PaymentURLField(serializers.URLField):
|
||||
def to_representation(self, instance: OrderPayment):
|
||||
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||
return None
|
||||
return build_absolute_uri(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||
return eventreverse_absolute(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||
'order': instance.order.code,
|
||||
'secret': instance.order.secret,
|
||||
'payment': instance.pk,
|
||||
@@ -806,7 +806,7 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class OrderURLField(serializers.URLField):
|
||||
def to_representation(self, instance: Order):
|
||||
return build_absolute_uri(instance.event, 'presale:event.order', kwargs={
|
||||
return eventreverse_absolute(instance.event, 'presale:event.order', kwargs={
|
||||
'order': instance.code,
|
||||
'secret': instance.secret,
|
||||
})
|
||||
@@ -1043,15 +1043,13 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
|
||||
use_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(),
|
||||
required=False, allow_null=True)
|
||||
add_to_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(),
|
||||
required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
|
||||
'requested_valid_from', 'use_reusable_medium', 'add_to_reusable_medium', 'discount')
|
||||
'requested_valid_from', 'use_reusable_medium', 'discount')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -1063,8 +1061,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
with scopes_disabled():
|
||||
if 'use_reusable_medium' in self.fields:
|
||||
self.fields['use_reusable_medium'].queryset = ReusableMedium.objects.all()
|
||||
if 'add_to_reusable_medium' in self.fields:
|
||||
self.fields['add_to_reusable_medium'].queryset = ReusableMedium.objects.all()
|
||||
|
||||
def validate_secret(self, secret):
|
||||
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
|
||||
@@ -1080,9 +1076,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
return m
|
||||
|
||||
def validate_add_to_reusable_medium(self, m):
|
||||
return self.validate_use_reusable_medium(m)
|
||||
|
||||
def validate_item(self, item):
|
||||
if item.event != self.context['event']:
|
||||
raise ValidationError(
|
||||
@@ -1157,12 +1150,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
|
||||
)
|
||||
|
||||
if 'use_reusable_medium' in data and 'add_to_reusable_medium' in data:
|
||||
raise ValidationError({
|
||||
'use_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'],
|
||||
'add_to_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'],
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -1602,7 +1589,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
pos_data['attendee_name_parts'] = {
|
||||
'_legacy': attendee_name
|
||||
}
|
||||
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium', 'add_to_reusable_medium')})
|
||||
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium')})
|
||||
if simulate:
|
||||
pos.order = order._wrapped
|
||||
else:
|
||||
@@ -1676,7 +1663,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
for pos_data in positions_data:
|
||||
answers_data = pos_data.pop('answers', [])
|
||||
use_reusable_medium = pos_data.pop('use_reusable_medium', None)
|
||||
add_to_reusable_medium = pos_data.pop('add_to_reusable_medium', None)
|
||||
pos = pos_data['__instance']
|
||||
pos._calculate_tax(invoice_address=ia)
|
||||
|
||||
@@ -1718,14 +1704,17 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
answ.options.add(*options)
|
||||
|
||||
if use_reusable_medium:
|
||||
for op_pk in use_reusable_medium.linked_orderpositions.values_list('pk', flat=True):
|
||||
use_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.removed',
|
||||
data={
|
||||
'linked_orderposition': op_pk,
|
||||
}
|
||||
)
|
||||
use_reusable_medium.linked_orderpositions.set([pos])
|
||||
if pos.item.media_policy not in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
for op_pk in use_reusable_medium.linked_orderpositions.values_list('pk', flat=True):
|
||||
use_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.removed',
|
||||
data={
|
||||
'linked_orderposition': op_pk,
|
||||
}
|
||||
)
|
||||
use_reusable_medium.linked_orderpositions.set([pos])
|
||||
else:
|
||||
use_reusable_medium.linked_orderpositions.add(pos)
|
||||
use_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
data={
|
||||
@@ -1733,15 +1722,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
'linked_orderposition': pos.pk,
|
||||
}
|
||||
)
|
||||
elif add_to_reusable_medium:
|
||||
add_to_reusable_medium.linked_orderpositions.add(pos)
|
||||
add_to_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
data={
|
||||
'by_order': order.code,
|
||||
'linked_orderposition': pos.pk,
|
||||
}
|
||||
)
|
||||
use_reusable_medium.touch()
|
||||
|
||||
if not simulate:
|
||||
for cp in delete_cps:
|
||||
|
||||
@@ -58,8 +58,8 @@ from pretix.helpers.permission_migration import (
|
||||
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_EVENT_MIGRATION,
|
||||
OLD_TO_NEW_ORGANIZER_COMPAT, OLD_TO_NEW_ORGANIZER_MIGRATION,
|
||||
)
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -71,7 +71,7 @@ class OrganizerSerializer(I18nAwareModelSerializer):
|
||||
slug = serializers.CharField(read_only=True)
|
||||
|
||||
def get_organizer_url(self, organizer):
|
||||
return build_absolute_uri(organizer, 'presale:organizer.index')
|
||||
return eventreverse_absolute(organizer, 'presale:organizer.index')
|
||||
|
||||
class Meta:
|
||||
model = Organizer
|
||||
@@ -499,7 +499,7 @@ class TeamInviteSerializer(serializers.ModelSerializer):
|
||||
'user': self,
|
||||
'organizer': self.context['organizer'].name,
|
||||
'team': instance.team.name,
|
||||
'url': build_global_uri('control:auth.invite', kwargs={
|
||||
'url': mainreverse_absolute('control:auth.invite', kwargs={
|
||||
'token': instance.token
|
||||
})
|
||||
},
|
||||
@@ -605,6 +605,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
'cookie_consent_dialog_button_yes',
|
||||
'cookie_consent_dialog_button_no',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
|
||||
@@ -69,8 +69,10 @@ from pretix.base.models import (
|
||||
from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.permissions import AnyPermissionOf
|
||||
from pretix.base.services.checkin import (
|
||||
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
|
||||
CheckInError, RequiredMediaExchangeError, RequiredQuestionsError, SQLLogic,
|
||||
perform_checkin,
|
||||
)
|
||||
from pretix.base.services.media import perform_media_exchange
|
||||
from pretix.base.signals import checkin_annulled
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
@@ -454,7 +456,8 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
||||
|
||||
def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, checkin_type, ignore_unpaid, nonce,
|
||||
untrusted_input, user, auth, expand, pdf_data, request, questions_supported, canceled_supported,
|
||||
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False):
|
||||
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False,
|
||||
exchange_medium_type=None, exchange_medium_identifier=None):
|
||||
if not checkinlists:
|
||||
raise ValidationError('No check-in list passed.')
|
||||
|
||||
@@ -463,6 +466,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
|
||||
device = auth if isinstance(auth, Device) else None
|
||||
gate = gate or (auth.gate if isinstance(auth, Device) else None)
|
||||
medium = None
|
||||
|
||||
context = {
|
||||
'request': request,
|
||||
@@ -522,7 +526,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
# with respecting the force option), or it's a reusable medium (-> proceed with that)
|
||||
if not op_candidates:
|
||||
try:
|
||||
media = ReusableMedium.objects.active().filter(
|
||||
medium = ReusableMedium.objects.active().filter(
|
||||
Exists(ReusableMedium.linked_orderpositions.through.objects.filter(reusablemedium_id=OuterRef('pk')))
|
||||
).get(
|
||||
organizer_id=checkinlists[0].event.organizer_id,
|
||||
@@ -630,7 +634,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
'list': MiniCheckinListSerializer(list_by_event[revoked_matches[0].event_id]).data,
|
||||
}, status=400)
|
||||
else:
|
||||
linked_ops = media.linked_orderpositions.all().select_related("order").prefetch_related("addons")
|
||||
linked_ops = medium.linked_orderpositions.all().select_related("order").prefetch_related("addons")
|
||||
linked_event_ids = {op.order.event_id for op in linked_ops}
|
||||
if not any(event_id in list_by_event for event_id in linked_event_ids):
|
||||
# Medium exists but connected ticket is for the wrong event
|
||||
@@ -661,7 +665,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
op_candidates = []
|
||||
for op in linked_ops:
|
||||
if op.order.event_id in list_by_event:
|
||||
reusable_medium_used = media
|
||||
reusable_medium_used = medium
|
||||
op_candidates.append(op)
|
||||
if list_by_event[op.order.event_id].addon_match:
|
||||
op_candidates += list(op.addons.all())
|
||||
@@ -788,7 +792,10 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
if str(q.pk) in answers_data:
|
||||
try:
|
||||
if q.type == Question.TYPE_FILE:
|
||||
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
|
||||
if answers_data[str(q.pk)]:
|
||||
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
|
||||
else:
|
||||
given_answers[q] = None
|
||||
else:
|
||||
given_answers[q] = q.clean_answer(answers_data[str(q.pk)])
|
||||
except (ValidationError, BaseValidationError):
|
||||
@@ -801,7 +808,14 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
locale = op.order.event.settings.locale
|
||||
with language(locale):
|
||||
try:
|
||||
perform_checkin(
|
||||
if exchange_medium_identifier and medium:
|
||||
# Cannot scan a medium and then request to exchange it
|
||||
raise CheckInError(
|
||||
gettext('You cannot exchange a medium for a medium.'),
|
||||
'error'
|
||||
)
|
||||
|
||||
checkin_args = dict(
|
||||
op=op,
|
||||
clist=list_by_event[op.order.event_id],
|
||||
given_answers=given_answers,
|
||||
@@ -819,7 +833,25 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
from_revoked_secret=from_revoked_secret,
|
||||
simulate=simulate,
|
||||
gate=gate,
|
||||
reusable_medium=medium,
|
||||
)
|
||||
|
||||
if exchange_medium_identifier: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
|
||||
with transaction.atomic():
|
||||
# Do exchange and check-in atomically, i.e. both succeed or both fail
|
||||
medium = perform_media_exchange(
|
||||
organizer=request.organizer,
|
||||
media_type=exchange_medium_type,
|
||||
identifier=exchange_medium_identifier,
|
||||
link_orderposition=op,
|
||||
user=user,
|
||||
auth=auth,
|
||||
)
|
||||
source_type = medium.media_type.identifier
|
||||
checkin_args['reusable_medium'] = medium
|
||||
perform_checkin(**checkin_args)
|
||||
else:
|
||||
perform_checkin(**checkin_args)
|
||||
except RequiredQuestionsError as e:
|
||||
return Response({
|
||||
'status': 'incomplete',
|
||||
@@ -831,6 +863,18 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
],
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
}, status=400)
|
||||
except RequiredMediaExchangeError as e:
|
||||
return Response({
|
||||
'status': 'exchange',
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'checkin_texts': op.checkin_texts,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'media_policy': e.media_policy,
|
||||
'media_type': e.media_type,
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
'reason': e.code,
|
||||
'reason_explanation': e.msg,
|
||||
}, status=400)
|
||||
except CheckInError as e:
|
||||
if not simulate:
|
||||
op.order.log_action('pretix.event.checkin.denied', data={
|
||||
@@ -1018,6 +1062,8 @@ class CheckinRPCRedeemView(views.APIView):
|
||||
canceled_supported=True,
|
||||
request=self.request, # this is not clean, but we need it in the serializers for URL generation
|
||||
legacy_url_support=False,
|
||||
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
|
||||
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||
|
||||
return Response({"result": None})
|
||||
|
||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
|
||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some performance
|
||||
def list(self, request, **kwargs):
|
||||
date = serializers.DateTimeField().to_representation(now())
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
@@ -36,7 +36,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from requests import RequestException
|
||||
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -313,7 +313,7 @@ def _get_or_create_server_keypair(organizer):
|
||||
|
||||
def generate_id_token(customer, client, auth_time, nonce, scope, expires: datetime, scope_claims=False, with_code=None, with_access_token=None):
|
||||
payload = {
|
||||
'iss': build_absolute_uri(client.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'iss': eventreverse_absolute(client.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'aud': client.client_id,
|
||||
'exp': int(expires.timestamp()),
|
||||
'iat': int(time.time()),
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import Checkin, InvoiceAddress, Order, Question
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
def get_answer(op, question_identifier=None):
|
||||
@@ -545,7 +545,7 @@ def get_data_fields(event, for_model=None):
|
||||
_("Order link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: build_absolute_uri(
|
||||
lambda order: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
@@ -560,7 +560,7 @@ def get_data_fields(event, for_model=None):
|
||||
_("Ticket link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda op: build_absolute_uri(
|
||||
lambda op: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': op.order.code,
|
||||
|
||||
@@ -68,7 +68,7 @@ from ...control.forms.filter import get_all_payment_providers
|
||||
from ...helpers import GroupConcat
|
||||
from ...helpers.iter import chunked_iterable
|
||||
from ...helpers.safe_openpyxl import remove_invalid_excel_chars
|
||||
from ...multidomain.urlreverse import build_absolute_uri
|
||||
from ...multidomain.urlreverse import eventreverse_absolute
|
||||
from ..exporter import (
|
||||
ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin,
|
||||
)
|
||||
@@ -429,7 +429,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
]))
|
||||
|
||||
row.append(
|
||||
build_absolute_uri(order.event, 'presale:event.order', kwargs={
|
||||
eventreverse_absolute(order.event, 'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
})
|
||||
@@ -855,7 +855,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
]))
|
||||
|
||||
row.append(
|
||||
build_absolute_uri(order.event, 'presale:event.order.position', kwargs={
|
||||
eventreverse_absolute(order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': op.web_secret,
|
||||
'position': op.positionid
|
||||
|
||||
@@ -64,7 +64,13 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
yield headers
|
||||
yield self.ProgressSetTotal(total=media.count())
|
||||
|
||||
can_read_giftcards = self.permission_holder.has_organizer_permission(self.organizer, 'organizer.giftcards:read')
|
||||
|
||||
for medium in media.iterator(chunk_size=1000):
|
||||
giftcard_secret = medium.linked_giftcard.secret if medium.linked_giftcard_id else ''
|
||||
if giftcard_secret and not can_read_giftcards:
|
||||
giftcard_secret = giftcard_secret[:3] + "…"
|
||||
|
||||
yield [
|
||||
medium.type,
|
||||
medium.identifier,
|
||||
@@ -72,7 +78,7 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
date_format(medium.expires, 'SHORT_DATETIME_FORMAT') if medium.expires else '',
|
||||
medium.customer.identifier if medium.customer_id else '',
|
||||
', '.join([f"{op.order.code}-{op.positionid}" for op in medium.linked_orderpositions.all()]),
|
||||
medium.linked_giftcard.secret if medium.linked_giftcard_id else '',
|
||||
giftcard_secret,
|
||||
medium.notes,
|
||||
]
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ class Command(Parent):
|
||||
# Start the vite server in the background
|
||||
vite_server = subprocess.Popen(
|
||||
["npm", "run", "dev:control"],
|
||||
cwd=Path(__file__).parent.parent.parent.parent.parent
|
||||
cwd=Path(__file__).parent.parent.parent.parent.parent,
|
||||
stdin=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
def cleanup():
|
||||
|
||||
+20
-14
@@ -26,6 +26,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class BaseMediaType:
|
||||
medium_created_by_server = False
|
||||
medium_created_from_unknown_supported = False
|
||||
supports_orderposition = False
|
||||
supports_giftcard = False
|
||||
|
||||
@@ -56,7 +57,7 @@ class BaseMediaType:
|
||||
def is_active(self, organizer):
|
||||
return organizer.settings.get(f'reusable_media_type_{self.identifier}', as_type=bool, default=False)
|
||||
|
||||
def handle_unknown(self, organizer, identifier, user, auth):
|
||||
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||
pass
|
||||
|
||||
def handle_new(self, organizer, medium, user, auth):
|
||||
@@ -88,23 +89,32 @@ class NfcUidMediaType(BaseMediaType):
|
||||
verbose_name = _('NFC UID-based')
|
||||
icon = 'pretixbase/img/media/nfc_uid.svg'
|
||||
medium_created_by_server = False
|
||||
medium_created_from_unknown_supported = True
|
||||
supports_giftcard = True
|
||||
supports_orderposition = False
|
||||
supports_orderposition = True
|
||||
|
||||
def handle_unknown(self, organizer, identifier, user, auth):
|
||||
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||
from pretix.base.models import GiftCard, ReusableMedium
|
||||
|
||||
if organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool):
|
||||
create_giftcard = organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool)
|
||||
if create_giftcard or force_create:
|
||||
if identifier.startswith("08"):
|
||||
# Don't create gift cards for NFC UIDs that start with 08, which represents NFC cards that issue random
|
||||
# UIDs on every read, so they won't be useful.
|
||||
return
|
||||
with transaction.atomic():
|
||||
gc = GiftCard.objects.create(
|
||||
issuer=organizer,
|
||||
expires=organizer.default_gift_card_expiry,
|
||||
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
||||
)
|
||||
if create_giftcard:
|
||||
gc = GiftCard.objects.create(
|
||||
issuer=organizer,
|
||||
expires=organizer.default_gift_card_expiry,
|
||||
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
||||
)
|
||||
gc.log_action(
|
||||
'pretix.giftcards.created',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
else:
|
||||
gc = None
|
||||
m = ReusableMedium.objects.create(
|
||||
type=self.identifier,
|
||||
identifier=identifier,
|
||||
@@ -116,10 +126,6 @@ class NfcUidMediaType(BaseMediaType):
|
||||
'pretix.reusable_medium.created.auto',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
gc.log_action(
|
||||
'pretix.giftcards.created',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
return m
|
||||
|
||||
|
||||
@@ -129,7 +135,7 @@ class NfcMf0aesMediaType(BaseMediaType):
|
||||
icon = 'pretixbase/img/media/nfc_secure.svg'
|
||||
medium_created_by_server = False
|
||||
supports_giftcard = True
|
||||
supports_orderposition = False
|
||||
supports_orderposition = True
|
||||
|
||||
def handle_new(self, organizer, medium, user, auth):
|
||||
from pretix.base.models import GiftCard
|
||||
|
||||
@@ -282,10 +282,12 @@ def metric_values():
|
||||
|
||||
# Throwaway metrics
|
||||
exact_tables = [
|
||||
Order, OrderPosition, Invoice, Event, Organizer
|
||||
Order, Invoice, Event, Organizer
|
||||
]
|
||||
for m in apps.get_models(): # Count all models
|
||||
if any(issubclass(m, p) for p in exact_tables):
|
||||
if issubclass(m, OrderPosition):
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = m.all.count()
|
||||
elif any(issubclass(m, p) for p in exact_tables):
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = m.objects.count()
|
||||
else:
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = estimate_count_fast(m)
|
||||
|
||||
@@ -74,6 +74,7 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
|
||||
def process_request(self, request: HttpRequest):
|
||||
language = get_language_from_request(request)
|
||||
region = None
|
||||
# Normally, this middleware runs *before* the event is set. However, on event frontend pages it
|
||||
# might be run a second time by pretix.presale.EventMiddleware and in this case the event is already
|
||||
# set and can be taken into account for the decision.
|
||||
@@ -94,15 +95,16 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
if '-' not in language and settings_holder.settings.region:
|
||||
language += '-' + settings_holder.settings.region
|
||||
if settings_holder.settings.region:
|
||||
set_region(settings_holder.settings.region)
|
||||
region = settings_holder.settings.region
|
||||
else:
|
||||
gs = global_settings_object(request)
|
||||
if '-' not in language and gs.settings.region:
|
||||
language += '-' + gs.settings.region
|
||||
if gs.settings.region:
|
||||
set_region(gs.settings.region)
|
||||
region = gs.settings.region
|
||||
|
||||
translation.activate(language)
|
||||
set_region(region)
|
||||
request.LANGUAGE_CODE = get_language_without_region()
|
||||
|
||||
tzname = None
|
||||
|
||||
@@ -57,7 +57,7 @@ from django_otp.models import Device
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
from ...helpers.countries import FastCountryField
|
||||
from ...helpers.u2f import pub_key_from_der, websafe_decode
|
||||
@@ -378,7 +378,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
{
|
||||
'user': self,
|
||||
'messages': msg,
|
||||
'url': build_absolute_uri('control:user.settings'),
|
||||
'url': mainreverse_absolute('control:user.settings'),
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
},
|
||||
event=None,
|
||||
@@ -466,7 +466,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'user': self,
|
||||
'url': (build_absolute_uri('control:auth.forgot.recover')
|
||||
'url': (mainreverse_absolute('control:auth.forgot.recover')
|
||||
+ '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self)))
|
||||
},
|
||||
None, locale=self.locale, user=self
|
||||
|
||||
@@ -346,11 +346,14 @@ class Checkin(models.Model):
|
||||
REASON_INCOMPLETE = 'incomplete'
|
||||
REASON_ALREADY_REDEEMED = 'already_redeemed'
|
||||
REASON_AMBIGUOUS = 'ambiguous'
|
||||
REASON_MEDIUM_INVALID = 'medium_invalid'
|
||||
REASON_MEDIUM_EXISTS = 'medium_exists'
|
||||
REASON_ERROR = 'error'
|
||||
REASON_BLOCKED = 'blocked'
|
||||
REASON_UNAPPROVED = 'unapproved'
|
||||
REASON_INVALID_TIME = 'invalid_time'
|
||||
REASON_ANNULLED = 'annulled'
|
||||
REASON_ALREADY_EXCHANGED = 'already_exchanged'
|
||||
REASONS = (
|
||||
(REASON_CANCELED, _('Order canceled')),
|
||||
(REASON_INVALID, _('Unknown ticket')),
|
||||
@@ -366,6 +369,9 @@ class Checkin(models.Model):
|
||||
(REASON_UNAPPROVED, _('Order not approved')),
|
||||
(REASON_INVALID_TIME, _('Ticket not valid at this time')),
|
||||
(REASON_ANNULLED, _('Check-in annulled')),
|
||||
(REASON_ALREADY_EXCHANGED, _('Ticket already exchanged')),
|
||||
(REASON_MEDIUM_INVALID, _('Reusable medium invalid')),
|
||||
(REASON_MEDIUM_EXISTS, _('Reusable medium already exists')),
|
||||
)
|
||||
|
||||
successful = models.BooleanField(
|
||||
|
||||
@@ -167,7 +167,7 @@ class Customer(LoggedModel):
|
||||
|
||||
def send_security_notice(self, message, email=None):
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
try:
|
||||
with language(self.locale):
|
||||
@@ -178,7 +178,7 @@ class Customer(LoggedModel):
|
||||
{
|
||||
**self.get_email_context(),
|
||||
'message': str(message),
|
||||
'url': build_absolute_uri(self.organizer, 'presale:organizer.customer.index')
|
||||
'url': eventreverse_absolute(self.organizer, 'presale:organizer.customer.index')
|
||||
},
|
||||
customer=self,
|
||||
organizer=self.organizer,
|
||||
@@ -299,12 +299,12 @@ class Customer(LoggedModel):
|
||||
|
||||
def send_activation_mail(self):
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
|
||||
ctx = self.get_email_context()
|
||||
token = TokenGenerator().make_token(self)
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.organizer,
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?id=' + self.identifier + '&token=' + token
|
||||
|
||||
@@ -724,7 +724,7 @@ class Event(EventMixin, LoggedModel):
|
||||
|
||||
@property
|
||||
def social_image(self):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
img = None
|
||||
logo_file = self.settings.get('logo_image', as_type=str, default='')[7:]
|
||||
@@ -742,7 +742,7 @@ class Event(EventMixin, LoggedModel):
|
||||
logger.exception(f'Failed to create thumbnail of {logo_file}')
|
||||
img = default_storage.url(logo_file)
|
||||
if img:
|
||||
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
|
||||
return urljoin(eventreverse_absolute(self, 'presale:event.index'), img)
|
||||
|
||||
def _seats(self, ignore_voucher=None):
|
||||
from .seating import Seat
|
||||
|
||||
@@ -452,11 +452,16 @@ class Item(LoggedModel):
|
||||
MEDIA_POLICY_REUSE = 'reuse'
|
||||
MEDIA_POLICY_NEW = 'new'
|
||||
MEDIA_POLICY_REUSE_OR_NEW = 'reuse_or_new'
|
||||
MEDIA_POLICY_APPEND = 'append'
|
||||
MEDIA_POLICY_APPEND_OR_NEW = 'append_or_new'
|
||||
MEDIA_POLICIES = (
|
||||
(None, _("Don't use re-usable media, use regular one-off tickets")),
|
||||
(MEDIA_POLICY_REUSE, _('Require an existing medium to be re-used')),
|
||||
(None, _("Don't use reusable media, use regular one-off tickets")),
|
||||
(MEDIA_POLICY_NEW, _('Require a previously unknown medium to be newly added')),
|
||||
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used')),
|
||||
(MEDIA_POLICY_REUSE, _('Require an existing medium to be reused, replacing any previous tickets')),
|
||||
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used, replacing any previous tickets')),
|
||||
(MEDIA_POLICY_APPEND, _('Require an existing medium to be reused, adding to any previous tickets')),
|
||||
(MEDIA_POLICY_APPEND_OR_NEW,
|
||||
_('Require either an existing or a new medium to be used, adding to any previous tickets')),
|
||||
)
|
||||
|
||||
objects = ItemQuerySetManager()
|
||||
@@ -769,7 +774,7 @@ class Item(LoggedModel):
|
||||
null=True, blank=True, max_length=16,
|
||||
verbose_name=_('Reusable media policy'),
|
||||
help_text=_(
|
||||
'If this product should be stored on a re-usable physical medium, you can attach a physical media policy. '
|
||||
'If this product should be stored on a reusable physical medium, you can attach a physical media policy. '
|
||||
'This is not required for regular tickets, which just use a one-time barcode, but only for products like '
|
||||
'renewable season tickets or re-chargeable gift card wristbands. '
|
||||
'This is an advanced feature that also requires specific configuration of ticketing and printing settings.'
|
||||
@@ -778,7 +783,7 @@ class Item(LoggedModel):
|
||||
media_type = models.CharField(
|
||||
max_length=100,
|
||||
null=True, blank=True,
|
||||
choices=[(None, _("Don't use re-usable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
|
||||
choices=[(None, _("Don't use reusable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
|
||||
verbose_name=_('Reusable media type'),
|
||||
help_text=_(
|
||||
'Select the type of physical medium that should be used for this product. Note that not all media types '
|
||||
@@ -995,6 +1000,11 @@ class Item(LoggedModel):
|
||||
raise ValidationError(_('The selected media type does not support usage for tickets currently.'))
|
||||
if not mt.supports_giftcard and issue_giftcard:
|
||||
raise ValidationError(_('The selected media type does not support usage for gift cards currently.'))
|
||||
if media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if not mt.medium_created_by_server and not mt.medium_created_from_unknown_supported:
|
||||
raise ValidationError(_('The selected media type requires all media to be registered in the system '
|
||||
'prior to their usage. Therefore, the selected media policy does not make '
|
||||
'sense for this media type.'))
|
||||
if issue_giftcard:
|
||||
raise ValidationError(_('You currently cannot create gift cards with a reusable media policy. Instead, '
|
||||
'gift cards for some reusable media types can be created or re-charged directly '
|
||||
@@ -2220,7 +2230,7 @@ class Quota(LoggedModel):
|
||||
class ItemMetaProperty(LoggedModel):
|
||||
"""
|
||||
An event can have ItemMetaProperty objects attached to define meta information fields
|
||||
for its items. This information can be re-used for example in ticket layouts.
|
||||
for its items. This information can be reused for example in ticket layouts.
|
||||
|
||||
:param event: The event this property is defined for.
|
||||
:type event: Event
|
||||
|
||||
@@ -129,7 +129,10 @@ class ReusableMedium(LoggedModel):
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
return self.expires and self.expires > now()
|
||||
return self.expires and self.expires < now()
|
||||
|
||||
def touch(self):
|
||||
self.save(update_fields=['updated'])
|
||||
|
||||
class Meta:
|
||||
unique_together = (("identifier", "type", "organizer"),)
|
||||
|
||||
@@ -354,38 +354,60 @@ class Order(LockModel, LoggedModel):
|
||||
def _transaction_key_reset(self):
|
||||
self.__initial_status_paid_or_pending = self.status in (Order.STATUS_PENDING, Order.STATUS_PAID) and not self.require_approval
|
||||
|
||||
def gracefully_delete(self, user=None, auth=None):
|
||||
from . import GiftCard, GiftCardTransaction, Membership, Voucher
|
||||
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
self.log_action(
|
||||
'pretix.event.order.deleted', user=user, auth=auth,
|
||||
data={
|
||||
'code': self.code,
|
||||
}
|
||||
@classmethod
|
||||
def gracefully_delete_bulk(cls, event, orders, user=None, auth=None):
|
||||
# Expects to be called in a transaction
|
||||
from . import (
|
||||
GiftCard, GiftCardTransaction, LogEntry, Membership, Voucher,
|
||||
)
|
||||
|
||||
order_gracefully_delete.send(self.event, order=self)
|
||||
if not transaction.get_connection().in_atomic_block:
|
||||
raise Exception('gracefully_delete_bulk should only be called in atomic transaction!')
|
||||
|
||||
if self.status != Order.STATUS_CANCELED:
|
||||
for position in self.positions.all():
|
||||
if position.voucher:
|
||||
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
|
||||
logs_create = []
|
||||
for o in orders:
|
||||
if not o.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
order_gracefully_delete.send(event, order=o)
|
||||
logs_create.append(o.log_action(
|
||||
'pretix.event.order.deleted', user=user, auth=auth,
|
||||
data={
|
||||
'code': o.code,
|
||||
},
|
||||
save=False,
|
||||
))
|
||||
LogEntry.bulk_create_and_postprocess(logs_create)
|
||||
|
||||
GiftCardTransaction.objects.filter(payment__in=self.payments.all()).update(payment=None)
|
||||
GiftCardTransaction.objects.filter(refund__in=self.refunds.all()).update(refund=None)
|
||||
GiftCardTransaction.objects.filter(order=self).update(order=None)
|
||||
GiftCard.objects.filter(issued_in__in=self.positions.all()).update(issued_in=None)
|
||||
Membership.objects.filter(granted_in__order=self, testmode=True).update(granted_in=None)
|
||||
OrderPosition.all.filter(order=self, addon_to__isnull=False).delete()
|
||||
OrderPosition.all.filter(order=self).delete()
|
||||
OrderFee.all.filter(order=self).delete()
|
||||
Transaction.objects.filter(order=self).delete()
|
||||
self.refunds.all().delete()
|
||||
self.payments.all().delete()
|
||||
self.event.cache.delete('complain_testmode_orders')
|
||||
self.delete()
|
||||
voucher_ids = OrderPosition.objects.filter(
|
||||
order__in=orders,
|
||||
voucher__isnull=False
|
||||
).exclude(order__status=Order.STATUS_CANCELED).values_list("voucher_id", flat=True)
|
||||
voucher_usages = Counter(voucher_ids)
|
||||
for v_id, usage_count in voucher_usages.items():
|
||||
Voucher.objects.filter(pk=v_id).update(redeemed=Greatest(0, F('redeemed') - usage_count))
|
||||
|
||||
GiftCardTransaction.objects.filter(payment__order__in=orders).update(payment=None)
|
||||
GiftCardTransaction.objects.filter(refund__order__in=orders).update(refund=None)
|
||||
GiftCardTransaction.objects.filter(order__in=orders).update(order=None)
|
||||
GiftCard.objects.filter(issued_in__order__in=orders).update(issued_in=None)
|
||||
Membership.objects.filter(granted_in__order__in=orders, testmode=True).update(granted_in=None)
|
||||
OrderPosition.all.filter(order__in=orders, addon_to__isnull=False).delete()
|
||||
OrderPosition.all.filter(order__in=orders).delete()
|
||||
OrderFee.all.filter(order__in=orders).delete()
|
||||
Transaction.objects.filter(order__in=orders).delete()
|
||||
OrderRefund.objects.filter(order__in=orders).delete()
|
||||
OrderPayment.objects.filter(order__in=orders).delete()
|
||||
if isinstance(orders, models.QuerySet):
|
||||
orders.delete()
|
||||
else:
|
||||
Order.objects.filter(pk__in=[o.pk for o in orders]).delete()
|
||||
event.cache.delete('complain_testmode_orders')
|
||||
|
||||
def gracefully_delete(self, user=None, auth=None):
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
|
||||
Order.gracefully_delete_bulk(self.event, Order.objects.filter(pk=self.pk), user, auth)
|
||||
|
||||
def email_confirm_secret(self):
|
||||
return self.tagged_secret("email_confirm", 9)
|
||||
|
||||
@@ -118,7 +118,10 @@ class SeatingPlan(LoggedModel):
|
||||
for zi, z in enumerate(self.layout_data['zones']):
|
||||
zpos = (z['position']['x'], z['position']['y'])
|
||||
for ri, r in enumerate(z['rows']):
|
||||
rpos = (zpos[0] + r['position']['x'], zpos[1] + r['position']['y'])
|
||||
rpos = (
|
||||
zpos[0] + r.get('position', {}).get('x', 0),
|
||||
zpos[1] + r.get('position', {}).get('y', 0),
|
||||
)
|
||||
row_label = None
|
||||
if r.get('row_label'):
|
||||
row_label = r['row_label'].replace("%s", r.get('row_number', str(ri)))
|
||||
@@ -147,8 +150,8 @@ class SeatingPlan(LoggedModel):
|
||||
zone=z['name'],
|
||||
category=s['category'],
|
||||
sorting_rank=rank,
|
||||
x=rpos[0] + s['position']['x'],
|
||||
y=rpos[1] + s['position']['y'],
|
||||
x=rpos[0] + s.get('position', {}).get('x', 0),
|
||||
y=rpos[1] + s.get('position', {}).get('y', 0),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from pretix.base.models import Event, LogEntry
|
||||
from pretix.base.signals import register_notification_types
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_ALL_TYPES = None
|
||||
@@ -170,7 +170,7 @@ class ParametrizedOrderNotificationType(NotificationType):
|
||||
def build_notification(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
|
||||
order_url = build_absolute_uri(
|
||||
order_url = mainreverse_absolute(
|
||||
'control:event.order',
|
||||
kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
|
||||
@@ -71,7 +71,7 @@ from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.money import DecimalTextInput
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.views import get_cart
|
||||
from pretix.presale.views.cart import cart_session, get_or_create_cart_id
|
||||
|
||||
@@ -379,7 +379,7 @@ class BasePaymentProvider:
|
||||
|
||||
if not self.settings.get('_hidden_seed'):
|
||||
self.settings.set('_hidden_seed', get_random_string(64))
|
||||
hidden_url = build_absolute_uri(self.event, 'presale:event.payment.unlock', kwargs={
|
||||
hidden_url = eventreverse_absolute(self.event, 'presale:event.payment.unlock', kwargs={
|
||||
'hash': hashlib.sha256((self.settings._hidden_seed + self.event.slug).encode()).hexdigest(),
|
||||
})
|
||||
|
||||
|
||||
@@ -287,11 +287,11 @@ def _check_position_constraints(
|
||||
raise CartPositionError(error_messages['unavailable'])
|
||||
|
||||
# Invalid media policy for online sale
|
||||
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
mt = MEDIA_TYPES[item.media_type]
|
||||
if not mt.medium_created_by_server:
|
||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||
elif item.media_policy == Item.MEDIA_POLICY_REUSE:
|
||||
elif item.media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||
|
||||
# Item removed from sales channel
|
||||
|
||||
@@ -867,6 +867,15 @@ class RequiredQuestionsError(Exception):
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class RequiredMediaExchangeError(Exception):
|
||||
def __init__(self, msg, code, media_policy, media_type):
|
||||
self.msg = msg
|
||||
self.code = code
|
||||
self.media_policy = media_policy
|
||||
self.media_type = media_type
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
def _save_answers(op, answers, given_answers):
|
||||
def _create_answer(question, answer):
|
||||
try:
|
||||
@@ -939,7 +948,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
ignore_unpaid=False, nonce=None, datetime=None, questions_supported=True,
|
||||
user=None, auth=None, canceled_supported=False, type=Checkin.TYPE_ENTRY,
|
||||
raw_barcode=None, raw_source_type=None, from_revoked_secret=False, simulate=False,
|
||||
gate=None):
|
||||
gate=None, reusable_medium=None):
|
||||
"""
|
||||
Create a checkin for this particular order position and check-in list. Fails with CheckInError if the check in is
|
||||
not valid at this time.
|
||||
@@ -955,6 +964,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
:param datetime: The datetime of the checkin, defaults to now.
|
||||
:param simulate: If true, the check-in is not saved.
|
||||
:param gate: The gate the check-in was performed at.
|
||||
:param reusable_medium: The medium that is available for an exchange
|
||||
"""
|
||||
|
||||
# !!!!!!!!!
|
||||
@@ -1035,7 +1045,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
|
||||
with transaction.atomic():
|
||||
# Lock order positions, if it is an entry. We don't need it for exits, as a race condition wouldn't be problematic
|
||||
opqs = OrderPosition.all
|
||||
opqs = OrderPosition.all.select_related("order", "item")
|
||||
if type != Checkin.TYPE_EXIT:
|
||||
opqs = opqs.select_for_update(of=OF_SELF)
|
||||
op = opqs.get(pk=op.pk)
|
||||
@@ -1101,6 +1111,24 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
require_answers
|
||||
)
|
||||
|
||||
required_media_policy = op.item.media_policy
|
||||
required_media_type = op.item.media_type
|
||||
require_a_medium = required_media_policy and required_media_type
|
||||
linked_media = op.linked_media
|
||||
if require_a_medium and not reusable_medium and not force:
|
||||
if not linked_media.exists():
|
||||
raise RequiredMediaExchangeError(
|
||||
_('Ticket needs to be exchanged to a suitable medium.'),
|
||||
'exchange',
|
||||
required_media_policy,
|
||||
required_media_type
|
||||
)
|
||||
elif op.organizer.settings.reusable_media_usage_enforced:
|
||||
raise CheckInError(
|
||||
_('This ticket has already been exchanged for a reusable medium that now needs to be used instead.'),
|
||||
'already_exchanged',
|
||||
)
|
||||
|
||||
device = None
|
||||
if isinstance(auth, Device):
|
||||
device = auth
|
||||
|
||||
@@ -51,7 +51,7 @@ from pretix.base.signals import (
|
||||
)
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF, repeatable_reads_transaction
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -455,7 +455,7 @@ def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> Non
|
||||
schedule,
|
||||
organizer,
|
||||
exporter,
|
||||
build_absolute_uri(
|
||||
mainreverse_absolute(
|
||||
'control:organizer.export',
|
||||
kwargs={
|
||||
'organizer': organizer.slug,
|
||||
@@ -481,7 +481,7 @@ def scheduled_event_export(self, event: Event, schedule: int) -> None:
|
||||
schedule,
|
||||
event,
|
||||
exporter,
|
||||
build_absolute_uri(
|
||||
mainreverse_absolute(
|
||||
'control:event.orders.export',
|
||||
kwargs={
|
||||
'event': event.slug,
|
||||
|
||||
@@ -85,7 +85,7 @@ from pretix.helpers.format import (
|
||||
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.ical import get_private_icals
|
||||
|
||||
logger = logging.getLogger('pretix.base.mail')
|
||||
@@ -997,7 +997,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += _(
|
||||
"You can view your order details at the following URL:\n{orderurl}."
|
||||
).replace("\n", "\r\n").format(
|
||||
orderurl=build_absolute_uri(
|
||||
orderurl=eventreverse_absolute(
|
||||
order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': position.web_secret,
|
||||
@@ -1013,7 +1013,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += _(
|
||||
"You can view your order details at the following URL:\n{orderurl}."
|
||||
).replace("\n", "\r\n").format(
|
||||
event=event.name, orderurl=build_absolute_uri(
|
||||
event=event.name, orderurl=eventreverse_absolute(
|
||||
order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
|
||||
@@ -23,10 +23,13 @@ import secrets
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import GiftCardAcceptance
|
||||
from pretix.base.models.media import MediumKeySet
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import Checkin, GiftCardAcceptance, Item
|
||||
from pretix.base.models.media import MediumKeySet, ReusableMedium
|
||||
from pretix.base.services.checkin import CheckInError
|
||||
|
||||
|
||||
def create_nfc_mf0aes_keyset(organizer):
|
||||
@@ -70,3 +73,174 @@ def get_keysets_for_organizer(organizer):
|
||||
if new_set:
|
||||
sets.append(new_set)
|
||||
return sets
|
||||
|
||||
|
||||
def perform_media_exchange(organizer, media_type, identifier, link_orderposition, user, auth):
|
||||
"""
|
||||
Create or retrieve a medium, then link the order position to it. Expected to be called in a transaction.
|
||||
|
||||
:param organizer: Organizer to operate in
|
||||
:param media_type: Type of medium to operate with
|
||||
:param identifier: Identifier of the medium
|
||||
:param link_orderposition: Position to link to the medium
|
||||
:return: ReusableMedium
|
||||
"""
|
||||
medium = None
|
||||
media_policy = link_orderposition.item.media_policy
|
||||
|
||||
if media_type not in MEDIA_TYPES: # should be caught by serializer already
|
||||
raise CheckInError(
|
||||
_('Invalid medium type.'),
|
||||
Checkin.REASON_ERROR,
|
||||
reason=_('Invalid medium type.'),
|
||||
)
|
||||
|
||||
if not MEDIA_TYPES[media_type].is_active(organizer):
|
||||
raise CheckInError(
|
||||
_('Medium type is not enabled for organizer.'),
|
||||
Checkin.REASON_ERROR,
|
||||
reason=_('Medium type is not enabled for organizer.'),
|
||||
)
|
||||
|
||||
if link_orderposition.item.media_type != media_type:
|
||||
raise CheckInError(
|
||||
_('Incorrect medium type for product.'),
|
||||
Checkin.REASON_PRODUCT,
|
||||
reason=_('Incorrect medium type for product.'),
|
||||
)
|
||||
|
||||
if link_orderposition.linked_media.exists():
|
||||
raise CheckInError(
|
||||
_('Ticket is already exchanged for reusable medium.'),
|
||||
Checkin.REASON_ALREADY_EXCHANGED,
|
||||
reason=_('Ticket is already exchanged for reusable medium.'),
|
||||
)
|
||||
|
||||
if media_policy in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_NEW):
|
||||
link_action = "append"
|
||||
else:
|
||||
link_action = "replace"
|
||||
|
||||
if media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||
try:
|
||||
medium = ReusableMedium.objects.get(
|
||||
type=media_type,
|
||||
identifier=identifier,
|
||||
organizer=organizer,
|
||||
)
|
||||
except ReusableMedium.DoesNotExist:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium not found.'),
|
||||
)
|
||||
else:
|
||||
if medium.is_expired or not medium.active:
|
||||
raise CheckInError(
|
||||
_('Reusable medium is inactive or expired.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium is inactive or expired.'),
|
||||
)
|
||||
|
||||
elif media_policy in (Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
try:
|
||||
medium = ReusableMedium.objects.get(
|
||||
type=media_type,
|
||||
identifier=identifier,
|
||||
organizer=organizer,
|
||||
)
|
||||
except ReusableMedium.DoesNotExist:
|
||||
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||
if not medium:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
if medium.is_expired or not medium.active:
|
||||
raise CheckInError(
|
||||
_('Reusable medium is inactive or expired.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium is inactive or expired.'),
|
||||
)
|
||||
|
||||
elif media_policy == Item.MEDIA_POLICY_NEW:
|
||||
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
try:
|
||||
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||
except IntegrityError:
|
||||
raise CheckInError(
|
||||
_('Reusable medium already exists.'),
|
||||
Checkin.REASON_MEDIUM_EXISTS,
|
||||
)
|
||||
else:
|
||||
if not medium:
|
||||
raise CheckInError(
|
||||
_('Reusable medium could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
else:
|
||||
raise CheckInError(
|
||||
_('Product does not support medium exchange.'),
|
||||
Checkin.REASON_PRODUCT,
|
||||
reason=_('Product does not support medium exchange.'),
|
||||
)
|
||||
|
||||
if link_action == 'append':
|
||||
medium.linked_orderpositions.add(link_orderposition)
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'linked_orderposition': link_orderposition,
|
||||
}
|
||||
)
|
||||
elif link_action == 'replace':
|
||||
already_found = False
|
||||
for op_pk in medium.linked_orderpositions.values_list('pk', flat=True):
|
||||
if op_pk == link_orderposition.pk:
|
||||
already_found = True
|
||||
continue
|
||||
else:
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.removed',
|
||||
data={
|
||||
'linked_orderposition': op_pk,
|
||||
}
|
||||
)
|
||||
if not already_found:
|
||||
medium.linked_orderpositions.set([link_orderposition])
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'linked_orderposition': link_orderposition,
|
||||
}
|
||||
)
|
||||
|
||||
link_orderposition.order.log_action(
|
||||
'pretix.reusable_medium.exchanged',
|
||||
data={
|
||||
'position': link_orderposition.pk,
|
||||
'positionid': link_orderposition.positionid,
|
||||
'medium': medium.pk,
|
||||
'medium_identifier': medium.identifier,
|
||||
'medium_type': medium.media_type.identifier,
|
||||
}
|
||||
)
|
||||
medium.touch()
|
||||
|
||||
return medium
|
||||
|
||||
@@ -37,7 +37,7 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
|
||||
from pretix.base.signals import notification
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.celery import get_task_priority
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
@app.task(base=TransactionAwareTask, acks_late=True, max_retries=9, default_retry_delay=900)
|
||||
@@ -136,10 +136,10 @@ def send_notification_mail(notification: Notification, user: User):
|
||||
'site_url': settings.SITE_URL,
|
||||
'color': settings.PRETIX_PRIMARY_COLOR,
|
||||
'notification': notification,
|
||||
'settings_url': build_absolute_uri(
|
||||
'settings_url': mainreverse_absolute(
|
||||
'control:user.settings.notifications',
|
||||
),
|
||||
'disable_url': build_absolute_uri(
|
||||
'disable_url': mainreverse_absolute(
|
||||
'control:user.settings.notifications.off',
|
||||
kwargs={
|
||||
'token': user.notifications_token,
|
||||
|
||||
@@ -3506,7 +3506,7 @@ def signal_listener_issue_media(sender: Event, order: Order, **kwargs):
|
||||
from pretix.base.models import ReusableMedium
|
||||
|
||||
for p in order.positions.all():
|
||||
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
mt = MEDIA_TYPES[p.item.media_type]
|
||||
if mt.medium_created_by_server and not p.linked_media.exists():
|
||||
rm = ReusableMedium.objects.create(
|
||||
|
||||
@@ -327,7 +327,7 @@ def get_best_name(position_or_address, parts=False):
|
||||
|
||||
@receiver(register_text_placeholders, dispatch_uid="pretixbase_register_text_placeholders")
|
||||
def base_placeholders(sender, **kwargs):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
def _event_sample(event):
|
||||
if event.has_subevents:
|
||||
@@ -388,14 +388,14 @@ def base_placeholders(sender, **kwargs):
|
||||
lambda event: LazyDate(now() + timedelta(days=15))
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_secret()
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -406,7 +406,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['order', 'event'],
|
||||
url_func=lambda order, event: build_absolute_uri(
|
||||
url_func=lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
@@ -415,7 +415,7 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
text_func=lambda order, event: _("View order details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
sample_url_func=lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -426,13 +426,13 @@ def base_placeholders(sender, **kwargs):
|
||||
sample_text_func=lambda event: _("View order details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -441,13 +441,13 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_products_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_products_change', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -456,13 +456,13 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_cancel', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_cancel', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -471,7 +471,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
|
||||
'url', ['event', 'position'], lambda event, position: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position',
|
||||
kwargs={
|
||||
@@ -480,7 +480,7 @@ def base_placeholders(sender, **kwargs):
|
||||
'position': position.positionid
|
||||
}
|
||||
),
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -491,7 +491,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['event', 'position'],
|
||||
url_func=lambda event, position: build_absolute_uri(
|
||||
url_func=lambda event, position: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': position.order.code,
|
||||
@@ -500,7 +500,7 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
text_func=lambda event, position: _("View registration details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
sample_url_func=lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -511,14 +511,14 @@ def base_placeholders(sender, **kwargs):
|
||||
sample_text_func=lambda event: _("View registration details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.modify', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.modify', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -528,14 +528,14 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_products_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
'url_products_change', ['position', 'event'], lambda position, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.change', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.change', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -581,20 +581,20 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_remove', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
lambda waiting_list_voucher, event: eventreverse_absolute(
|
||||
event, 'presale:event.waitinglist.remove'
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.waitinglist.remove',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
lambda waiting_list_voucher, event: eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.redeem',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
@@ -611,7 +611,7 @@ def base_placeholders(sender, **kwargs):
|
||||
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
order.full_code,
|
||||
build_absolute_uri(event, 'presale:event.order.open', kwargs={
|
||||
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order.code,
|
||||
@@ -623,7 +623,7 @@ def base_placeholders(sender, **kwargs):
|
||||
), lambda event: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
'{}-{}'.format(event.slug.upper(), order['code']),
|
||||
build_absolute_uri(event, 'presale:event.order.open', kwargs={
|
||||
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order['code'],
|
||||
@@ -662,13 +662,13 @@ def base_placeholders(sender, **kwargs):
|
||||
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
|
||||
'voucher_url_list', ['event', 'voucher_list'],
|
||||
lambda event, voucher_list: ' \n'.join([
|
||||
build_absolute_uri(
|
||||
eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + c
|
||||
for c in voucher_list
|
||||
]),
|
||||
lambda event: ' \n'.join([
|
||||
build_absolute_uri(
|
||||
eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + c
|
||||
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
|
||||
@@ -676,10 +676,10 @@ def base_placeholders(sender, **kwargs):
|
||||
inline=False,
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
|
||||
'url', ['event', 'voucher_list'], lambda event, voucher_list: eventreverse_absolute(event, 'presale:event.index', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}), lambda event: build_absolute_uri(event, 'presale:event.index', kwargs={
|
||||
}), lambda event: eventreverse_absolute(event, 'presale:event.index', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ from pretix.base.services.mail import mail
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@@ -121,7 +121,7 @@ def send_update_notification_email():
|
||||
)
|
||||
),
|
||||
{
|
||||
'url': build_absolute_uri('control:global.update')
|
||||
'url': mainreverse_absolute('control:global.update')
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -211,12 +211,25 @@ DEFAULTS = {
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Activate re-usable media"),
|
||||
help_text=_("The re-usable media feature allows you to connect tickets and gift cards with physical media "
|
||||
"such as wristbands or chip cards that may be re-used for different tickets or gift cards "
|
||||
label=_("Activate reusable media"),
|
||||
help_text=_("The reusable media feature allows you to connect tickets and gift cards with physical media "
|
||||
"such as wristbands or chip cards that may be reused for different tickets or gift cards "
|
||||
"later.")
|
||||
)
|
||||
},
|
||||
'reusable_media_usage_enforced': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Enforce the usage of issued reusable media for check-in"),
|
||||
help_text=_("If enabled, a ticket barcode will not be accepted anymore, if a reusable medium has been "
|
||||
"created and linked to a ticket. Keeping this option turned off will treat the reusable "
|
||||
"medium and ticket as equals."),
|
||||
widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_settings-reusable_media_active'}),
|
||||
)
|
||||
},
|
||||
'reusable_media_type_barcode': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
{% block custom_header %}{% endblock %}
|
||||
{% if css_theme %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ css_theme }}" />
|
||||
@@ -21,5 +22,4 @@
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
<script src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
</html>
|
||||
|
||||
@@ -80,7 +80,7 @@ from pretix.control.forms.widgets import Select2
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.models import AlternativeDomainAssignment, KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_organizer_domain,
|
||||
eventreverse_absolute, get_organizer_domain,
|
||||
)
|
||||
from pretix.plugins.banktransfer.payment import BankTransfer
|
||||
from pretix.presale.style import get_fonts
|
||||
@@ -219,7 +219,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
self.fields['slug'].widget.prefix = build_absolute_uri(self.organizer, 'presale:organizer.index')
|
||||
self.fields['slug'].widget.prefix = eventreverse_absolute(self.organizer, 'presale:organizer.index')
|
||||
self.fields['tax_rate']._required = True # Do not render as optional because it is conditionally required
|
||||
if self.has_subevents:
|
||||
del self.fields['presale_start']
|
||||
|
||||
@@ -1342,7 +1342,13 @@ class QuestionAnswerFilterForm(forms.Form):
|
||||
opqs = opqs.filter(canceled=False)
|
||||
if fdata.get("item", "") != "":
|
||||
i = fdata.get("item", "")
|
||||
opqs = opqs.filter(item_id__in=(i,))
|
||||
if '-' in i:
|
||||
opqs = opqs.filter(
|
||||
item_id=i.split('-')[0],
|
||||
variation_id=i.split('-')[1],
|
||||
)
|
||||
else:
|
||||
opqs = opqs.filter(item_id=i)
|
||||
|
||||
return opqs
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ from pretix.control.forms.event import (
|
||||
)
|
||||
from pretix.control.forms.widgets import Select2, Select2Multiple
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
class OrganizerForm(I18nModelForm):
|
||||
@@ -636,6 +636,7 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
'cookie_consent_dialog_button_yes',
|
||||
'cookie_consent_dialog_button_no',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -790,7 +791,7 @@ class MailSettingsForm(SettingsForm):
|
||||
}
|
||||
|
||||
if 'url' in base_parameters:
|
||||
placeholders['url'] = build_absolute_uri(
|
||||
placeholders['url'] = eventreverse_absolute(
|
||||
self.organizer,
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?token=' + get_random_string(30)
|
||||
|
||||
@@ -746,6 +746,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
|
||||
'pretix.reusable_medium.linked_orderposition.added': _('A new ticket has been added to the medium.'),
|
||||
'pretix.reusable_medium.linked_orderposition.removed': _('A ticket has been removed from the medium.'),
|
||||
'pretix.reusable_medium.linked_orderposition.changed': _('The medium has been connected to a new ticket.'),
|
||||
'pretix.reusable_medium.exchanged': _('The ticket #{positionid} was exchanged for reusable medium {medium_identifier}.'),
|
||||
'pretix.reusable_medium.linked_giftcard.changed': _('The medium has been connected to a new gift card.'),
|
||||
'pretix.email.error': _('Sending of an email has failed.'),
|
||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{{ html_head|safe }}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
<span class="fa fa-check-circle"></span>
|
||||
{% elif result.status == "incomplete" %}
|
||||
<span class="fa fa-question-circle"></span>
|
||||
{% elif result.status == "exchange" %}
|
||||
<span class="fa fa-recycle"></span>
|
||||
{% elif result.status == "error" %}
|
||||
{% if result.reason == "already_redeemed" %}
|
||||
<span class="fa fa-warning"></span>
|
||||
@@ -79,6 +81,14 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% elif result.status == "exchange" %}
|
||||
<h3 class="nomargin-top">{% trans "Media exchange required" %}</h3>
|
||||
<p>
|
||||
{% blocktrans trimmed with media_policy=media_policies|getitem:result.media_policy media_type=media_types|getitem:result.media_type %}
|
||||
This ticket needs to be exchanged into a <strong>{{ media_type }}</strong> reusable medium.
|
||||
<strong>{{ media_policy }}</strong>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% elif result.status == "error" %}
|
||||
<h3 class="nomargin-top">{{ reason_labels|getitem:result.reason }}</h3>
|
||||
{% if result.reason_explanation %}
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Reusable media" %}</legend>
|
||||
{% bootstrap_field sform.reusable_media_active layout="control" %}
|
||||
{% bootstrap_field sform.reusable_media_usage_enforced layout="control" %}
|
||||
<div data-display-dependency="#{{ sform.reusable_media_active.id_for_label }}">
|
||||
|
||||
<div class="panel panel-default">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load getitem %}
|
||||
{% load icon %}
|
||||
{% block inner %}
|
||||
{% if team %}
|
||||
<h1>{% trans "Team:" %} {{ team.name }}</h1>
|
||||
@@ -25,6 +26,18 @@
|
||||
<legend>{% trans "Organizer permissions" %}</legend>
|
||||
{% bootstrap_field form.all_organizer_permissions layout="control" %}
|
||||
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_organizer_permissions" data-inverse>
|
||||
<p class="text-muted">
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed %}
|
||||
Even if a team has no access to a certain category of data, they might still be able to see
|
||||
parts of this data when it is linked to data they can see.
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
For example, someone with access to customer accounts will be able to see some information
|
||||
about gift cards linked to a customer account, even if they generally can't see gift cards
|
||||
directly.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% for f in form.organizer_field_names %}
|
||||
{% bootstrap_field form|getitem:f layout="control" %}
|
||||
{% endfor %}
|
||||
@@ -37,6 +50,17 @@
|
||||
{% bootstrap_field form.limit_events layout="control" %}
|
||||
{% bootstrap_field form.all_event_permissions layout="control" %}
|
||||
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_event_permissions" data-inverse>
|
||||
<p class="text-muted">
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed %}
|
||||
Even if a team has no access to a certain category of data, they might still be able to see
|
||||
parts of this data when it is linked to data they can see.
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
For example, someone with access to orders will be able to see some information about
|
||||
vouchers used to create an order, even if they generally can't see vouchers directly.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% for f in form.event_field_names %}
|
||||
{% bootstrap_field form|getitem:f layout="control" %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -50,7 +50,7 @@ from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.api.views.checkin import _redeem_process
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import Checkin, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models import Checkin, Item, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.permissions import AnyPermissionOf
|
||||
@@ -401,13 +401,14 @@ class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
{
|
||||
'id': i.pk,
|
||||
'name': str(i),
|
||||
'active': i.active,
|
||||
'variations': [
|
||||
{
|
||||
'id': v.pk,
|
||||
'name': str(v.value)
|
||||
} for v in i.variations.all()
|
||||
]
|
||||
} for i in self.request.event.items.filter(active=True).prefetch_related('variations')
|
||||
} for i in self.request.event.items.prefetch_related('variations')
|
||||
],
|
||||
**super().get_context_data(),
|
||||
}
|
||||
@@ -532,6 +533,8 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
|
||||
checkinlist=self.list,
|
||||
result=self.result,
|
||||
reason_labels=dict(Checkin.REASONS),
|
||||
media_policies=dict(Item.MEDIA_POLICIES),
|
||||
media_types=dict(MEDIA_TYPES),
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
@@ -97,7 +97,9 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views.mailsetup import MailSettingsSetupView
|
||||
from pretix.control.views.user import RecentAuthenticationRequiredMixin
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
eventreverse_absolute, get_event_domain,
|
||||
)
|
||||
from pretix.presale.views.widget import (
|
||||
version_default as widget_version_default,
|
||||
)
|
||||
@@ -1148,8 +1150,11 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
|
||||
if request.POST.get("delete") == "yes":
|
||||
try:
|
||||
with transaction.atomic():
|
||||
for order in request.event.orders.filter(testmode=True):
|
||||
order.gracefully_delete(user=self.request.user)
|
||||
Order.gracefully_delete_bulk(
|
||||
request.event,
|
||||
request.event.orders.filter(testmode=True),
|
||||
user=self.request.user
|
||||
)
|
||||
except ProtectedError:
|
||||
messages.error(self.request, _('An order could not be deleted as some constraints (e.g. data '
|
||||
'created by plug-ins) do not allow it.'))
|
||||
@@ -1734,7 +1739,7 @@ class EventQRCode(EventPermissionRequiredMixin, View):
|
||||
permission = None
|
||||
|
||||
def get(self, request, *args, filetype, **kwargs):
|
||||
url = build_absolute_uri(request.event, 'presale:event.index')
|
||||
url = eventreverse_absolute(request.event, 'presale:event.index')
|
||||
|
||||
if "url" in request.GET:
|
||||
if url_has_allowed_host_and_scheme(request.GET["url"], allowed_hosts=[urlparse(url).netloc]):
|
||||
|
||||
@@ -139,6 +139,7 @@ from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.helpers.iter import chunked_iterable
|
||||
from pretix.helpers.json import CustomJSONEncoder
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
@@ -240,7 +241,7 @@ class BaseOrderBulkActionView(OrderSearchMixin, EventPermissionRequiredMixin, As
|
||||
raise NotImplementedError()
|
||||
|
||||
def execute_bulk(self, queryset: QuerySet, form: forms.Form):
|
||||
qs = self.allowed_for(self.allowed_for(self.get_queryset()))
|
||||
qs = self.allowed_for(self.get_queryset())
|
||||
total = qs.count()
|
||||
orders_with_successful_action = 0
|
||||
for i, o in enumerate(qs):
|
||||
@@ -394,8 +395,21 @@ class OrderDeleteBulkActionView(BaseOrderBulkActionView):
|
||||
testmode=True,
|
||||
)
|
||||
|
||||
def execute_single(self, instance, form: forms.Form):
|
||||
instance.gracefully_delete(user=self.request.user)
|
||||
def execute_bulk(self, queryset: QuerySet, form: forms.Form):
|
||||
qs = self.allowed_for(self.get_queryset())
|
||||
total = qs.count()
|
||||
all_ids = list(qs.values_list("id", flat=True))
|
||||
|
||||
orders_with_successful_action = 0
|
||||
for chunk in chunked_iterable(all_ids, 1000):
|
||||
Order.gracefully_delete_bulk(
|
||||
self.request.event,
|
||||
qs.filter(id__in=chunk),
|
||||
user=self.request.user,
|
||||
)
|
||||
orders_with_successful_action += len(chunk)
|
||||
self.async_set_progress(orders_with_successful_action / total * 100)
|
||||
return orders_with_successful_action, total
|
||||
|
||||
|
||||
class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
|
||||
@@ -139,8 +139,8 @@ from pretix.helpers import OF_SELF, GroupConcat
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1039,7 +1039,7 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
||||
'user': self,
|
||||
'organizer': self.request.organizer.name,
|
||||
'team': instance.team.name,
|
||||
'url': build_global_uri('control:auth.invite', kwargs={
|
||||
'url': mainreverse_absolute('control:auth.invite', kwargs={
|
||||
'token': instance.token
|
||||
})
|
||||
},
|
||||
@@ -2851,10 +2851,12 @@ class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['redirect_uri'] = build_absolute_uri(self.request.organizer, 'presale:organizer.customer.login.return',
|
||||
kwargs={
|
||||
'provider': self.object.pk
|
||||
})
|
||||
ctx['redirect_uri'] = eventreverse_absolute(
|
||||
self.request.organizer, 'presale:organizer.customer.login.return',
|
||||
kwargs={
|
||||
'provider': self.object.pk
|
||||
}
|
||||
)
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self):
|
||||
@@ -3085,7 +3087,7 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
self.customer.log_action('pretix.customer.password.resetrequested', {}, user=self.request.user)
|
||||
ctx = self.customer.get_email_context()
|
||||
token = TokenGenerator().make_token(self.customer)
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.request.organizer,
|
||||
'presale:organizer.customer.recoverpw'
|
||||
) + '?id=' + self.customer.identifier + '&token=' + token
|
||||
|
||||
@@ -200,7 +200,7 @@ def giftcard_select2(request, **kwargs):
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:write', request):
|
||||
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:read', request):
|
||||
qs = request.organizer.issued_gift_cards.filter(
|
||||
Q(secret__icontains=query)
|
||||
).order_by('secret')
|
||||
|
||||
@@ -77,7 +77,7 @@ from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
@@ -338,7 +338,7 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
}
|
||||
if self.object.subevent_id:
|
||||
url_params['subevent'] = self.object.subevent_id
|
||||
ctx['url'] = build_absolute_uri(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
|
||||
ctx['url'] = eventreverse_absolute(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ from geoip2.errors import AddressNotFoundError
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.helpers.http import get_client_ip
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -175,7 +175,7 @@ def handle_login_source(user, request):
|
||||
'source': src,
|
||||
'country': Country(str(country)).name if country else _('Unknown country'),
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'url': build_absolute_uri('control:user.settings')
|
||||
'url': mainreverse_absolute('control:user.settings')
|
||||
},
|
||||
event=None,
|
||||
user=user,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import warnings
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
@@ -26,6 +27,15 @@ from django.urls import reverse
|
||||
|
||||
|
||||
def build_absolute_uri(urlname, args=None, kwargs=None):
|
||||
warnings.warn(
|
||||
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
||||
'Replace this usage with ',
|
||||
DeprecationWarning
|
||||
)
|
||||
return mainreverse_absolute(urlname, args, kwargs)
|
||||
|
||||
|
||||
def mainreverse_absolute(urlname, args=None, kwargs=None):
|
||||
from pretix.multidomain import maindomain_urlconf
|
||||
|
||||
return urljoin(settings.SITE_URL, reverse(urlname, args=args, kwargs=kwargs, urlconf=maindomain_urlconf))
|
||||
|
||||
@@ -4,16 +4,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
|
||||
"PO-Revision-Date: 2026-05-22 07:03+0000\n"
|
||||
"PO-Revision-Date: 2026-06-20 17:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
|
||||
">\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"da/>\n"
|
||||
"Language: da\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:166
|
||||
@@ -612,16 +612,17 @@ msgstr ""
|
||||
"underordnede objekter som f.eks. varianter eller pakker."
|
||||
|
||||
#: pretix/api/webhooks.py:413
|
||||
#, fuzzy
|
||||
#| msgid "Quota handling"
|
||||
msgid "Quota changed"
|
||||
msgstr "Håndtering af kvoter"
|
||||
msgstr "Kvoten er ændret"
|
||||
|
||||
#: pretix/api/webhooks.py:414
|
||||
msgid ""
|
||||
"This includes related events like creation, deletion, opening or closing of "
|
||||
"quotas. No webhook is sent for changes to the resulting availability."
|
||||
msgstr ""
|
||||
"Dette omfatter relaterede hændelser såsom oprettelse, sletning, åbning eller "
|
||||
"lukning af kvoter. Der sendes ingen webhook ved ændringer i den deraf "
|
||||
"følgende tilgængelighed."
|
||||
|
||||
#: pretix/api/webhooks.py:419
|
||||
msgid "Shop taken live"
|
||||
@@ -3394,12 +3395,12 @@ msgstr "Indtast venligst et kortere navn."
|
||||
msgid ""
|
||||
"The field \"%(label)s\" may not contain special characters such as "
|
||||
"\"%(chars)s\"."
|
||||
msgstr ""
|
||||
msgstr "Feltet \"%(label)s\" må ikke indeholde specialtegn såsom \"%(chars)s\"."
|
||||
|
||||
#: pretix/base/forms/questions.py:305
|
||||
#, python-format
|
||||
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
|
||||
msgstr ""
|
||||
msgstr "Feltet \"%(label)s\" må ikke indeholde en URL (%(url)s)."
|
||||
|
||||
#: pretix/base/forms/questions.py:338
|
||||
msgctxt "phonenumber"
|
||||
@@ -8290,19 +8291,14 @@ msgid "Program times"
|
||||
msgstr "Programtider"
|
||||
|
||||
#: pretix/base/pdf.py:503
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "2017-05-31 10:00 – 12:00\n"
|
||||
#| "2017-05-31 14:00 – 16:00\n"
|
||||
#| "2017-05-31 14:00 – 2017-06-01 14:00"
|
||||
msgid ""
|
||||
"2017-05-31 10:00 – 12:00, Room 1\n"
|
||||
"2017-05-31 14:00 – 16:00, Room 2\n"
|
||||
"2017-05-31 14:00 – 2017-06-01 14:00, Building A"
|
||||
msgstr ""
|
||||
"31. maj 2017 kl. 10.00 – 12.00\n"
|
||||
"31. maj 2017 kl. 14.00 – 16.00\n"
|
||||
"31. maj 2017 kl. 14.00 – 1. juni 2017 kl. 14.00"
|
||||
"31. maj 2017 kl. 10.00 – 12.00, lokale 1\n"
|
||||
"31. maj 2017 kl. 14.00 – 16.00, lokale 2\n"
|
||||
"31. maj 2017 kl. 14.00 – 1. juni 2017 kl. 14.00, bygning A"
|
||||
|
||||
#: pretix/base/pdf.py:507
|
||||
msgid "Reusable Medium ID"
|
||||
@@ -8827,13 +8823,7 @@ msgid "This voucher code is not known in our database."
|
||||
msgstr "Denne voucherkode genkendes ikke."
|
||||
|
||||
#: pretix/base/services/cart.py:165
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
#| "%(number)s matching products."
|
||||
#| msgid_plural ""
|
||||
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
#| "%(number)s matching products."
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
"%(number)s matching product."
|
||||
@@ -8841,22 +8831,14 @@ msgid_plural ""
|
||||
"The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
"%(number)s matching products."
|
||||
msgstr[0] ""
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst "
|
||||
"%(number)s passende produkter."
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst %"
|
||||
"(number)s passende produkter."
|
||||
msgstr[1] ""
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst "
|
||||
"%(number)s passende produkter."
|
||||
"Voucherkoderne \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst %"
|
||||
"(number)s passende produkter."
|
||||
|
||||
#: pretix/base/services/cart.py:170
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
#| "%(number)s matching products. We have therefore removed some positions "
|
||||
#| "from your cart that can no longer be purchased like this."
|
||||
#| msgid_plural ""
|
||||
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
#| "%(number)s matching products. We have therefore removed some positions "
|
||||
#| "from your cart that can no longer be purchased like this."
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
"%(number)s matching product. We have therefore removed some positions from "
|
||||
@@ -8866,12 +8848,12 @@ msgid_plural ""
|
||||
"%(number)s matching products. We have therefore removed some positions from "
|
||||
"your cart that can no longer be purchased like this."
|
||||
msgstr[0] ""
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst "
|
||||
"%(number)s passende produkter. Vi har derfor fjernet nogle poster fra din "
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst %"
|
||||
"(number)s passende produkter. Vi har derfor fjernet nogle poster fra din "
|
||||
"indkøbskurv, som ikke længere kan blive købt på denne måde."
|
||||
msgstr[1] ""
|
||||
"Voucherkoden \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst "
|
||||
"%(number)s passende produkter. Vi har derfor fjernet nogle poster fra din "
|
||||
"Voucherkoderne \"%(voucher)s\" kan kun blive brugt, hvis du vælger mindst %"
|
||||
"(number)s passende produkter. Vi har derfor fjernet nogle poster fra din "
|
||||
"indkøbskurv, som ikke længere kan blive købt på denne måde."
|
||||
|
||||
#: pretix/base/services/cart.py:176
|
||||
@@ -14105,6 +14087,8 @@ msgid ""
|
||||
"You entered an URL, which is not allowed. Please remove %(match)s from your "
|
||||
"input."
|
||||
msgstr ""
|
||||
"Du har indtastet en URL, som ikke er tilladt. Fjern venligst %(match)s fra "
|
||||
"din indtastning."
|
||||
|
||||
#: pretix/base/views/errors.py:48
|
||||
msgid ""
|
||||
@@ -16027,14 +16011,8 @@ msgid "inactive"
|
||||
msgstr "inaktiv"
|
||||
|
||||
#: pretix/control/forms/item.py:1414
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Sample Conference Center\n"
|
||||
#| "Heidelberg, Germany"
|
||||
msgid "Sample Conference Center, Heidelberg, Germany"
|
||||
msgstr ""
|
||||
"Eksempelkonferencecenter\n"
|
||||
"Andeby, Danmark"
|
||||
msgstr "Eksempelkonferencecenter, Århus, Danmark"
|
||||
|
||||
#: pretix/control/forms/mailsetup.py:42
|
||||
msgid "Hostname"
|
||||
@@ -17819,12 +17797,11 @@ msgstr ""
|
||||
"afvist."
|
||||
|
||||
#: pretix/control/logdisplay.py:571
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"An email has been sent to notify the user that the order has been approved."
|
||||
msgstr ""
|
||||
"En e-mail er blevet sendt for at fortælle brugeren at bestillingen er "
|
||||
"modtaget."
|
||||
"Der er sendt en e-mail for at informere brugeren om, at ordren er blevet "
|
||||
"godkendt."
|
||||
|
||||
#: pretix/control/logdisplay.py:574
|
||||
msgid ""
|
||||
@@ -17835,13 +17812,12 @@ msgstr ""
|
||||
"modtaget og kræver betaling."
|
||||
|
||||
#: pretix/control/logdisplay.py:575
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"An email has been sent to notify the user that the order has been received "
|
||||
"and requires approval."
|
||||
msgstr ""
|
||||
"En e-mail er blevet sendt for at fortælle brugeren at bestillingen er "
|
||||
"modtaget og kræver betaling."
|
||||
"Der er sendt en e-mail for at informere brugeren om, at ordren er modtaget "
|
||||
"og skal godkendes."
|
||||
|
||||
#: pretix/control/logdisplay.py:578
|
||||
msgid ""
|
||||
@@ -17851,21 +17827,22 @@ msgstr ""
|
||||
"bestillingsdetaljer."
|
||||
|
||||
#: pretix/control/logdisplay.py:579
|
||||
#, fuzzy
|
||||
msgid "An email has been sent to notify the user that the payment failed."
|
||||
msgstr ""
|
||||
"En e-mail er blevet sendt for at fortælle brugeren at betaling er modtaget."
|
||||
"Der er sendt en e-mail for at informere brugeren om, at betalingen "
|
||||
"mislykkedes."
|
||||
|
||||
#: pretix/control/logdisplay.py:586
|
||||
msgid "The voucher has been created."
|
||||
msgstr "Voucheren er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:589
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"The voucher has been set to expire because the recipient removed themselves "
|
||||
"from the waiting list."
|
||||
msgstr "En rabatkode er blevet sendt til en person på ventelisten."
|
||||
msgstr ""
|
||||
"Kuponen er sat til at udløbe, fordi modtageren har fjernet sig selv fra "
|
||||
"ventelisten."
|
||||
|
||||
#: pretix/control/logdisplay.py:590
|
||||
msgid "The voucher has been changed."
|
||||
@@ -17876,16 +17853,13 @@ msgid "The voucher has been deleted."
|
||||
msgstr "Voucheren er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:592
|
||||
#, fuzzy
|
||||
#| msgid "The selected voucher has been deleted."
|
||||
msgid "Cart positions including the voucher have been deleted."
|
||||
msgstr "Den valgte rabatkode er blevet slettet."
|
||||
msgstr "Indkøbskurvens varer, herunder gavekortet, er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:593
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "A voucher has been sent to a person on the waiting list."
|
||||
#, python-brace-format
|
||||
msgid "The voucher has been assigned to {email} through the waiting list."
|
||||
msgstr "En rabatkode er blevet sendt til en person på ventelisten."
|
||||
msgstr "Kuponen er blevet tildelt {email} via ventelisten."
|
||||
|
||||
#: pretix/control/logdisplay.py:602
|
||||
#, python-brace-format
|
||||
@@ -17905,9 +17879,8 @@ msgid "The category has been changed."
|
||||
msgstr "Kategorien er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:620
|
||||
#, fuzzy
|
||||
msgid "The category has been reordered."
|
||||
msgstr "Kategorien er blevet slettet."
|
||||
msgstr "Kategorien er blevet omorganiseret."
|
||||
|
||||
#: pretix/control/logdisplay.py:627
|
||||
msgid "The tax rule has been added."
|
||||
@@ -17937,14 +17910,14 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} er blevet inviteret med i gruppen."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "Invitationen til {user} er blevet trukket tilbage."
|
||||
msgstr "Invitationen til {user} er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been resent."
|
||||
msgstr "Invitationen til {user} er blevet trukket tilbage."
|
||||
msgstr "Invitationen til {user} er blevet sendt igen."
|
||||
|
||||
#: pretix/control/logdisplay.py:656
|
||||
#, python-brace-format
|
||||
@@ -17962,294 +17935,245 @@ msgstr "Din e-mailadresse er blevet ændret til {email}."
|
||||
|
||||
#: pretix/control/logdisplay.py:673
|
||||
msgid "Your account has been enabled."
|
||||
msgstr ""
|
||||
msgstr "Din konto er nu aktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:675
|
||||
msgid "Your account has been disabled."
|
||||
msgstr ""
|
||||
msgstr "Din konto er blevet deaktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:680 pretix/presale/views/customer.py:642
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid ""
|
||||
#| "The email address has been changed from \"{old_email}\" to \"{new_email}"
|
||||
#| "\"."
|
||||
#, python-brace-format
|
||||
msgid "Your email address has been changed from {old_email} to {email}."
|
||||
msgstr ""
|
||||
"E-mailadressen er blevet ændret fra \"{old_email}\" til \"{new_email}\"."
|
||||
msgstr "Din e-mailadresse er blevet ændret fra {old_email} til {email}."
|
||||
|
||||
#: pretix/control/logdisplay.py:681
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Your email address {email} has been confirmed."
|
||||
msgstr "Din kurv er blevet opdateret."
|
||||
msgstr "Din e-mailadresse {email} er blevet bekræftet."
|
||||
|
||||
#: pretix/control/logdisplay.py:693
|
||||
#, python-brace-format
|
||||
msgid "You impersonated {}."
|
||||
msgstr ""
|
||||
msgstr "Du udgav dig for at være {}."
|
||||
|
||||
#: pretix/control/logdisplay.py:694
|
||||
#, python-brace-format
|
||||
msgid "You stopped impersonating {}."
|
||||
msgstr ""
|
||||
msgstr "Du er holdt op med at udgive dig for at være {}."
|
||||
|
||||
#: pretix/control/logdisplay.py:701
|
||||
msgid "This object has been created by cloning."
|
||||
msgstr "Dette objekt er blevet oprettet ved kloning"
|
||||
|
||||
#: pretix/control/logdisplay.py:702
|
||||
#, fuzzy
|
||||
msgid "The organizer has been changed."
|
||||
msgstr "Bestillingen er blevet ændret."
|
||||
msgstr "Arrangøren er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:703
|
||||
#, fuzzy
|
||||
msgid "The organizer settings have been changed."
|
||||
msgstr "Gruppeindstillingerne er blevet ændret."
|
||||
msgstr "Indstillingerne for arrangøren er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:704 pretix/control/logdisplay.py:849
|
||||
#, fuzzy
|
||||
msgid "The footer links have been changed."
|
||||
msgstr "Bestillingsdetaljerne er blevet ændret."
|
||||
msgstr "Linkene i sidefoden er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:705 pretix/control/logdisplay.py:754
|
||||
#, fuzzy
|
||||
msgid "A scheduled export has been added."
|
||||
msgstr "Kvoten er blevet tilføjet."
|
||||
msgstr "Der er tilføjet en planlagt eksport."
|
||||
|
||||
#: pretix/control/logdisplay.py:706 pretix/control/logdisplay.py:755
|
||||
#, fuzzy
|
||||
msgid "A scheduled export has been changed."
|
||||
msgstr "Produktet er blevet ændret."
|
||||
msgstr "En planlagt eksport er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:707 pretix/control/logdisplay.py:756
|
||||
#, fuzzy
|
||||
msgid "A scheduled export has been deleted."
|
||||
msgstr "Det valgte produkt er blevet slettet."
|
||||
msgstr "En planlagt eksport er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:708 pretix/control/logdisplay.py:757
|
||||
#, fuzzy
|
||||
msgid "A scheduled export has been executed."
|
||||
msgstr "Det valgte produkt er blevet slettet."
|
||||
msgstr "En planlagt eksport er blevet udført."
|
||||
|
||||
#: pretix/control/logdisplay.py:709 pretix/control/logdisplay.py:758
|
||||
#, python-brace-format
|
||||
msgid "A scheduled export has failed: {reason}."
|
||||
msgstr ""
|
||||
msgstr "En planlagt eksport er mislykket: {reason}."
|
||||
|
||||
#: pretix/control/logdisplay.py:710
|
||||
#, fuzzy
|
||||
#| msgid "The email has been queued to be sent."
|
||||
msgid "Failed emails have been scheduled to be retried."
|
||||
msgstr "Denne e-mail er sat i udsendelseskøen."
|
||||
msgstr ""
|
||||
"Der er planlagt et nyt forsøg på at sende de e-mails, der ikke kunne leveres."
|
||||
|
||||
#: pretix/control/logdisplay.py:711
|
||||
#, fuzzy
|
||||
#| msgid "The order details have been changed."
|
||||
msgid "Queued emails have been aborted."
|
||||
msgstr "Bestillingsdetaljerne er blevet ændret."
|
||||
msgstr "E-mails i køen er blevet afbrudt."
|
||||
|
||||
#: pretix/control/logdisplay.py:712
|
||||
#, fuzzy
|
||||
msgid "Gift card acceptance for another organizer has been added."
|
||||
msgstr "Den nye arrangør er blevet oprettet."
|
||||
msgstr ""
|
||||
"Der er nu tilføjet mulighed for at acceptere gavekort fra en anden arrangør."
|
||||
|
||||
#: pretix/control/logdisplay.py:713
|
||||
#, fuzzy
|
||||
msgid "Gift card acceptance for another organizer has been removed."
|
||||
msgstr "Den nye arrangør er blevet oprettet."
|
||||
msgstr ""
|
||||
"Muligheden for at modtage gavekort fra en anden arrangør er blevet fjernet."
|
||||
|
||||
#: pretix/control/logdisplay.py:714
|
||||
#, fuzzy
|
||||
msgid "A new gift card acceptor has been invited."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Der er blevet inviteret en ny modtager af gavekort."
|
||||
|
||||
#: pretix/control/logdisplay.py:715
|
||||
#, fuzzy
|
||||
msgid "A gift card acceptor has been removed."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "En gavekort-modtager er blevet fjernet."
|
||||
|
||||
#: pretix/control/logdisplay.py:716
|
||||
#, fuzzy
|
||||
msgid "A gift card issuer has been removed or declined."
|
||||
msgstr "Den valgte kategorien er blevet slettet."
|
||||
msgstr "En udsteder af gavekort er blevet fjernet eller afvist."
|
||||
|
||||
#: pretix/control/logdisplay.py:717
|
||||
#, fuzzy
|
||||
msgid "A new gift card issuer has been accepted."
|
||||
msgstr "Den valgte kategorien er blevet slettet."
|
||||
msgstr "En ny udsteder af gavekort er blevet godkendt."
|
||||
|
||||
#: pretix/control/logdisplay.py:718
|
||||
#, fuzzy
|
||||
msgid "The webhook has been created."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Webhooken er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:719
|
||||
#, fuzzy
|
||||
msgid "The webhook has been changed."
|
||||
msgstr "Kategorien er blevet ændret."
|
||||
msgstr "Webhooken er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:720
|
||||
msgid "The webhook call retry jobs have been manually expedited."
|
||||
msgstr ""
|
||||
"Opgaverne vedrørende gentagelse af webhook-kald er blevet fremskyndet "
|
||||
"manuelt."
|
||||
|
||||
#: pretix/control/logdisplay.py:721
|
||||
#, fuzzy
|
||||
msgid "The webhook call retry jobs have been dropped."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Opgaverne til gentagelse af webhook-kald er blevet annulleret."
|
||||
|
||||
#: pretix/control/logdisplay.py:722
|
||||
#, fuzzy
|
||||
msgid "The SSO provider has been created."
|
||||
msgstr "Bestilling oprettet."
|
||||
msgstr "SSO-udbyderen er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:723
|
||||
#, fuzzy
|
||||
msgid "The SSO provider has been changed."
|
||||
msgstr "Bestillingen er blevet ændret."
|
||||
msgstr "SSO-udbyderen er blevet skiftet."
|
||||
|
||||
#: pretix/control/logdisplay.py:724
|
||||
#, fuzzy
|
||||
msgid "The SSO provider has been deleted."
|
||||
msgstr "Bestillingen er blevet tilbagebetalt."
|
||||
msgstr "SSO-udbyderen er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:725
|
||||
#, fuzzy
|
||||
msgid "The SSO client has been created."
|
||||
msgstr "Arrangementsdatoen er blevet oprettet."
|
||||
msgstr "SSO-klienten er oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:726
|
||||
#, fuzzy
|
||||
msgid "The SSO client has been changed."
|
||||
msgstr "Kategorien er blevet ændret."
|
||||
msgstr "SSO-klienten er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:727
|
||||
#, fuzzy
|
||||
msgid "The SSO client has been deleted."
|
||||
msgstr "Produktet er blevet slettet."
|
||||
msgstr "SSO-klienten er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:728 pretix/control/views/organizer.py:2715
|
||||
#, fuzzy
|
||||
msgid "The membership type has been created."
|
||||
msgstr "Arrangementsdatoen er blevet oprettet."
|
||||
msgstr "Medlemskabstypen er oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:729
|
||||
#, fuzzy
|
||||
msgid "The membership type has been changed."
|
||||
msgstr "Arrangementsdatoen er blevet ændret."
|
||||
msgstr "Medlemskabstypen er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:730
|
||||
#, fuzzy
|
||||
msgid "The membership type has been deleted."
|
||||
msgstr "Arrangementsdatoen er blevet slettet."
|
||||
msgstr "Medlemskabstypen er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:731 pretix/control/views/organizer.py:3556
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "The new date has been created."
|
||||
msgid "The sales channel has been created."
|
||||
msgstr "Den nye dato er blevet oprettet."
|
||||
msgstr "Salgskanalen er oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:732
|
||||
#, fuzzy
|
||||
msgid "The sales channel has been changed."
|
||||
msgstr "Voucheren er blevet ændret."
|
||||
msgstr "Salgskanalen er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:733
|
||||
#, fuzzy
|
||||
#| msgid "The selected list has been deleted."
|
||||
msgid "The sales channel has been deleted."
|
||||
msgstr "Den valgte lister er blevet slettet."
|
||||
msgstr "Salgskanalen er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:734
|
||||
#, fuzzy
|
||||
msgid "The account has been created."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Kontoen er oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:735
|
||||
#, fuzzy
|
||||
msgid "The account has been changed."
|
||||
msgstr "Kategorien er blevet ændret."
|
||||
msgstr "Kontoen er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:736
|
||||
#, fuzzy
|
||||
msgid "A membership for this account has been added."
|
||||
msgstr "Den valgte konfiguration er blevet slettet."
|
||||
msgstr "Der er tilføjet et medlemskab til denne konto."
|
||||
|
||||
#: pretix/control/logdisplay.py:737
|
||||
#, fuzzy
|
||||
msgid "A membership of this account has been changed."
|
||||
msgstr "En svarmulighed er blevet ændret."
|
||||
msgstr "Et medlemskab på denne konto er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:738
|
||||
#, fuzzy
|
||||
msgid "A membership of this account has been deleted."
|
||||
msgstr "Den valgte konfiguration er blevet slettet."
|
||||
msgstr "Et medlemskab tilknyttet denne konto er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:739
|
||||
#, fuzzy
|
||||
msgid "The account has been disabled and anonymized."
|
||||
msgstr "Bestillingen er blevet tilbagebetalt."
|
||||
msgstr "Kontoen er blevet deaktiveret og anonymiseret."
|
||||
|
||||
#: pretix/control/logdisplay.py:740
|
||||
#, fuzzy
|
||||
msgid "A new password has been requested."
|
||||
msgstr "Adgangskoden er blevet nulstillet."
|
||||
msgstr "Der er anmodet om en ny adgangskode."
|
||||
|
||||
#: pretix/control/logdisplay.py:741
|
||||
#, fuzzy
|
||||
msgid "A new password has been set."
|
||||
msgstr "Adgangskoden er blevet nulstillet."
|
||||
msgstr "Der er blevet oprettet en ny adgangskode."
|
||||
|
||||
#: pretix/control/logdisplay.py:743
|
||||
#, fuzzy
|
||||
msgid "The reusable medium has been created."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Det genanvendelige medie er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:744
|
||||
#, fuzzy
|
||||
msgid "The reusable medium has been created automatically."
|
||||
msgstr "Arrangementsdatoen er blevet oprettet."
|
||||
msgstr "Det genanvendelige medie er blevet oprettet automatisk."
|
||||
|
||||
#: pretix/control/logdisplay.py:745
|
||||
#, fuzzy
|
||||
msgid "The reusable medium has been changed."
|
||||
msgstr "Momsreglen er blevet ændret."
|
||||
msgstr "Det genanvendelige medie er blevet udskiftet."
|
||||
|
||||
#: pretix/control/logdisplay.py:746
|
||||
#, fuzzy
|
||||
msgid "The medium has been connected to a new ticket."
|
||||
msgstr "Denne e-mail er sat i udsendelseskøen."
|
||||
msgstr "Mediet er blevet knyttet til en ny sag."
|
||||
|
||||
#: pretix/control/logdisplay.py:747
|
||||
#, fuzzy
|
||||
msgid "The medium has been connected to a new gift card."
|
||||
msgstr "Denne e-mail er sat i udsendelseskøen."
|
||||
msgstr "Mediet er blevet knyttet til et nyt gavekort."
|
||||
|
||||
#: pretix/control/logdisplay.py:749
|
||||
msgid "The event's internal comment has been updated."
|
||||
msgstr "Intern kommentar opdateret."
|
||||
|
||||
#: pretix/control/logdisplay.py:750
|
||||
#, fuzzy
|
||||
msgid "The event has been canceled."
|
||||
msgstr "Bestillingen er blevet annulleret."
|
||||
msgstr "Arrangementet er blevet aflyst."
|
||||
|
||||
#: pretix/control/logdisplay.py:751
|
||||
#, fuzzy
|
||||
msgid "An event has been deleted."
|
||||
msgstr "Arrangementsdatoen er blevet slettet."
|
||||
msgstr "En begivenhed er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:752
|
||||
msgid "A removal process for personal data has been started."
|
||||
msgstr ""
|
||||
msgstr "Der er iværksat en sletningsproces for personoplysninger."
|
||||
|
||||
#: pretix/control/logdisplay.py:753
|
||||
msgid "A removal process for personal data has been completed."
|
||||
msgstr ""
|
||||
msgstr "En sletning af personoplysninger er blevet gennemført."
|
||||
|
||||
#: pretix/control/logdisplay.py:759
|
||||
msgid "The user has been created."
|
||||
msgstr ""
|
||||
msgstr "Brugeren er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:760
|
||||
#, python-brace-format
|
||||
@@ -18257,6 +18181,8 @@ msgid ""
|
||||
"A first login using {agent_type} on {os_type} from {country} has been "
|
||||
"detected."
|
||||
msgstr ""
|
||||
"Der er registreret en første login med {agent_type} på {os_type} fra "
|
||||
"{country}."
|
||||
|
||||
#: pretix/control/logdisplay.py:762 pretix/control/views/user.py:474
|
||||
#: pretix/control/views/user.py:534 pretix/control/views/user.py:593
|
||||
@@ -18273,9 +18199,8 @@ msgid "Your two-factor emergency codes have been regenerated."
|
||||
msgstr "Dine sikkerhedskoder til tofaktorgodkendelse er blevet genoprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:765
|
||||
#, fuzzy
|
||||
msgid "A two-factor emergency code has been generated."
|
||||
msgstr "Dine sikkerhedskoder til tofaktorgodkendelse er blevet genoprettet."
|
||||
msgstr "Der er genereret en tofaktorkode til nødsituationer."
|
||||
|
||||
#: pretix/control/logdisplay.py:766
|
||||
#, python-brace-format
|
||||
@@ -18295,20 +18220,19 @@ msgstr ""
|
||||
|
||||
#: pretix/control/logdisplay.py:770
|
||||
msgid "Notifications have been enabled."
|
||||
msgstr ""
|
||||
msgstr "Notifikationer er blevet aktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:771
|
||||
msgid "Notifications have been disabled."
|
||||
msgstr ""
|
||||
msgstr "Notifikationer er blevet deaktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:772
|
||||
msgid "Your notification settings have been changed."
|
||||
msgstr ""
|
||||
msgstr "Dine notifikationsindstillinger er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:773
|
||||
#, fuzzy
|
||||
msgid "This user has been anonymized."
|
||||
msgstr "Bestillingen er blevet tilbagebetalt."
|
||||
msgstr "Denne bruger er blevet anonymiseret."
|
||||
|
||||
#: pretix/control/logdisplay.py:777
|
||||
msgid "Password reset mail sent."
|
||||
@@ -18323,20 +18247,21 @@ msgid ""
|
||||
"A repeated password reset has been denied, as the last request was less than "
|
||||
"24 hours ago."
|
||||
msgstr ""
|
||||
"En gentagen anmodning om nulstilling af adgangskode er blevet afvist, da den "
|
||||
"seneste anmodning blev fremsat for mindre end 24 timer siden."
|
||||
|
||||
#: pretix/control/logdisplay.py:781
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "The organizer \"{name}\" has been deleted."
|
||||
msgstr "Varianten \"{value}\" er blevet slettet."
|
||||
msgstr "Arrangøren \"{name}\" er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:782 pretix/control/logdisplay.py:1013
|
||||
msgid "A voucher has been sent to a person on the waiting list."
|
||||
msgstr "En rabatkode er blevet sendt til en person på ventelisten."
|
||||
|
||||
#: pretix/control/logdisplay.py:783
|
||||
#, fuzzy
|
||||
msgid "An entry has been transferred to another waiting list."
|
||||
msgstr "En bruger er føjet til gruppen."
|
||||
msgstr "En ansøgning er blevet flyttet til en anden venteliste."
|
||||
|
||||
#: pretix/control/logdisplay.py:784
|
||||
msgid "The team has been created."
|
||||
@@ -18351,19 +18276,16 @@ msgid "The team has been deleted."
|
||||
msgstr "Gruppen er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:787 pretix/control/views/organizer.py:2381
|
||||
#, fuzzy
|
||||
msgid "The gate has been created."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Porten er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:788
|
||||
#, fuzzy
|
||||
msgid "The gate has been changed."
|
||||
msgstr "Kategorien er blevet ændret."
|
||||
msgstr "Porten er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:789
|
||||
#, fuzzy
|
||||
msgid "The gate has been deleted."
|
||||
msgstr "Gruppen er blevet slettet."
|
||||
msgstr "Porten er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:790
|
||||
msgctxt "subevent"
|
||||
@@ -18371,10 +18293,9 @@ msgid "The event date has been deleted."
|
||||
msgstr "Arrangementsdatoen er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:791
|
||||
#, fuzzy
|
||||
msgctxt "subevent"
|
||||
msgid "The event date has been canceled."
|
||||
msgstr "Arrangementsdatoen er blevet ændret."
|
||||
msgstr "Begivenhedsdatoen er blevet aflyst."
|
||||
|
||||
#: pretix/control/logdisplay.py:792
|
||||
msgctxt "subevent"
|
||||
@@ -18402,58 +18323,50 @@ msgid "A quota has been removed from the event date."
|
||||
msgstr "En kvote er blevet fjernet fra arrangementsdatoen."
|
||||
|
||||
#: pretix/control/logdisplay.py:797
|
||||
#, fuzzy
|
||||
msgid "The device has been created."
|
||||
msgstr "Arrangementsdatoen er blevet oprettet."
|
||||
msgstr "Enheden er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:798
|
||||
#, fuzzy
|
||||
msgid "The device has been changed."
|
||||
msgstr "Voucheren er blevet ændret."
|
||||
msgstr "Enheden er blevet udskiftet."
|
||||
|
||||
#: pretix/control/logdisplay.py:799
|
||||
#, fuzzy
|
||||
msgid "Access of the device has been revoked."
|
||||
msgstr "Enheden er blevet fjernet."
|
||||
msgstr "Adgangen til enheden er blevet tilbagekaldt."
|
||||
|
||||
#: pretix/control/logdisplay.py:800
|
||||
#, fuzzy
|
||||
msgid "The device has been initialized."
|
||||
msgstr "Enheden er blevet fjernet."
|
||||
msgstr "Enheden er blevet initialiseret."
|
||||
|
||||
#: pretix/control/logdisplay.py:801
|
||||
#, fuzzy
|
||||
msgid "The access token of the device has been regenerated."
|
||||
msgstr "Fakturaen er blevet genereret igen."
|
||||
msgstr "Enhedens adgangstoken er blevet genereret på ny."
|
||||
|
||||
#: pretix/control/logdisplay.py:802
|
||||
msgid "The device has notified the server of an hardware or software update."
|
||||
msgstr ""
|
||||
"Enheden har meddelt serveren, at der er en hardware- eller "
|
||||
"softwareopdatering."
|
||||
|
||||
#: pretix/control/logdisplay.py:803
|
||||
#, fuzzy
|
||||
msgid "The gift card has been created."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Gavekortet er oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:804 pretix/control/views/organizer.py:1943
|
||||
#, fuzzy
|
||||
msgid "The gift card has been changed."
|
||||
msgstr "Gruppen er blevet oprettet."
|
||||
msgstr "Gavekortet er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:805
|
||||
#, fuzzy
|
||||
msgid "A manual transaction has been performed."
|
||||
msgstr "Spørgsmålet er blevet tilføjet."
|
||||
msgstr "Der er udført en manuel overførsel."
|
||||
|
||||
#: pretix/control/logdisplay.py:806
|
||||
#, fuzzy
|
||||
msgid "A payment has been performed."
|
||||
msgstr "Betalingsbetingelser er blevet ændret."
|
||||
msgstr "Der er blevet foretaget en betaling."
|
||||
|
||||
#: pretix/control/logdisplay.py:807
|
||||
#, fuzzy
|
||||
msgid "A refund has been performed. "
|
||||
msgstr "Bestilling oprettet."
|
||||
msgstr "Der er blevet foretaget en tilbagebetaling. "
|
||||
|
||||
#: pretix/control/logdisplay.py:808
|
||||
#, python-brace-format
|
||||
@@ -18466,44 +18379,34 @@ msgid "The token \"{name}\" has been revoked."
|
||||
msgstr "Token \"{name}\" slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:810
|
||||
#, fuzzy
|
||||
#| msgid "The check-in list has been deleted."
|
||||
msgid "The check-in and print log state has been reset."
|
||||
msgstr "Tjek-ind-listen er blevet slettet."
|
||||
msgstr "Status for indtjeknings- og udskriftsloggen er blevet nulstillet."
|
||||
|
||||
#: pretix/control/logdisplay.py:817 pretix/control/logdisplay.py:876
|
||||
#, fuzzy
|
||||
#| msgid "A plugin has been enabled."
|
||||
msgid "The plugin has been enabled."
|
||||
msgstr "En plugin er blevet aktiveret."
|
||||
msgstr "Plugin'et er blevet aktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:818 pretix/control/logdisplay.py:877
|
||||
#: pretix/control/views/event.py:497 pretix/control/views/organizer.py:757
|
||||
#, fuzzy
|
||||
#| msgid "A plugin has been disabled."
|
||||
msgid "The plugin has been disabled."
|
||||
msgstr "En plugin er blevet deaktiveret."
|
||||
msgstr "Plugin'et er blevet deaktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:821 pretix/control/logdisplay.py:880
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Question {val}"
|
||||
#, python-brace-format
|
||||
msgid "Plugin {val}"
|
||||
msgstr "Spørgsmål {val}"
|
||||
msgstr "Plugin {val}"
|
||||
|
||||
#: pretix/control/logdisplay.py:836
|
||||
#, fuzzy
|
||||
msgid "A meta property has been added to this event."
|
||||
msgstr "En bruger er føjet til gruppen."
|
||||
msgstr "Der er tilføjet en metaegenskab til denne begivenhed."
|
||||
|
||||
#: pretix/control/logdisplay.py:837
|
||||
#, fuzzy
|
||||
msgid "A meta property has been removed from this event."
|
||||
msgstr "En bruger er blevet fjernet fra gruppen."
|
||||
msgstr "En metaegenskab er blevet fjernet fra denne begivenhed."
|
||||
|
||||
#: pretix/control/logdisplay.py:838
|
||||
#, fuzzy
|
||||
msgid "A meta property has been changed on this event."
|
||||
msgstr "En kvote er blevet ændret på arrangementsdatoen."
|
||||
msgstr "En metaegenskab er blevet ændret i forbindelse med denne begivenhed."
|
||||
|
||||
#: pretix/control/logdisplay.py:839
|
||||
msgid "The event settings have been changed."
|
||||
@@ -18515,31 +18418,27 @@ msgstr "Indstillingerne for download af billet er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:843
|
||||
msgid "The shop has been taken live."
|
||||
msgstr ""
|
||||
msgstr "Butikken er nu gået live."
|
||||
|
||||
#: pretix/control/logdisplay.py:844
|
||||
msgid "The shop has been taken offline."
|
||||
msgstr ""
|
||||
msgstr "Butikken er blevet taget offline."
|
||||
|
||||
#: pretix/control/logdisplay.py:845
|
||||
#, fuzzy
|
||||
msgid "The shop has been taken into test mode."
|
||||
msgstr "Bestillingen er blevet tilbagebetalt."
|
||||
msgstr "Butikken er nu sat i testtilstand."
|
||||
|
||||
#: pretix/control/logdisplay.py:846
|
||||
#, fuzzy
|
||||
msgid "The test mode has been disabled."
|
||||
msgstr "Den valgte dato er blevet slettet."
|
||||
msgstr "Testtilstanden er blevet deaktiveret."
|
||||
|
||||
#: pretix/control/logdisplay.py:847
|
||||
#, fuzzy
|
||||
msgid "The event has been created."
|
||||
msgstr "Arrangementsdatoen er blevet oprettet."
|
||||
msgstr "Begivenheden er blevet oprettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:848
|
||||
#, fuzzy
|
||||
msgid "The event details have been changed."
|
||||
msgstr "Indstillingerne for arrangementet er blevet ændret."
|
||||
msgstr "Begivenhedens oplysninger er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:850
|
||||
msgid "An answer option has been added to the question."
|
||||
@@ -18582,10 +18481,9 @@ msgid "The check-in list has been changed."
|
||||
msgstr "Tjek-ind-listen er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:869
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Check-in list"
|
||||
#, python-brace-format
|
||||
msgid "Check-in list {val}"
|
||||
msgstr "Tjek-ind-liste"
|
||||
msgstr "Tjek-ind-liste {val}"
|
||||
|
||||
#: pretix/control/logdisplay.py:896
|
||||
msgid "The product has been created."
|
||||
@@ -18596,9 +18494,8 @@ msgid "The product has been changed."
|
||||
msgstr "Produktet er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:898
|
||||
#, fuzzy
|
||||
msgid "The product has been reordered."
|
||||
msgstr "Produktet er blevet oprettet."
|
||||
msgstr "Produktet er blevet genbestilt."
|
||||
|
||||
#: pretix/control/logdisplay.py:899
|
||||
msgid "The product has been deleted."
|
||||
@@ -18617,37 +18514,28 @@ msgid "An add-on has been changed on this product."
|
||||
msgstr "En tilføjelse er blevet ændret på dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:903
|
||||
#, fuzzy
|
||||
msgid "A bundled item has been added to this product."
|
||||
msgstr "En tilføjelse er blevet føjet til dette produkt."
|
||||
msgstr "Der er blevet tilføjet en medfølgende vare til dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:904
|
||||
#, fuzzy
|
||||
msgid "A bundled item has been removed from this product."
|
||||
msgstr "En tilføjelse er blevet fjernet fra dette produkt."
|
||||
msgstr "En medfølgende vare er blevet fjernet fra dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:905
|
||||
#, fuzzy
|
||||
msgid "A bundled item has been changed on this product."
|
||||
msgstr "En tilføjelse er blevet ændret på dette produkt."
|
||||
msgstr "En medfølgende vare er blevet ændret for dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:906
|
||||
#, fuzzy
|
||||
#| msgid "An add-on has been added to this product."
|
||||
msgid "A program time has been added to this product."
|
||||
msgstr "En tilføjelse er blevet føjet til dette produkt."
|
||||
msgstr "Der er blevet tilføjet en programtid til dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:907
|
||||
#, fuzzy
|
||||
#| msgid "An add-on has been changed on this product."
|
||||
msgid "A program time has been changed on this product."
|
||||
msgstr "En tilføjelse er blevet ændret på dette produkt."
|
||||
msgstr "En programtid for dette produkt er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:908
|
||||
#, fuzzy
|
||||
#| msgid "An add-on has been removed from this product."
|
||||
msgid "A program time has been removed from this product."
|
||||
msgstr "En tilføjelse er blevet fjernet fra dette produkt."
|
||||
msgstr "En programtid er blevet fjernet fra dette produkt."
|
||||
|
||||
#: pretix/control/logdisplay.py:915
|
||||
#, python-brace-format
|
||||
@@ -18665,9 +18553,9 @@ msgid "The variation \"{value}\" has been changed."
|
||||
msgstr "Varianten \"{value}\" er blevet ændret."
|
||||
|
||||
#: pretix/control/logdisplay.py:934
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Payment {local_id} has been confirmed."
|
||||
msgstr "Betalingsmetoden er blevet ændret."
|
||||
msgstr "Betalingen {local_id} er blevet bekræftet."
|
||||
|
||||
#: pretix/control/logdisplay.py:935
|
||||
#, fuzzy, python-brace-format
|
||||
|
||||
@@ -5,16 +5,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
|
||||
"PO-Revision-Date: 2026-05-27 15:20+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||
">\n"
|
||||
"PO-Revision-Date: 2026-06-09 20:00+0000\n"
|
||||
"Last-Translator: Mira <weller@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"de/>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
|
||||
@@ -11353,7 +11353,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Wenn diese Option deaktiviert ist, werden Tickets nur für Produkte "
|
||||
"aktiviert, bei denen die Option \"Berechtigt zum Eintritt\" gesetzt ist. Sie "
|
||||
"können die Ticketgenerierung auch in den Einstellungen von jedes Produktes "
|
||||
"können die Ticketgenerierung auch in den Einstellungen jedes Produktes "
|
||||
"einzeln abschalten."
|
||||
|
||||
#: pretix/base/settings.py:1813
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
|
||||
"PO-Revision-Date: 2026-05-27 15:20+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-06-09 20:00+0000\n"
|
||||
"Last-Translator: Mira <weller@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
"Language: de_Informal\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:166
|
||||
@@ -11339,8 +11339,8 @@ msgid ""
|
||||
"issuing in every product separately."
|
||||
msgstr ""
|
||||
"Wenn diese Option deaktiviert ist, werden Tickets nur für Produkte "
|
||||
"aktiviert, bei denen die Option \"Berechtigt zum Eintritt\" gesetzt ist. Sie "
|
||||
"können die Ticketgenerierung auch in den Einstellungen von jedes Produktes "
|
||||
"aktiviert, bei denen die Option \"Berechtigt zum Eintritt\" gesetzt ist. Du "
|
||||
"kannst die Ticketgenerierung auch in den Einstellungen jedes Produktes "
|
||||
"einzeln abschalten."
|
||||
|
||||
#: pretix/base/settings.py:1813
|
||||
|
||||
@@ -4,8 +4,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
|
||||
"PO-Revision-Date: 2026-05-29 17:00+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"PO-Revision-Date: 2026-06-08 17:00+0000\n"
|
||||
"Last-Translator: Sébastien BRUNEAU <s.bruneau@beauvaisis.fr>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"fr/>\n"
|
||||
"Language: fr\n"
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:166
|
||||
@@ -35123,7 +35123,7 @@ msgstr "Vous devez cocher toutes les cases en bas de la page."
|
||||
|
||||
#: pretix/presale/forms/checkout.py:67
|
||||
msgid "Email address (repeated)"
|
||||
msgstr "Adresse de courriel (répété)"
|
||||
msgstr "Adresse de courriel (répétée)"
|
||||
|
||||
#: pretix/presale/forms/checkout.py:68
|
||||
msgid ""
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 14:47+0000\n"
|
||||
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
|
||||
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.hu>\n"
|
||||
"PO-Revision-Date: 2026-06-18 21:00+0000\n"
|
||||
"Last-Translator: Szurofka Márton <szurofka.marton@sze.hu>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/hu/>\n"
|
||||
"Language: hu\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.15.2\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -31,16 +31,16 @@ msgstr "Megjegyzés:"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
msgstr ""
|
||||
msgstr "PayPal"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
|
||||
msgid "Venmo"
|
||||
msgstr ""
|
||||
msgstr "Venmo"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:36
|
||||
#: pretix/static/pretixpresale/js/walletdetection.js:38
|
||||
msgid "Apple Pay"
|
||||
msgstr ""
|
||||
msgstr "Apple Pay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:37
|
||||
msgid "Itaú"
|
||||
@@ -56,7 +56,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
|
||||
msgid "PayPal Pay Later"
|
||||
msgstr ""
|
||||
msgstr "PayPal Későbbi Fizetés"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
@@ -148,7 +148,7 @@ msgstr "A fizetés megerősítése…"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:254
|
||||
msgid "Payment method unavailable"
|
||||
msgstr ""
|
||||
msgstr "Fizetésimód nem elérhető"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63
|
||||
@@ -163,12 +163,12 @@ msgstr "Kifizetett megrendelések"
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr ""
|
||||
msgstr "Résztvevők (megrendelve)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Attendees (paid)"
|
||||
msgstr ""
|
||||
msgstr "Résztvevők (fizetve)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
|
||||
msgid "Total revenue"
|
||||
@@ -187,18 +187,13 @@ msgid "Contacting your bank …"
|
||||
msgstr "Kapcsolatfelvétel a bankjával…"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
msgid "close"
|
||||
msgstr "Bezárás"
|
||||
msgstr "bezárás"
|
||||
|
||||
#: pretix/static/pretixbase/js/addressform.js:108
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:529
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "A kosár lejárt"
|
||||
msgstr "a kosár lejárt"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:13
|
||||
#, fuzzy
|
||||
@@ -276,6 +271,8 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:286
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr ""
|
||||
"Amennyiben ez több időt vesz igénybe, mint néhány perc, kérjük jelezze "
|
||||
"felénk."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:341
|
||||
msgid "Close message"
|
||||
@@ -302,26 +299,20 @@ msgid "Group of objects"
|
||||
msgstr "tárgy csoport"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Szöveg"
|
||||
msgstr "Szöveg elem (elavult)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Szöveg"
|
||||
msgstr "Szövegdoboz"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Vonalkód terület"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
#, fuzzy
|
||||
#| msgid "Barcode area"
|
||||
msgid "Image area"
|
||||
msgstr "Vonalkód terület"
|
||||
msgstr "Kép helye"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
@@ -361,19 +352,16 @@ msgid "Unknown error."
|
||||
msgstr "Ismeretlen hiba."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:310
|
||||
#, fuzzy
|
||||
#| msgid "Your color has great contrast and is very easy to read!"
|
||||
msgid "Your color has great contrast and will provide excellent accessibility."
|
||||
msgstr "A választott színek remek kontrasztot adnak, és nagyon könnyű olvasni!"
|
||||
msgstr "A választott színek remek kontrasztot adnak, és nagyon könnyű olvasni."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:314
|
||||
#, fuzzy
|
||||
#| msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgid ""
|
||||
"Your color has decent contrast and is sufficient for minimum accessibility "
|
||||
"requirements."
|
||||
msgstr ""
|
||||
"A választott színek kontrasztja elégséges, és valószínűleg jól olvasható!"
|
||||
"A választott színek kontrasztja elégséges, és elegendő a minimum "
|
||||
"követelménynek."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:318
|
||||
msgid ""
|
||||
@@ -384,7 +372,7 @@ msgstr ""
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:446
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:466
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
msgstr "Keresési lekérdezés"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:464
|
||||
msgid "All"
|
||||
@@ -404,7 +392,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:845
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
msgstr "Helytelen oldalszám."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1003
|
||||
msgid "Use a different name internally"
|
||||
@@ -465,7 +453,7 @@ msgstr "A kosár lejárt"
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:58
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:84
|
||||
msgid "Your cart is about to expire."
|
||||
msgstr ""
|
||||
msgstr "Kosara tartalma hamarosan lejár."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||
#, fuzzy
|
||||
@@ -532,32 +520,32 @@ msgstr ""
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
msgstr "Mennyiség"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Decrease quantity"
|
||||
msgstr ""
|
||||
msgstr "Mennyiség csökkentése"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Increase quantity"
|
||||
msgstr ""
|
||||
msgstr "Mennyiség növelése"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "Filter events by"
|
||||
msgstr ""
|
||||
msgstr "Események szűrése a következő szerint"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Szűrés"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
msgstr "Ár"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
#, javascript-format
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
|
||||
"PO-Revision-Date: 2025-10-17 16:55+0000\n"
|
||||
"Last-Translator: fd <fd@denkena-consulting.com>\n"
|
||||
"PO-Revision-Date: 2026-06-20 17:00+0000\n"
|
||||
"Last-Translator: Nikita Mitasov <me@ch4og.com>\n"
|
||||
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ru/>\n"
|
||||
"Language: ru\n"
|
||||
@@ -18,7 +18,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.13.3\n"
|
||||
"X-Generator: Weblate 2026.6.1\n"
|
||||
|
||||
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:166
|
||||
@@ -53,7 +53,7 @@ msgstr "В продаже"
|
||||
|
||||
#: pretix/_base_settings.py:89
|
||||
msgid "English"
|
||||
msgstr "английский язык"
|
||||
msgstr "Английский язык"
|
||||
|
||||
#: pretix/_base_settings.py:90
|
||||
msgid "German"
|
||||
@@ -466,7 +466,7 @@ msgstr "Этот пользователь уже имеет разрешения
|
||||
#: pretix/api/views/cart.py:209
|
||||
msgid ""
|
||||
"The specified voucher has already been used the maximum number of times."
|
||||
msgstr "Указанный ваучер уже использован максимальное количество раз."
|
||||
msgstr "Указанный промокод уже использован максимальное количество раз."
|
||||
|
||||
#: pretix/api/views/checkin.py:645 pretix/api/views/checkin.py:652
|
||||
msgid "Medium connected to other event"
|
||||
@@ -565,65 +565,50 @@ msgid "Refund of payment failed"
|
||||
msgstr "Возврат платежа не удался"
|
||||
|
||||
#: pretix/api/webhooks.py:359
|
||||
#, fuzzy
|
||||
#| msgctxt "checkoutflow"
|
||||
#| msgid "Order confirmed"
|
||||
msgid "Payment confirmed"
|
||||
msgstr "Заказ подтверждён"
|
||||
msgstr "Платёж подтверждён"
|
||||
|
||||
#: pretix/api/webhooks.py:363
|
||||
msgid "Order approved"
|
||||
msgstr ""
|
||||
msgstr "Заказ подтверждён"
|
||||
|
||||
#: pretix/api/webhooks.py:367
|
||||
msgid "Order denied"
|
||||
msgstr ""
|
||||
msgstr "Заказ отклонён"
|
||||
|
||||
#: pretix/api/webhooks.py:371
|
||||
#, fuzzy
|
||||
#| msgid "Order date"
|
||||
msgid "Order deleted"
|
||||
msgstr "Дата заказа"
|
||||
msgstr "Заказ удалён"
|
||||
|
||||
#: pretix/api/webhooks.py:375
|
||||
msgid "Ticket checked in"
|
||||
msgstr ""
|
||||
msgstr "Билет использован"
|
||||
|
||||
#: pretix/api/webhooks.py:379
|
||||
msgid "Ticket check-in reverted"
|
||||
msgstr ""
|
||||
msgstr "Использование билета отменено"
|
||||
|
||||
#: pretix/api/webhooks.py:383
|
||||
#, fuzzy
|
||||
#| msgid "Event date"
|
||||
msgid "Event created"
|
||||
msgstr "Дата мероприятия"
|
||||
msgstr "Мероприятие создано"
|
||||
|
||||
#: pretix/api/webhooks.py:387
|
||||
#, fuzzy
|
||||
#| msgid "Event date range"
|
||||
msgid "Event details changed"
|
||||
msgstr "Диапазон дат мероприятия"
|
||||
msgstr "Данные о мероприятии изменились"
|
||||
|
||||
#: pretix/api/webhooks.py:391
|
||||
#, fuzzy
|
||||
#| msgid "Event date"
|
||||
msgid "Event deleted"
|
||||
msgstr "Дата мероприятия"
|
||||
msgstr "Мероприятие удалено"
|
||||
|
||||
#: pretix/api/webhooks.py:395
|
||||
#, fuzzy
|
||||
#| msgid "Event begin date and time"
|
||||
msgctxt "subevent"
|
||||
msgid "Event series date added"
|
||||
msgstr "Дата и время начала мероприятия"
|
||||
msgstr "Добавлена дата серии мероприятий"
|
||||
|
||||
#: pretix/api/webhooks.py:399
|
||||
#, fuzzy
|
||||
#| msgid "Event date range"
|
||||
msgctxt "subevent"
|
||||
msgid "Event series date changed"
|
||||
msgstr "Диапазон дат мероприятия"
|
||||
msgstr "Изменена дата серии мероприятий"
|
||||
|
||||
#: pretix/api/webhooks.py:403
|
||||
#, fuzzy
|
||||
@@ -866,14 +851,13 @@ msgid "<a {a_attr}>powered by {name} based on pretix</a>"
|
||||
msgstr "на базе <a %(a_attr)s>pretix</a>"
|
||||
|
||||
#: pretix/base/context.py:55
|
||||
#, fuzzy, python-format
|
||||
#| msgid "<a %(a_attr)s>event ticketing powered by pretix</a>"
|
||||
#, python-format
|
||||
msgid "<a %(a_attr)s>ticketing powered by pretix</a>"
|
||||
msgstr "<a %(a_attr)s>event ticketing powered by pretix</a>"
|
||||
msgstr "<a %(a_attr)s>продажа билетов на базе pretix</a>"
|
||||
|
||||
#: pretix/base/context.py:64
|
||||
msgid "source code"
|
||||
msgstr "Промокод"
|
||||
msgstr "исходный код"
|
||||
|
||||
#: pretix/base/customersso/oidc.py:61
|
||||
#, python-brace-format
|
||||
@@ -2777,10 +2761,8 @@ msgid "Voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:656
|
||||
#, fuzzy
|
||||
#| msgid "Voucher code used:"
|
||||
msgid "Voucher budget usage"
|
||||
msgstr "Использованный код ваучера:"
|
||||
msgstr "Использовано по промокоду"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:657
|
||||
#, fuzzy
|
||||
@@ -3176,7 +3158,7 @@ msgstr "Заказы в ожидании"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:1209
|
||||
msgid "Blocking vouchers"
|
||||
msgstr "Блокирующие ваучеры"
|
||||
msgstr "Блокирующие промокоды"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:1210 pretix/control/views/item.py:1149
|
||||
msgid "Current user's carts"
|
||||
@@ -4606,7 +4588,7 @@ msgstr "Архивные события не найдены."
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:50 pretix/base/models/vouchers.py:491
|
||||
msgid "A voucher with this code already exists."
|
||||
msgstr "Ваучер с этим кодом уже существует."
|
||||
msgstr "Промокод с этим кодом уже существует."
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:68 pretix/base/models/memberships.py:57
|
||||
#: pretix/base/models/vouchers.py:199 pretix/control/views/vouchers.py:121
|
||||
@@ -4708,7 +4690,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:334 pretix/base/models/vouchers.py:300
|
||||
msgid "Shows hidden products that match this voucher"
|
||||
msgstr "Показывает скрытые продукты, которые соответствуют этому ваучеру"
|
||||
msgstr "Показывает скрытые продукты, которые соответствуют этому промокоду"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:343 pretix/base/models/vouchers.py:304
|
||||
msgid "Offer all add-on products for free when redeeming this voucher"
|
||||
@@ -4960,11 +4942,8 @@ msgid "Order not approved"
|
||||
msgstr "Срок действия заказа истёк"
|
||||
|
||||
#: pretix/base/models/checkin.py:367
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "This voucher is not valid for this event date."
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr "Этот ваучер не действителен на эту дату мероприятия."
|
||||
msgstr "Билет не действителен в это время"
|
||||
|
||||
#: pretix/base/models/checkin.py:368
|
||||
#, fuzzy
|
||||
@@ -7761,9 +7740,9 @@ msgid ""
|
||||
"same value for multiple vouchers, you can get statistics on how many of them "
|
||||
"have been redeemed etc."
|
||||
msgstr ""
|
||||
"Вы можете использовать это поле для группировки нескольких ваучеров. Если вы "
|
||||
"введёте одно и то же значение для нескольких ваучеров, вы сможете получить "
|
||||
"статистику о том, сколько из них было использовано и т. д."
|
||||
"Вы можете использовать это поле для группировки нескольких промокодов. Если "
|
||||
"вы введёте одно и то же значение для нескольких промокодов, вы сможете "
|
||||
"получить статистику о том, сколько из них было использовано и т. д."
|
||||
|
||||
#: pretix/base/models/vouchers.py:316 pretix/base/permissions.py:242
|
||||
#: pretix/control/navigation.py:289
|
||||
@@ -7808,7 +7787,7 @@ msgstr "Этот вариант не относится к этому проду
|
||||
#: pretix/base/models/vouchers.py:358
|
||||
msgid "It is currently not possible to create vouchers for add-on products."
|
||||
msgstr ""
|
||||
"В настоящее время невозможно создать ваучеры для дополнительных продуктов."
|
||||
"В настоящее время невозможно создать промокоды для дополнительных продуктов."
|
||||
|
||||
#: pretix/base/models/vouchers.py:360 pretix/base/models/vouchers.py:472
|
||||
#, fuzzy
|
||||
@@ -7824,7 +7803,7 @@ msgid ""
|
||||
"This voucher has already been redeemed %(redeemed)s times. You cannot reduce "
|
||||
"the maximum number of usages below this number."
|
||||
msgstr ""
|
||||
"Этот ваучер уже был использован %(redeemed)s раз. Вы не можете уменьшить "
|
||||
"Этот промокод уже был использован %(redeemed)s раз. Вы не можете уменьшить "
|
||||
"максимальное количество использований ниже этого числа."
|
||||
|
||||
#: pretix/base/models/vouchers.py:379
|
||||
@@ -7843,7 +7822,7 @@ msgstr ""
|
||||
msgid ""
|
||||
"If you want this voucher to block quota, you need to select a specific date."
|
||||
msgstr ""
|
||||
"Если вы хотите, чтобы этот ваучер блокировал квоту, вы должны выбрать "
|
||||
"Если вы хотите, чтобы этот промокод блокировал квоту, вы должны выбрать "
|
||||
"конкретную дату."
|
||||
|
||||
#: pretix/base/models/vouchers.py:387
|
||||
@@ -7857,7 +7836,7 @@ msgid ""
|
||||
"You cannot create a voucher that blocks quota as the selected product or "
|
||||
"quota is currently sold out or completely reserved."
|
||||
msgstr ""
|
||||
"Вы не можете создать ваучер, который блокирует квоту, поскольку выбранный "
|
||||
"Вы не можете создать промокод, который блокирует квоту, поскольку выбранный "
|
||||
"продукт или квота в настоящее время распроданы или полностью зарезервированы."
|
||||
|
||||
#: pretix/base/models/vouchers.py:507
|
||||
@@ -7879,7 +7858,7 @@ msgstr "В листе ожидания с"
|
||||
|
||||
#: pretix/base/models/waitinglist.py:84
|
||||
msgid "Assigned voucher"
|
||||
msgstr "Присвоенный ваучер"
|
||||
msgstr "Присвоенный промокод"
|
||||
|
||||
#: pretix/base/models/waitinglist.py:93
|
||||
msgid "The product the user waits for."
|
||||
@@ -7913,7 +7892,7 @@ msgstr "Эта запись анонимизирована и больше не
|
||||
|
||||
#: pretix/base/models/waitinglist.py:197
|
||||
msgid "A voucher has already been sent to this person."
|
||||
msgstr "Ваучер уже отправлен данному лицу."
|
||||
msgstr "Промокод уже отправлен данному лицу."
|
||||
|
||||
#: pretix/base/models/waitinglist.py:208
|
||||
#, python-brace-format
|
||||
@@ -8083,8 +8062,8 @@ msgid ""
|
||||
"You entered a voucher instead of a gift card. Vouchers can only be entered "
|
||||
"on the first page of the shop below the product selection."
|
||||
msgstr ""
|
||||
"Вы ввели ваучер вместо подарочного сертификата. Ваучеры можно вводить только "
|
||||
"на первой странице магазина под ассортиментом продуктов."
|
||||
"Вы ввели промокод вместо подарочного сертификата. Промокод можно вводить "
|
||||
"только на первой странице магазина под ассортиментом продуктов."
|
||||
|
||||
#: pretix/base/payment.py:151
|
||||
msgid "This gift card is not known."
|
||||
@@ -9373,7 +9352,7 @@ msgstr "Введённая цена слишком высока."
|
||||
|
||||
#: pretix/base/services/cart.py:163
|
||||
msgid "This voucher code is not known in our database."
|
||||
msgstr "Этот код ваучера не известен в нашей базе данных."
|
||||
msgstr "Этот промокод не известен в нашей базе данных."
|
||||
|
||||
#: pretix/base/services/cart.py:165
|
||||
#, fuzzy, python-format
|
||||
@@ -9407,7 +9386,7 @@ msgstr[2] ""
|
||||
msgid ""
|
||||
"This voucher code has already been used the maximum number of times allowed."
|
||||
msgstr ""
|
||||
"Этот код ваучера уже был использован максимально допустимое количество раз."
|
||||
"Этот промокод уже был использован максимально допустимое количество раз."
|
||||
|
||||
#: pretix/base/services/cart.py:178
|
||||
#, python-format
|
||||
@@ -9417,15 +9396,16 @@ msgid ""
|
||||
"or that you tried to redeem it before but did not complete the checkout "
|
||||
"process. You can try to use it again in %d minutes."
|
||||
msgstr ""
|
||||
"Этот код ваучера в настоящее время заблокирован, так как он уже содержится в "
|
||||
"Этот промокод в настоящее время заблокирован, так как он уже содержится в "
|
||||
"корзине. Это может означать, что кто-то другой сейчас использует этот "
|
||||
"ваучер, или что вы пытались его использовать ранее, но не завершили процесс "
|
||||
"оформления. Вы можете попробовать использовать его снова через %d минут."
|
||||
"промокод, или что вы пытались его использовать ранее, но не завершили "
|
||||
"процесс оформления. Вы можете попробовать использовать его снова через %d "
|
||||
"минут."
|
||||
|
||||
#: pretix/base/services/cart.py:183
|
||||
#, python-format
|
||||
msgid "This voucher code can only be redeemed %d more times."
|
||||
msgstr "Этот код ваучера можно использовать только ещё %d раз."
|
||||
msgstr "Этот промокод можно использовать ещё %d раз."
|
||||
|
||||
#: pretix/base/services/cart.py:184
|
||||
msgid ""
|
||||
@@ -9438,23 +9418,20 @@ msgid ""
|
||||
"You already used this voucher code. Remove the associated line from your "
|
||||
"cart if you want to use it for a different product."
|
||||
msgstr ""
|
||||
"Вы уже использовали этот код ваучера. Удалите связанную строку из вашей "
|
||||
"Вы уже использовали этот промокод. Удалите связанную строку из вашей "
|
||||
"корзины, если вы хотите использовать его для другого продукта."
|
||||
|
||||
#: pretix/base/services/cart.py:189
|
||||
msgid "This voucher is expired."
|
||||
msgstr "Срок действия этого ваучера истёк."
|
||||
msgstr "Срок действия этого промокода истёк."
|
||||
|
||||
#: pretix/base/services/cart.py:190
|
||||
msgid "This voucher is not valid for this product."
|
||||
msgstr "Этот ваучер недействителен для данного продукта."
|
||||
msgstr "Этот промокод недействителен для данного продукта."
|
||||
|
||||
#: pretix/base/services/cart.py:191
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "This voucher is not valid for this event date."
|
||||
msgid "This voucher is not valid for this seat."
|
||||
msgstr "Этот ваучер не действителен на эту дату мероприятия."
|
||||
msgstr "Этот промокод не действителен для данного места."
|
||||
|
||||
#: pretix/base/services/cart.py:193
|
||||
msgid ""
|
||||
@@ -9466,16 +9443,17 @@ msgstr ""
|
||||
#: pretix/base/services/cart.py:198
|
||||
msgid "Your voucher is valid for a product that is currently not for sale."
|
||||
msgstr ""
|
||||
"Ваш ваучер действителен для продукта, который в настоящее время не продаётся."
|
||||
"Ваш промокод действителен для продукта, который в настоящее время не "
|
||||
"продаётся."
|
||||
|
||||
#: pretix/base/services/cart.py:199
|
||||
msgctxt "subevent"
|
||||
msgid "This voucher is not valid for this event date."
|
||||
msgstr "Этот ваучер не действителен на эту дату мероприятия."
|
||||
msgstr "Этот промокод не действителен на эту дату мероприятия."
|
||||
|
||||
#: pretix/base/services/cart.py:200
|
||||
msgid "You need a valid voucher code to order this product."
|
||||
msgstr "Необходим действительный код ваучера для заказа этого продукта."
|
||||
msgstr "Необходим действительный промокод для заказа этого продукта."
|
||||
|
||||
#: pretix/base/services/cart.py:201
|
||||
msgctxt "subevent"
|
||||
@@ -9578,8 +9556,8 @@ msgid ""
|
||||
"You entered a gift card instead of a voucher. Gift cards can be entered "
|
||||
"later on when you're asked for your payment details."
|
||||
msgstr ""
|
||||
"Вы ввели подарочный сертификат вместо ваучера. Подарочные сертификаты можно "
|
||||
"ввести позже, когда вас попросят указать реквизиты для оплаты."
|
||||
"Вы ввели подарочный сертификат вместо промокода. Подарочные сертификаты "
|
||||
"можно ввести позже, когда вас попросят указать реквизиты для оплаты."
|
||||
|
||||
#: pretix/base/services/cart.py:224
|
||||
msgid ""
|
||||
@@ -9746,11 +9724,9 @@ msgid "This order is not yet approved."
|
||||
msgstr "Этот заказ не ожидает утверждения."
|
||||
|
||||
#: pretix/base/services/checkin.py:999 pretix/base/services/checkin.py:1003
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "This voucher is not valid for this event date."
|
||||
#, python-brace-format
|
||||
msgid "This ticket is only valid after {datetime}."
|
||||
msgstr "Этот ваучер не действителен на эту дату мероприятия."
|
||||
msgstr "Этот билет действителен только после {datetime}."
|
||||
|
||||
#: pretix/base/services/checkin.py:1013 pretix/base/services/checkin.py:1017
|
||||
#, fuzzy, python-brace-format
|
||||
@@ -9908,6 +9884,12 @@ msgid ""
|
||||
"We are sending this email because you configured us to do so in your event "
|
||||
"settings."
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"новый счет для заказа {order} на {event} создан, смотрите вложение.\n"
|
||||
"\n"
|
||||
"Вы получили это письмо, поскольку в настройках мероприятия была включена "
|
||||
"отправка таких уведомлений."
|
||||
|
||||
#: pretix/base/services/mail.py:144
|
||||
#, fuzzy
|
||||
@@ -10163,8 +10145,8 @@ msgid ""
|
||||
"The voucher code used for one of the items in your cart is not known in our "
|
||||
"database."
|
||||
msgstr ""
|
||||
"Код ваучера, использованный для одной из позиций в вашей корзине, неизвестен "
|
||||
"в нашей базе данных."
|
||||
"Промокод, использованный для одной из позиций в вашей корзине, неизвестен в "
|
||||
"нашей базе данных."
|
||||
|
||||
#: pretix/base/services/orders.py:173
|
||||
msgid ""
|
||||
@@ -10172,30 +10154,24 @@ msgid ""
|
||||
"used the maximum number of times allowed. We removed this item from your "
|
||||
"cart."
|
||||
msgstr ""
|
||||
"Код ваучера, использованный для одной из позиций в вашей корзине, уже был "
|
||||
"Промокод, использованный для одной из позиций в вашей корзине, уже был "
|
||||
"использован максимально допустимое количество раз. Мы удалили эту позицию из "
|
||||
"вашей корзины."
|
||||
|
||||
#: pretix/base/services/orders.py:177
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "The voucher code used for one of the items in your cart has already been "
|
||||
#| "used the maximum number of times allowed. We removed this item from your "
|
||||
#| "cart."
|
||||
msgid ""
|
||||
"The voucher code used for one of the items in your cart has already been too "
|
||||
"often. We adjusted the price of the item in your cart."
|
||||
msgstr ""
|
||||
"Код ваучера, использованный для одной из позиций в вашей корзине, уже был "
|
||||
"использован максимально допустимое количество раз. Мы удалили эту позицию из "
|
||||
"вашей корзины."
|
||||
"Промокод, применённый к одному из товаров в вашей корзине, был использован "
|
||||
"слишком много раз. Мы скорректировали цену товара в вашей корзине."
|
||||
|
||||
#: pretix/base/services/orders.py:181
|
||||
msgid ""
|
||||
"The voucher code used for one of the items in your cart is expired. We "
|
||||
"removed this item from your cart."
|
||||
msgstr ""
|
||||
"Срок действия кода ваучера, использованного для одной из позиций в вашей "
|
||||
"Срок действия промокода, использованного для одной из позиций в вашей "
|
||||
"корзине, истёк. Мы удалили эту позицию из вашей корзины."
|
||||
|
||||
#: pretix/base/services/orders.py:184
|
||||
@@ -10203,14 +10179,12 @@ msgid ""
|
||||
"The voucher code used for one of the items in your cart is not valid for "
|
||||
"this item. We removed this item from your cart."
|
||||
msgstr ""
|
||||
"Код ваучера, использованный для одной из позиций в вашей корзине, "
|
||||
"Промокод, использованный для одной из позиций в вашей корзине, "
|
||||
"недействителен для этой позиции. Мы удалили эту позицию из вашей корзины."
|
||||
|
||||
#: pretix/base/services/orders.py:186
|
||||
#, fuzzy
|
||||
#| msgid "You need a valid voucher code to order this product."
|
||||
msgid "You need a valid voucher code to order one of the products."
|
||||
msgstr "Необходим действительный код ваучера для заказа этого продукта."
|
||||
msgstr "Необходим действительный промокод для одного из продуктов."
|
||||
|
||||
#: pretix/base/services/orders.py:187
|
||||
msgid ""
|
||||
@@ -12221,6 +12195,16 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"вы получили это письмо, потому что запросили ссылку\n"
|
||||
"на ваш заказ для мероприятия {event}.\n"
|
||||
"\n"
|
||||
"Вы можете изменить данные заказа и просмотреть его статус по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2461
|
||||
#, python-brace-format
|
||||
@@ -12240,6 +12224,15 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"кто-то запросил список ваших заказов для мероприятия {event}.\n"
|
||||
"Список заказов::\n"
|
||||
"\n"
|
||||
"{orders}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2481
|
||||
#, python-brace-format
|
||||
@@ -12269,6 +12262,17 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"ваш заказ для мероприятия {event} прошел успешно. Поскольку ваш заказ "
|
||||
"состоит только из бесплатных позиций,\n"
|
||||
"оплата не требуется.\n"
|
||||
"\n"
|
||||
"Вы можете изменить детали заказа и посмотреть его статус по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2518
|
||||
#, python-brace-format
|
||||
@@ -12285,6 +12289,18 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"мы успешно получили ваш заказ для мероприятия {event}. Поскольку вы "
|
||||
"заказали\n"
|
||||
"товар, требующий подтверждения со стороны организатора, просим вас\n"
|
||||
"подождать нашего следующего письма.\n"
|
||||
"\n"
|
||||
"Вы можете изменить данные заказа и просмотреть его статус по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2536
|
||||
#, python-brace-format
|
||||
@@ -12303,6 +12319,19 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"мы успешно получили ваш заказ на сумму {total_with_currency} для мероприятия "
|
||||
"{event}.\n"
|
||||
"Пожалуйста, завершите оплату до {expire_date}.\n"
|
||||
"\n"
|
||||
"{payment_info}\n"
|
||||
"\n"
|
||||
"Вы можете изменить данные заказа и просмотреть его статус по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2554
|
||||
#, fuzzy
|
||||
@@ -12354,6 +12383,15 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"ваш заказ для мероприятия {event} был изменен.\n"
|
||||
"\n"
|
||||
"Вы можете посмотреть статус своего заказа по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2614
|
||||
#, python-brace-format
|
||||
@@ -12375,6 +12413,17 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"мы успешно получили вашу оплату за мероприятие {event}. Спасибо!\n"
|
||||
"\n"
|
||||
"{payment_info}\n"
|
||||
"\n"
|
||||
"Вы можете изменить данные заказа и просмотреть его статус по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2636
|
||||
#, python-brace-format
|
||||
@@ -12426,6 +12475,17 @@ msgid ""
|
||||
"Best regards, \n"
|
||||
"Your {event} team"
|
||||
msgstr ""
|
||||
"Здравствуйте,\n"
|
||||
"\n"
|
||||
"мы не получили полной оплаты вашего заказа для мероприятия {event}.\n"
|
||||
"Обратите внимание: мы можем гарантировать ваш заказ только в том случае,\n"
|
||||
"если оплата поступит до {expire_date}.\n"
|
||||
"\n"
|
||||
"Информацию об оплате и статус заказа можно посмотреть по ссылке:\n"
|
||||
"{url}\n"
|
||||
"\n"
|
||||
"С уважением, \n"
|
||||
"Команда мероприятия {event}"
|
||||
|
||||
#: pretix/base/settings.py:2685
|
||||
#, python-brace-format
|
||||
@@ -13648,10 +13708,8 @@ msgid "created by"
|
||||
msgstr "создано"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html:34
|
||||
#, fuzzy
|
||||
#| msgid "Bancontact"
|
||||
msgid "Contact:"
|
||||
msgstr "Bancontact"
|
||||
msgstr "Связаться:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html:54
|
||||
#, fuzzy, python-format
|
||||
@@ -13684,10 +13742,8 @@ msgstr ""
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html:136
|
||||
#: pretix/presale/templates/pretixpresale/event/base.html:223
|
||||
#: pretix/presale/templates/pretixpresale/organizers/base.html:101
|
||||
#, fuzzy
|
||||
#| msgid "Bancontact"
|
||||
msgid "Contact"
|
||||
msgstr "Bancontact"
|
||||
msgstr "Связаться"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, python-format
|
||||
@@ -13758,16 +13814,17 @@ msgid "Proceed to %(host)s"
|
||||
msgstr "Приступить к оформлению заказа"
|
||||
|
||||
#: pretix/base/templates/source.html:5 pretix/base/templates/source.html:9
|
||||
#, fuzzy
|
||||
#| msgid "Voucher code"
|
||||
msgid "Source code"
|
||||
msgstr "Код ваучера"
|
||||
msgstr "Исходный код"
|
||||
|
||||
#: pretix/base/templates/source.html:10
|
||||
msgid ""
|
||||
"This site is powered by free software. If you want to read the license terms "
|
||||
"or obtain the source code, follow these links or instructions:"
|
||||
msgstr ""
|
||||
"Этот сайт работает на свободном программном обеспечении. Если вы хотите "
|
||||
"ознакомиться с условиями лицензии или получить исходный код, перейдите по "
|
||||
"следующим ссылкам или следуйте инструкциям:"
|
||||
|
||||
#: pretix/base/ticketoutput.py:182
|
||||
#, fuzzy
|
||||
@@ -15702,6 +15759,7 @@ msgid ""
|
||||
"This installation of pretix includes changes or extensions made to the "
|
||||
"source code."
|
||||
msgstr ""
|
||||
"Данная установка pretix содержит изменения или расширения исходного кода."
|
||||
|
||||
#: pretix/control/forms/global_settings.py:162
|
||||
msgid "Usage of pretix"
|
||||
@@ -15792,10 +15850,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/global_settings.py:217
|
||||
#, fuzzy
|
||||
#| msgid "Your registration"
|
||||
msgid "Source code instructions"
|
||||
msgstr "Ваша регистрация"
|
||||
msgstr "Инструкции для получения исходного кода"
|
||||
|
||||
#: pretix/control/forms/global_settings.py:219
|
||||
msgid ""
|
||||
@@ -16761,10 +16817,8 @@ msgid "An medium with this type and identifier is already registered."
|
||||
msgstr "Ваучер с этим кодом уже существует."
|
||||
|
||||
#: pretix/control/forms/organizer.py:1059
|
||||
#, fuzzy
|
||||
#| msgid "A voucher with this code already exists."
|
||||
msgid "An account with this customer ID is already registered."
|
||||
msgstr "Ваучер с этим кодом уже существует."
|
||||
msgstr "Аккаунт с таким ID клиента уже существует."
|
||||
|
||||
#: pretix/control/forms/organizer.py:1076
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:62
|
||||
@@ -16887,10 +16941,8 @@ msgid "The selected organizer has already been invited."
|
||||
msgstr "Выбранный организатор не найден."
|
||||
|
||||
#: pretix/control/forms/organizer.py:1379
|
||||
#, fuzzy
|
||||
#| msgid "A voucher with this code already exists."
|
||||
msgid "A sales channel with the same identifier already exists."
|
||||
msgstr "Ваучер с этим кодом уже существует."
|
||||
msgstr "Канал продаж с таким идентификатором уже существует."
|
||||
|
||||
#: pretix/control/forms/organizer.py:1391
|
||||
msgid "Events with active plugin"
|
||||
@@ -17173,10 +17225,8 @@ msgid "Only includes active products."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/forms/waitinglist.py:115
|
||||
#, fuzzy
|
||||
#| msgid "A voucher with this code already exists."
|
||||
msgid "A voucher for this waiting list entry was already sent out."
|
||||
msgstr "Ваучер с этим кодом уже существует."
|
||||
msgstr "Промокод для данного места в листе ожидания уже отправлен."
|
||||
|
||||
#: pretix/control/forms/waitinglist.py:125
|
||||
#, fuzzy
|
||||
@@ -18812,7 +18862,7 @@ msgstr "Оплата"
|
||||
#: pretix/presale/templates/pretixpresale/organizers/index.html:89
|
||||
#: pretix/presale/templates/pretixpresale/organizers/index.html:91
|
||||
msgid "Tickets"
|
||||
msgstr ""
|
||||
msgstr "Билеты"
|
||||
|
||||
#: pretix/control/navigation.py:97
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:4
|
||||
@@ -22104,7 +22154,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html:3
|
||||
#, python-format
|
||||
msgid "Currently available: %(num)s"
|
||||
msgstr ""
|
||||
msgstr "Сейчас доступно: %(num)s"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_license.html:8
|
||||
msgid ""
|
||||
@@ -24027,7 +24077,7 @@ msgstr "Начальная дата мероприятия"
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:498
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:55
|
||||
msgid "Voucher code used:"
|
||||
msgstr "Использованный код ваучера:"
|
||||
msgstr "Использованный промокод:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:500
|
||||
#, fuzzy, python-format
|
||||
@@ -27824,10 +27874,8 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/import_start.html:6
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:81
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:94
|
||||
#, fuzzy
|
||||
#| msgid "Blocking vouchers"
|
||||
msgid "Import vouchers"
|
||||
msgstr "Блокирующие ваучеры"
|
||||
msgstr "Импорт промокодов"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/index.html:10
|
||||
msgid ""
|
||||
@@ -30761,6 +30809,8 @@ msgid ""
|
||||
"After completing your purchase, we will ask you to transfer the money to the "
|
||||
"following bank account, using a personal reference code:"
|
||||
msgstr ""
|
||||
"После завершения покупки, мы попросим вас перевести оплату на следующий "
|
||||
"банковский счет, указав персональный код назначения платежа:"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:11
|
||||
msgid ""
|
||||
@@ -30771,23 +30821,27 @@ msgstr ""
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:14
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html:21
|
||||
msgid "Reference code (important):"
|
||||
msgstr ""
|
||||
msgstr "Код назначения платежа (важно):"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:30
|
||||
msgid "We will assign you a personal reference code in the next step."
|
||||
msgstr ""
|
||||
msgstr "Мы присвоим вам персональный код назначения платежа на следующем шаге."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:36
|
||||
msgid ""
|
||||
"We will assign you a personal reference code to use after you completed the "
|
||||
"order."
|
||||
msgstr ""
|
||||
"Мы присвоим вам персональный код назначения платежа после завершения "
|
||||
"оформления заказа."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:41
|
||||
msgid ""
|
||||
"After completing your purchase, we will ask you to transfer the money to our "
|
||||
"bank account, using a personal reference code."
|
||||
msgstr ""
|
||||
"После завершения покупки мы попросим вас перевести оплату на следующий "
|
||||
"банковский счёт, указав персональный код назначения платежа."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:6
|
||||
#, fuzzy
|
||||
@@ -30810,7 +30864,7 @@ msgstr "Общая сумма"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:38
|
||||
msgid "Reference code"
|
||||
msgstr ""
|
||||
msgstr "Код назначения платежа"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html:4
|
||||
msgid ""
|
||||
@@ -30949,7 +31003,7 @@ msgstr "Сумма:"
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html:34
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:30
|
||||
msgid "There is no further action required on this website."
|
||||
msgstr ""
|
||||
msgstr "Дальнейших действий на этом сайте не требуется."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html:35
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:31
|
||||
@@ -34363,11 +34417,9 @@ msgstr ""
|
||||
|
||||
#: pretix/presale/forms/renderers.py:66
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:14
|
||||
#, fuzzy
|
||||
#| msgid "expired"
|
||||
msgctxt "form"
|
||||
msgid "required"
|
||||
msgstr "истекло"
|
||||
msgstr "обязательно"
|
||||
|
||||
#: pretix/presale/ical.py:87 pretix/presale/ical.py:146
|
||||
#: pretix/presale/ical.py:182
|
||||
@@ -34559,10 +34611,8 @@ msgid "We're now trying to book these add-ons for you!"
|
||||
msgstr "Сейчас мы пытаемся забронировать эти дополнительные продукты для вас!"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:28
|
||||
#, fuzzy
|
||||
#| msgid "Additional information"
|
||||
msgid "Additional options for"
|
||||
msgstr "Дополнительная информация"
|
||||
msgstr "Дополнительные опции"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:60
|
||||
#, fuzzy
|
||||
@@ -34614,10 +34664,8 @@ msgid "Cart expired"
|
||||
msgstr "Срок действия корзины истёк"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:36
|
||||
#, fuzzy
|
||||
#| msgid "Show variants"
|
||||
msgid "Show full cart"
|
||||
msgstr "Показать варианты"
|
||||
msgstr "Показать полную корзину"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:52
|
||||
#: pretix/presale/templates/pretixpresale/event/index.html:86
|
||||
@@ -34949,21 +34997,12 @@ msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "You can choose between %(min_count)s and %(max_count)s options from this "
|
||||
#| "category."
|
||||
#, python-format
|
||||
msgid "You can choose one option from this category."
|
||||
msgid_plural "You can choose up to %(max_count)s options from this category."
|
||||
msgstr[0] ""
|
||||
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
|
||||
"категории."
|
||||
msgstr[1] ""
|
||||
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
|
||||
"категории."
|
||||
msgstr[2] ""
|
||||
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
|
||||
"категории."
|
||||
msgstr[0] "Вы можете выбрать не более %(max_count)s варианта из этой категории."
|
||||
msgstr[1] "Вы можете выбрать не более %(max_count)s вариантов из этой категории."
|
||||
msgstr[2] "Вы можете выбрать не более %(max_count)s вариантов из этой категории."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
|
||||
#, python-format
|
||||
@@ -35363,8 +35402,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:516
|
||||
#, fuzzy, python-format
|
||||
#| msgid "The items in your cart are reserved for you for %(minutes)s minutes."
|
||||
#, python-format
|
||||
msgid "The items in your cart are reserved for you for %(minutes)s minutes."
|
||||
msgstr "Позиции в вашей корзине зарезервированы для вас на %(minutes)s минут."
|
||||
|
||||
@@ -35413,7 +35451,7 @@ msgstr "Очистить корзину"
|
||||
#: pretix/presale/templates/pretixpresale/event/index.html:248
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher_form.html:16
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Использовать ваучер/промокод"
|
||||
msgstr "Использовать промокод"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:71
|
||||
msgid "We're applying this voucher to your cart..."
|
||||
@@ -35422,7 +35460,7 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:79
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:27
|
||||
msgid "Redeem voucher"
|
||||
msgstr "Использовать ваучер"
|
||||
msgstr "Активировать промокод"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:10
|
||||
#, fuzzy
|
||||
@@ -35746,7 +35784,7 @@ msgstr "Изменить цену для %(item)s"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_quota_left.html:4
|
||||
#, python-format
|
||||
msgid "%(num)s currently available"
|
||||
msgstr ""
|
||||
msgstr "%(num)s доступно сейчас"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:5
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:5
|
||||
@@ -35999,32 +36037,23 @@ msgstr ""
|
||||
"завершения процесса."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:56
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Please bookmark or save the link to this exact page if you want to access "
|
||||
#| "your order later. We also sent you an email containing the link to the "
|
||||
#| "address you specified."
|
||||
msgid ""
|
||||
"Please bookmark or save the link to this exact page if you want to access "
|
||||
"your order later. We also sent you an email to the address you specified "
|
||||
"containing the link to this page."
|
||||
msgstr ""
|
||||
"Пожалуйста, добавьте в закладки или сохраните ссылку именно на эту страницу, "
|
||||
"если вы хотите получить доступ к вашему заказу позже. Мы также отправили вам "
|
||||
"электронное письмо со ссылкой на указанный вами адрес."
|
||||
"Пожалуйста, сохраните закладку или ссылку на эту страницу, если вам "
|
||||
"потребуется доступ к заказу позже. Мы также отправили письмо на указанный "
|
||||
"вами адрес электронной почты со ссылкой на эту страницу."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:60
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Please save the following link if you want to access your order later. We "
|
||||
#| "also sent you an email containing the link to the address you specified."
|
||||
msgid ""
|
||||
"Please save the following link if you want to access your order later. We "
|
||||
"also sent you an email to the address you specified containing the link."
|
||||
msgstr ""
|
||||
"Пожалуйста, сохраните следующую ссылку, если вы хотите получить доступ к "
|
||||
"вашему заказу позже. Мы также отправили вам электронное письмо со ссылкой на "
|
||||
"указанный вами адрес."
|
||||
"Пожалуйста, сохраните ссылку на эту страницу, если вам потребуется доступ к "
|
||||
"заказу позже. Мы также отправили письмо на указанный вами адрес электронной "
|
||||
"почты со ссылкой на эту страницу."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:75
|
||||
#: pretix/presale/templates/pretixpresale/event/position.html:18
|
||||
@@ -36551,14 +36580,11 @@ msgstr "Включить вебхук"
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:36
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher_form.html:9
|
||||
msgid "Voucher redemption"
|
||||
msgstr "Использование ваучера"
|
||||
msgstr "Активация промокода"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:20
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "This voucher is not valid for this event date."
|
||||
msgid "This voucher is valid only for the following specific date and time."
|
||||
msgstr "Этот ваучер не действителен на эту дату мероприятия."
|
||||
msgstr "Этот промокод действителен только на указанную дату и время."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:43
|
||||
msgid ""
|
||||
@@ -36577,8 +36603,8 @@ msgid ""
|
||||
"You entered a voucher code that allows you to buy one of the following "
|
||||
"products at the specified price:"
|
||||
msgstr ""
|
||||
"Вы ввели код ваучера, который позволяет вам купить один из следующих "
|
||||
"продуктов по указанной цене:"
|
||||
"Вы ввели промокод, который позволяет вам купить один из следующих продуктов "
|
||||
"по указанной цене:"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:112
|
||||
#, python-format
|
||||
|
||||
@@ -27,7 +27,7 @@ from django.urls import NoReverseMatch
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, mainreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute, mainreverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -49,7 +49,7 @@ class EventURLNode(URLNode):
|
||||
url = ''
|
||||
try:
|
||||
if self.absolute:
|
||||
url = build_absolute_uri(event, view_name, kwargs=kwargs)
|
||||
url = eventreverse_absolute(event, view_name, kwargs=kwargs)
|
||||
elif self.event is False:
|
||||
url = mainreverse(view_name, kwargs)
|
||||
else:
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import warnings
|
||||
from urllib.parse import urljoin, urlsplit
|
||||
|
||||
from django.conf import settings
|
||||
@@ -190,6 +191,15 @@ def eventreverse(obj, name, kwargs=None):
|
||||
|
||||
|
||||
def build_absolute_uri(obj, urlname, kwargs=None):
|
||||
warnings.warn(
|
||||
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
||||
'Replace this usage with eventreverse_absolute',
|
||||
DeprecationWarning
|
||||
)
|
||||
return eventreverse_absolute(obj, urlname, kwargs)
|
||||
|
||||
|
||||
def eventreverse_absolute(obj, urlname, kwargs=None):
|
||||
"""
|
||||
Works similar to ``eventreverse`` but always returns an absolute URL.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ from pretix.base.forms import SecretKeySettingsField
|
||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||
from pretix.base.settings import SettingsSandbox
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.plugins.paypal.api import Api
|
||||
from pretix.plugins.paypal.models import ReferencedPayPalObject
|
||||
|
||||
@@ -268,8 +268,8 @@ class Paypal(BasePaymentProvider):
|
||||
"payment_method": "paypal",
|
||||
},
|
||||
"redirect_urls": {
|
||||
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return', kwargs=kwargs),
|
||||
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort', kwargs=kwargs),
|
||||
"return_url": eventreverse_absolute(request.event, 'plugins:paypal:return', kwargs=kwargs),
|
||||
"cancel_url": eventreverse_absolute(request.event, 'plugins:paypal:abort', kwargs=kwargs),
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
@@ -351,7 +351,7 @@ class Paypal(BasePaymentProvider):
|
||||
if request.session.get('iframe_session', False):
|
||||
signer = signing.Signer(salt='safe-redirect')
|
||||
return (
|
||||
build_absolute_uri(request.event, 'plugins:paypal:redirect') + '?url=' +
|
||||
eventreverse_absolute(request.event, 'plugins:paypal:redirect') + '?url=' +
|
||||
urllib.parse.quote(signer.sign(link.href))
|
||||
)
|
||||
else:
|
||||
@@ -613,8 +613,8 @@ class Paypal(BasePaymentProvider):
|
||||
"payment_method": "paypal",
|
||||
},
|
||||
"redirect_urls": {
|
||||
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return'),
|
||||
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort'),
|
||||
"return_url": eventreverse_absolute(request.event, 'plugins:paypal:return'),
|
||||
"cancel_url": eventreverse_absolute(request.event, 'plugins:paypal:abort'),
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
|
||||
@@ -56,8 +56,8 @@ from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||
from pretix.base.settings import SettingsSandbox
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.plugins.paypal2.client.core.environment import (
|
||||
LiveEnvironment, SandboxEnvironment,
|
||||
)
|
||||
@@ -264,7 +264,7 @@ class PaypalSettingsHolder(BasePaymentProvider):
|
||||
settings_content = "<div class='alert alert-info'>%s<br /><code>%s</code></div>" % (
|
||||
_('Please configure a PayPal Webhook to the following endpoint in order to automatically cancel orders '
|
||||
'when payments are refunded externally.'),
|
||||
build_global_uri('plugins:paypal2:webhook')
|
||||
mainreverse_absolute('plugins:paypal2:webhook')
|
||||
)
|
||||
|
||||
if self.event.currency not in SUPPORTED_CURRENCIES:
|
||||
@@ -321,7 +321,7 @@ class PaypalSettingsHolder(BasePaymentProvider):
|
||||
],
|
||||
"partner_config_override": {
|
||||
"partner_logo_url": urllib.parse.urljoin(settings.SITE_URL, static('pretixbase/img/pretix-logo.svg')),
|
||||
"return_url": build_global_uri('plugins:paypal2:isu.return', kwargs={
|
||||
"return_url": mainreverse_absolute('plugins:paypal2:isu.return', kwargs={
|
||||
'organizer': self.event.organizer.slug,
|
||||
'event': self.event.slug,
|
||||
})
|
||||
@@ -585,8 +585,8 @@ class PaypalMethod(BasePaymentProvider):
|
||||
'locale': request.LANGUAGE_CODE.split('-')[0],
|
||||
'shipping_preference': 'NO_SHIPPING', # 'SET_PROVIDED_ADDRESS', # Do not set on non-ship order?
|
||||
'user_action': 'CONTINUE',
|
||||
'return_url': build_absolute_uri(request.event, 'plugins:paypal2:return', kwargs=kwargs),
|
||||
'cancel_url': build_absolute_uri(request.event, 'plugins:paypal2:abort', kwargs=kwargs),
|
||||
'return_url': eventreverse_absolute(request.event, 'plugins:paypal2:return', kwargs=kwargs),
|
||||
'cancel_url': eventreverse_absolute(request.event, 'plugins:paypal2:abort', kwargs=kwargs),
|
||||
},
|
||||
})
|
||||
response = self.client.execute(paymentreq)
|
||||
|
||||
@@ -270,18 +270,18 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
||||
def _transaction_group_header_label(self):
|
||||
return _("Event") + " / " + _("Product")
|
||||
|
||||
def _transaction_group_label(self, form_data, r):
|
||||
def _transaction_group(self, form_data, r):
|
||||
if not self.is_multievent and not form_data.get("split_subevents"):
|
||||
return None
|
||||
return None, None
|
||||
if r.get("subevent_id"):
|
||||
return "{} - {} ({}) [{}]".format(
|
||||
r["order__event__name"],
|
||||
r["subevent__name"],
|
||||
date_format(r["subevent__date_from"]),
|
||||
r["order__event__slug"]
|
||||
)
|
||||
), f"subevent-{r['subevent_id']}"
|
||||
else:
|
||||
return "{} [{}]".format(r["order__event__name"], r["order__event__slug"])
|
||||
return "{} [{}]".format(r["order__event__name"], r["order__event__slug"]), f"event-{r['order__event__slug']}"
|
||||
|
||||
def _transaction_row_label(self, r):
|
||||
if r["item_id"]:
|
||||
@@ -343,17 +343,17 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
||||
last_group = None
|
||||
last_group_head_idx = 0
|
||||
for r in qs:
|
||||
e = self._transaction_group_label(form_data, r)
|
||||
group_label, group_id = self._transaction_group(form_data, r)
|
||||
|
||||
if e != last_group:
|
||||
if last_group_head_idx > 0 and e is not None:
|
||||
if group_id != last_group:
|
||||
if last_group_head_idx > 0 and group_id is not None:
|
||||
tdata[last_group_head_idx][4] = Paragraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
|
||||
tdata[last_group_head_idx][5] = Paragraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
|
||||
tdata[last_group_head_idx][6] = Paragraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
|
||||
tdata.append(
|
||||
[
|
||||
FontFallbackParagraph(
|
||||
e,
|
||||
group_label,
|
||||
tstyle_bold,
|
||||
),
|
||||
"",
|
||||
@@ -367,7 +367,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
||||
tstyledata.append(
|
||||
("SPAN", (0, len(tdata) - 1), (3, len(tdata) - 1)),
|
||||
)
|
||||
last_group = e
|
||||
last_group = group_id
|
||||
last_group_head_idx = len(tdata) - 1
|
||||
sum_price_by_group = Decimal("0.00")
|
||||
sum_tax_by_group = Decimal("0.00")
|
||||
|
||||
@@ -75,8 +75,8 @@ from pretix.base.settings import SettingsSandbox
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.http import get_client_ip
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.plugins.stripe.forms import StripeKeyValidator
|
||||
from pretix.plugins.stripe.models import (
|
||||
ReferencedStripeObject, RegisteredApplePayDomain,
|
||||
@@ -197,7 +197,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
).format(
|
||||
self.settings.connect_client_id,
|
||||
request.session['payment_stripe_oauth_token'],
|
||||
urllib.parse.quote(build_global_uri('plugins:stripe:oauth.return')),
|
||||
urllib.parse.quote(mainreverse_absolute('plugins:stripe:oauth.return')),
|
||||
)
|
||||
|
||||
def settings_content_render(self, request):
|
||||
@@ -229,7 +229,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
_('Please configure a <a href="https://dashboard.stripe.com/account/webhooks">Stripe Webhook</a> to '
|
||||
'the following endpoint in order to automatically cancel orders when charges are refunded externally '
|
||||
'and to process asynchronous payment methods like SOFORT.'),
|
||||
build_global_uri('plugins:stripe:webhook')
|
||||
mainreverse_absolute('plugins:stripe:webhook')
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -746,7 +746,7 @@ class StripeMethod(BasePaymentProvider):
|
||||
def redirect(self, request, url):
|
||||
if request.session.get('iframe_session', False):
|
||||
return (
|
||||
build_absolute_uri(request.event, 'plugins:stripe:redirect') +
|
||||
eventreverse_absolute(request.event, 'plugins:stripe:redirect') +
|
||||
'?data=' + signing.dumps({
|
||||
'url': url,
|
||||
'session': {
|
||||
@@ -941,7 +941,7 @@ class StripeMethod(BasePaymentProvider):
|
||||
},
|
||||
# TODO: Is this sufficient?
|
||||
idempotency_key=str(self.event.id) + payment.order.code + idempotency_key_seed,
|
||||
return_url=build_absolute_uri(self.event, 'plugins:stripe:sca.return', kwargs={
|
||||
return_url=eventreverse_absolute(self.event, 'plugins:stripe:sca.return', kwargs={
|
||||
'order': payment.order.code,
|
||||
'payment': payment.pk,
|
||||
'hash': payment.order.tagged_secret('plugins:stripe'),
|
||||
@@ -1047,13 +1047,13 @@ class StripeMethod(BasePaymentProvider):
|
||||
raise PaymentException(_('Stripe reported an error: %s') % intent.last_payment_error.message)
|
||||
|
||||
def _redirect_to_sca(self, request, payment):
|
||||
url = build_absolute_uri(self.event, 'plugins:stripe:sca', kwargs={
|
||||
url = eventreverse_absolute(self.event, 'plugins:stripe:sca', kwargs={
|
||||
'order': payment.order.code,
|
||||
'payment': payment.pk,
|
||||
'hash': payment.order.tagged_secret('plugins:stripe'),
|
||||
})
|
||||
if not self.redirect_in_widget_allowed and request.session.get('iframe_session', False):
|
||||
return build_absolute_uri(self.event, 'plugins:stripe:redirect') + '?data=' + signing.dumps({
|
||||
return eventreverse_absolute(self.event, 'plugins:stripe:redirect') + '?data=' + signing.dumps({
|
||||
'url': url,
|
||||
'session': {},
|
||||
}, salt='safe-redirect')
|
||||
@@ -1068,7 +1068,7 @@ class StripeMethod(BasePaymentProvider):
|
||||
|
||||
intent = stripe.PaymentIntent.confirm(
|
||||
payment_info['id'],
|
||||
return_url=build_absolute_uri(self.event, 'plugins:stripe:sca.return', kwargs={
|
||||
return_url=eventreverse_absolute(self.event, 'plugins:stripe:sca.return', kwargs={
|
||||
'order': payment.order.code,
|
||||
'payment': payment.pk,
|
||||
'hash': payment.order.tagged_secret('plugins:stripe'),
|
||||
|
||||
@@ -64,7 +64,7 @@ from pretix.control.views.event import DecoupleMixin
|
||||
from pretix.control.views.organizer import OrganizerDetailViewMixin
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.plugins.stripe.forms import OrganizerStripeSettingsForm
|
||||
from pretix.plugins.stripe.models import ReferencedStripeObject
|
||||
from pretix.plugins.stripe.tasks import (
|
||||
@@ -90,7 +90,7 @@ def redirect_view(request, *args, **kwargs):
|
||||
params = request.GET.copy()
|
||||
params['go'] = '1'
|
||||
r = render(request, 'pretixplugins/stripe/redirect.html', {
|
||||
'url': build_absolute_uri(request.event, 'plugins:stripe:redirect') + '?' + urllib.parse.urlencode(params),
|
||||
'url': eventreverse_absolute(request.event, 'plugins:stripe:redirect') + '?' + urllib.parse.urlencode(params),
|
||||
})
|
||||
r._csp_ignore = True
|
||||
return r
|
||||
|
||||
@@ -44,7 +44,7 @@ from pretix.base.forms.questions import (
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.models import Customer
|
||||
from pretix.helpers.http import get_client_ip
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
class TokenGenerator(PasswordResetTokenGenerator):
|
||||
@@ -84,7 +84,7 @@ class AuthenticationForm(forms.Form):
|
||||
self.customer_cache = None
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['password'].help_text = "<a target='_blank' href='{}'>{}</a>".format(
|
||||
build_absolute_uri(False, 'presale:organizer.customer.resetpw', kwargs={
|
||||
eventreverse_absolute(False, 'presale:organizer.customer.resetpw', kwargs={
|
||||
'organizer': request.organizer.slug,
|
||||
}),
|
||||
_('Forgot your password?')
|
||||
|
||||
@@ -32,7 +32,7 @@ from django.utils.translation import gettext as _
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.models import Event
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
def get_public_ical(events):
|
||||
@@ -48,9 +48,9 @@ def get_public_ical(events):
|
||||
event = ev if isinstance(ev, Event) else ev.event
|
||||
tz = event.timezone
|
||||
if isinstance(ev, Event):
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
url = eventreverse_absolute(event, 'presale:event.index')
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
url = eventreverse_absolute(event, 'presale:event.index', {
|
||||
'subevent': ev.pk
|
||||
})
|
||||
|
||||
@@ -134,7 +134,7 @@ def get_private_icals(event, positions):
|
||||
program_times = p.item.program_times.all()
|
||||
if program_times:
|
||||
# if program times have been configured, they are preferred for the position's calendar entries
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
url = eventreverse_absolute(event, 'presale:event.index')
|
||||
for index, pt in enumerate(program_times):
|
||||
summary = _('{event} - {item}').format(event=ev, item=p.item.name)
|
||||
if event.settings.mail_attach_ical_description:
|
||||
@@ -167,11 +167,11 @@ def get_private_icals(event, positions):
|
||||
else:
|
||||
# without program times, the subevent or event times are used for calendar entries, preferring subevents
|
||||
if p.subevent:
|
||||
url = build_absolute_uri(event, 'presale:event.index', {
|
||||
url = eventreverse_absolute(event, 'presale:event.index', {
|
||||
'subevent': p.subevent.pk
|
||||
})
|
||||
else:
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
url = eventreverse_absolute(event, 'presale:event.index')
|
||||
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
|
||||
@@ -22,4 +22,5 @@
|
||||
<script type="text/javascript" src="{% static "pretixpresale/js/ui/iframe.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/deanonymize_email.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
{% endcompress %}
|
||||
|
||||
@@ -59,7 +59,7 @@ from pretix.base.timemachine import time_machine_now_assigned_from_request
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_event_domain, get_organizer_domain,
|
||||
eventreverse_absolute, get_event_domain, get_organizer_domain,
|
||||
)
|
||||
from pretix.presale.signals import process_request, process_response
|
||||
|
||||
@@ -283,7 +283,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
domain, domainmode = get_event_domain(request.event, fallback=False, return_mode=True)
|
||||
if not domain and request_domain_mode == KnownDomain.MODE_ORG_ALT_DOMAIN:
|
||||
path = request.get_full_path().split("/", 2)[-1]
|
||||
r = redirect_to_url(build_absolute_uri(request.event, "presale:event.index") + path)
|
||||
r = redirect_to_url(eventreverse_absolute(request.event, "presale:event.index") + path)
|
||||
r['Access-Control-Allow-Origin'] = '*'
|
||||
return r
|
||||
elif domain and domain != request.host:
|
||||
|
||||
@@ -56,7 +56,7 @@ from pretix.base.signals import customer_created, customer_signed_in
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.presale.forms.customer import (
|
||||
AuthenticationForm, ChangeInfoForm, ChangePasswordForm, RegistrationForm,
|
||||
ResetPasswordForm, SetPasswordForm, TokenGenerator,
|
||||
@@ -316,8 +316,10 @@ class ResetPasswordView(FormView):
|
||||
customer.log_action('pretix.customer.password.resetrequested', {})
|
||||
ctx = customer.get_email_context()
|
||||
token = TokenGenerator().make_token(customer)
|
||||
ctx['url'] = build_absolute_uri(self.request.organizer,
|
||||
'presale:organizer.customer.recoverpw') + '?id=' + customer.identifier + '&token=' + token
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.request.organizer,
|
||||
'presale:organizer.customer.recoverpw'
|
||||
) + '?id=' + customer.identifier + '&token=' + token
|
||||
mail(
|
||||
customer.email,
|
||||
self.request.organizer.settings.mail_subject_customer_reset,
|
||||
@@ -574,7 +576,7 @@ class ChangeInformationView(CustomerAccountBaseMixin, FormView):
|
||||
new_email = form.cleaned_data['email']
|
||||
form.cleaned_data['email'] = form.instance.email = self.initial_email
|
||||
ctx = form.instance.get_email_context()
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.request.organizer,
|
||||
'presale:organizer.customer.change.confirm'
|
||||
) + '?token=' + dumps({
|
||||
@@ -703,7 +705,7 @@ class SSOLoginView(RedirectBackMixin, View):
|
||||
request.session[f'pretix_customerauth_{self.provider.pk}_nonce'] = nonce
|
||||
request.session[f'pretix_customerauth_{self.provider.pk}_popup_origin'] = popup_origin
|
||||
request.session[f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'] = self.request.GET.get("request_cross_domain_customer_auth") == "true"
|
||||
redirect_uri = build_absolute_uri(self.request.organizer, 'presale:organizer.customer.login.return', kwargs={
|
||||
redirect_uri = eventreverse_absolute(self.request.organizer, 'presale:organizer.customer.login.return', kwargs={
|
||||
'provider': self.provider.pk
|
||||
})
|
||||
|
||||
@@ -777,7 +779,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
|
||||
),
|
||||
popup_origin,
|
||||
)
|
||||
redirect_uri = build_absolute_uri(
|
||||
redirect_uri = eventreverse_absolute(
|
||||
self.request.organizer, 'presale:organizer.customer.login.return',
|
||||
kwargs={
|
||||
'provider': self.provider.pk
|
||||
|
||||
@@ -46,7 +46,7 @@ from pretix.base.models.customers import (
|
||||
)
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.multidomain.middlewares import CsrfViewMiddleware
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.customer import AuthenticationForm
|
||||
from pretix.presale.utils import customer_login, get_customer_auth_time
|
||||
|
||||
@@ -519,17 +519,17 @@ class ConfigurationView(View):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return JsonResponse({
|
||||
'issuer': build_absolute_uri(request.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'authorization_endpoint': build_absolute_uri(
|
||||
'issuer': eventreverse_absolute(request.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'authorization_endpoint': eventreverse_absolute(
|
||||
request.organizer, 'presale:organizer.oauth2.v1.authorize'
|
||||
),
|
||||
'token_endpoint': build_absolute_uri(
|
||||
'token_endpoint': eventreverse_absolute(
|
||||
request.organizer, 'presale:organizer.oauth2.v1.token'
|
||||
),
|
||||
'userinfo_endpoint': build_absolute_uri(
|
||||
'userinfo_endpoint': eventreverse_absolute(
|
||||
request.organizer, 'presale:organizer.oauth2.v1.userinfo'
|
||||
),
|
||||
'jwks_uri': build_absolute_uri(
|
||||
'jwks_uri': eventreverse_absolute(
|
||||
request.organizer, 'presale:organizer.oauth2.v1.jwks'
|
||||
),
|
||||
'scopes_supported': [k for k, v in CustomerSSOClient.SCOPE_CHOICES],
|
||||
|
||||
@@ -89,7 +89,7 @@ from pretix.base.views.mixins import OrderQuestionsViewMixin
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.presale.forms.checkout import InvoiceAddressForm, QuestionsForm
|
||||
from pretix.presale.forms.order import OrderPositionChangeForm
|
||||
from pretix.presale.signals import question_form_fields_overrides
|
||||
@@ -312,7 +312,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
|
||||
state__in=[OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED]
|
||||
).exists() and self.order.status == Order.STATUS_PENDING:
|
||||
ctx['generate_invoice_requires'] = 'payment'
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.request.event, 'presale:event.order', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.order.secret
|
||||
|
||||
@@ -74,7 +74,7 @@ from pretix.helpers.formats.en.formats import (
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.helpers.i18n import parse_date_localized
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.presale.forms.organizer import EventListFilterForm
|
||||
from pretix.presale.ical import get_public_ical
|
||||
from pretix.presale.signals import filter_subevents
|
||||
@@ -1380,7 +1380,7 @@ class OrganizerFavicon(View):
|
||||
|
||||
class RedirectToOrganizerIndex(View):
|
||||
def get(self, *args, **kwargs):
|
||||
return redirect_to_url(build_absolute_uri(self.request.organizer, "presale:organizer.index"))
|
||||
return redirect_to_url(eventreverse_absolute(self.request.organizer, "presale:organizer.index"))
|
||||
|
||||
|
||||
class AccessibilityView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
|
||||
@@ -65,7 +65,7 @@ from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.helpers.daterange import daterange
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.organizer import meta_filtersets
|
||||
from pretix.presale.style import get_theme_vars_css
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
@@ -128,6 +128,9 @@ def _use_vite(request):
|
||||
origin = request.META.get('HTTP_ORIGIN', '')
|
||||
gs = GlobalSettingsObject()
|
||||
vite_origins = gs.settings.get('widget_vite_origins', as_type=str, default='')
|
||||
if vite_origins and not origin:
|
||||
referer = request.META.get('HTTP_REFERER', '')
|
||||
origin = '/'.join(referer.split('/', 3)[:3])
|
||||
if origin and vite_origins:
|
||||
origins_list = [o.strip() for o in vite_origins.strip().splitlines() if o.strip()]
|
||||
return origin in origins_list
|
||||
@@ -300,7 +303,7 @@ def get_picture(event, picture, size=None):
|
||||
logger.exception(f'Failed to create thumbnail of {picture.name}')
|
||||
if not thumb:
|
||||
thumb = default_storage.url(picture.name)
|
||||
return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb)
|
||||
return urljoin(eventreverse_absolute(event, 'presale:event.index'), thumb)
|
||||
|
||||
|
||||
class WidgetAPIProductList(EventListMixin, View):
|
||||
@@ -544,7 +547,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'location': str(ev.location),
|
||||
'date_range': self._get_date_range(ev, event, tz=tz),
|
||||
'availability': self._get_availability(ev, event, tz=tz),
|
||||
'event_url': build_absolute_uri(event, 'presale:event.index'),
|
||||
'event_url': eventreverse_absolute(event, 'presale:event.index'),
|
||||
'subevent': ev.pk if isinstance(ev, SubEvent) else None,
|
||||
})
|
||||
return events
|
||||
@@ -745,7 +748,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'location': str(ev.location),
|
||||
'date_range': self._get_date_range(ev, ev.event, tz),
|
||||
'availability': self._get_availability(ev, ev.event, tz=tz),
|
||||
'event_url': build_absolute_uri(ev.event, 'presale:event.index'),
|
||||
'event_url': eventreverse_absolute(ev.event, 'presale:event.index'),
|
||||
'subevent': ev.pk,
|
||||
} for ev in evs
|
||||
]
|
||||
@@ -768,7 +771,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'location': str(event.location),
|
||||
'date_range': dr,
|
||||
'availability': avail,
|
||||
'event_url': build_absolute_uri(event, 'presale:event.index'),
|
||||
'event_url': eventreverse_absolute(event, 'presale:event.index'),
|
||||
})
|
||||
|
||||
cache.set(cache_key, data, 30)
|
||||
@@ -793,7 +796,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
return self.response(cached_data)
|
||||
|
||||
data = {
|
||||
'target_url': build_absolute_uri(request.event, 'presale:event.index'),
|
||||
'target_url': eventreverse_absolute(request.event, 'presale:event.index'),
|
||||
'subevent': self.subevent.pk if self.subevent else None,
|
||||
'currency': request.event.currency,
|
||||
'display_net_prices': request.event.settings.display_net_prices,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
|
||||
'use strict';
|
||||
{
|
||||
const globals = this;
|
||||
|
||||
@@ -174,15 +174,16 @@ function async_task_error(jqXHR, textStatus, errorThrown) {
|
||||
var respdom = $(jqXHR.responseText);
|
||||
var c = respdom.filter('.container');
|
||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||
// This is a failed form validation, let's just use it
|
||||
|
||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||
// This is a failed form validation, let's just use it
|
||||
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
|
||||
} else {
|
||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
document.dispatchEvent(new Event("pretix:async-task-error"))
|
||||
|
||||
}
|
||||
|
||||
} else if (c.length > 0) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
document.getElementById('goback').onclick =
|
||||
function() {window.history.back()};
|
||||
|
||||
document.getElementById('reload').onclick =
|
||||
function() {window.location.reload(true)};
|
||||
['DOMContentLoaded', 'pretix:async-task-error'].forEach(function (ev) {
|
||||
document.addEventListener(ev, function () {
|
||||
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
||||
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback';
|
||||
element.addEventListener('click', regularLoad
|
||||
? () => window.history.back()
|
||||
: () => window.location.reload()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { rules as rawRules, items, allProducts, limitProducts } from './django-interop'
|
||||
import { rules as rawRules, allItems, activeItems, allProducts, limitProducts } from './django-interop'
|
||||
import { convertToDNF } from './jsonlogic-boolalg'
|
||||
|
||||
import RulesEditor from './checkin-rules-editor.vue'
|
||||
@@ -53,7 +53,7 @@ const missingItems = computed(() => {
|
||||
}
|
||||
|
||||
let missing = []
|
||||
for (const item of items.value) {
|
||||
for (const item of activeItems.value) {
|
||||
if (productsSeen[item.id]) continue
|
||||
if (!allProducts.value && !limitProducts.value.includes(item.id)) continue
|
||||
if (item.variations.length > 0) {
|
||||
@@ -87,7 +87,7 @@ const missingItems = computed(() => {
|
||||
|
||||
//- Tab panes
|
||||
.tab-content
|
||||
#rules-edit.tab-pane.active(v-if="items", role="tabpanel")
|
||||
#rules-edit.tab-pane.active(v-if="allItems", role="tabpanel")
|
||||
RulesEditor
|
||||
#rules-viz.tab-pane(role="tabpanel")
|
||||
RulesVisualization
|
||||
|
||||
@@ -26,11 +26,13 @@ watch(rules, (newVal) => {
|
||||
rulesInput.value = JSON.stringify(newVal)
|
||||
}, { deep: true })
|
||||
|
||||
export const items = ref<any[]>([])
|
||||
export const activeItems = ref<any[]>([])
|
||||
export const allItems = ref<any[]>([])
|
||||
|
||||
const itemsEl = document.querySelector('#items')
|
||||
if (itemsEl?.textContent) {
|
||||
items.value = JSON.parse(itemsEl.textContent || '[]')
|
||||
allItems.value = JSON.parse(itemsEl.textContent || '[]')
|
||||
activeItems.value = allItems.value.filter(item => item.active)
|
||||
|
||||
function checkForInvalidIds (validProducts: Record<string, string>, validVariations: Record<string, string>, rule: any) {
|
||||
if (rule['and']) {
|
||||
@@ -57,8 +59,8 @@ if (itemsEl?.textContent) {
|
||||
}
|
||||
|
||||
checkForInvalidIds(
|
||||
Object.fromEntries(items.value.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(items.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||
Object.fromEntries(allItems.value.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(allItems.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||
rules.value
|
||||
)
|
||||
}
|
||||
|
||||
@@ -864,6 +864,9 @@ tbody th {
|
||||
.checkin-sim-result-status-incomplete {
|
||||
background: $brand-primary;
|
||||
}
|
||||
.checkin-sim-result-status-exchange {
|
||||
background: $brand-primary;
|
||||
}
|
||||
.checkin-sim-result-status-error {
|
||||
background: $brand-danger;
|
||||
}
|
||||
|
||||
@@ -2051,9 +2051,16 @@ var shared_root_methods = {
|
||||
this.$root.set_cart_id(data.cart_id);
|
||||
this.$root.overlay.frame_loading = false;
|
||||
callback()
|
||||
}, () => {
|
||||
}, (xhr, data) => {
|
||||
if (xhr.status === 429 && typeof xhr.responseURL !== "undefined") {
|
||||
this.$root.overlay.error_message = strings['cart_error_429'];
|
||||
this.$root.overlay.frame_loading = false;
|
||||
this.$root.overlay.error_url_after = this.$root.newTabTarget;
|
||||
this.$root.overlay.error_url_after_new_tab = true;
|
||||
} else {
|
||||
this.$root.overlay.error_message = strings['cart_error'];
|
||||
this.$root.overlay.frame_loading = false;
|
||||
}
|
||||
})
|
||||
},
|
||||
get_cart_id: function() {
|
||||
@@ -2142,7 +2149,14 @@ var shared_root_computed = {
|
||||
if (this.subevent) {
|
||||
target = this.target_url + this.subevent + '/';
|
||||
}
|
||||
return target;
|
||||
var parameters = this.$root.consent_parameter
|
||||
if (this.$root.additionalURLParams) {
|
||||
parameters += `&${this.$root.additionalURLParams}`
|
||||
}
|
||||
if (parameters) {
|
||||
target += '?' + parameters.replace(/^&/, '')
|
||||
}
|
||||
return target
|
||||
},
|
||||
useIframe: function () {
|
||||
if (window.crossOriginIsolated === true) {
|
||||
|
||||
@@ -162,7 +162,15 @@ export function createWidgetStore (config: {
|
||||
return params.toString()
|
||||
},
|
||||
newTabTarget (): string {
|
||||
return this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
|
||||
let url = this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
|
||||
let parameters = this.consentParameter
|
||||
if (this.additionalURLParams) {
|
||||
parameters += `&${this.additionalURLParams}`
|
||||
}
|
||||
if (parameters) {
|
||||
url += '?' + parameters.replace(/^&/, '')
|
||||
}
|
||||
return url
|
||||
},
|
||||
formTarget (): string {
|
||||
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
|
||||
@@ -441,6 +449,7 @@ export function createWidgetStore (config: {
|
||||
this.overlay.frameLoading = false
|
||||
this.overlay.errorUrlAfter = this.newTabTarget
|
||||
this.overlay.errorUrlAfterNewTab = true
|
||||
return
|
||||
} else if (e.status === 405) {
|
||||
// Likely a redirect!
|
||||
this.targetUrl = e.responseUrl.substring(0, e.responseUrl.indexOf('/cart/add') - 18)
|
||||
@@ -460,11 +469,18 @@ export function createWidgetStore (config: {
|
||||
this.overlay.frameLoading = true
|
||||
const data = await createCart(url)
|
||||
this.setCartId(data.cart_id)
|
||||
return true
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && (e.status === 200 || (e.status >= 400 && e.status < 500))) {
|
||||
if (e instanceof ApiError && e.status === 429) {
|
||||
this.overlay.errorMessage = STRINGS.cart_error_429
|
||||
this.overlay.frameLoading = false
|
||||
this.overlay.errorUrlAfter = this.newTabTarget
|
||||
this.overlay.errorUrlAfterNewTab = true
|
||||
} else if (e instanceof ApiError && (e.status === 200 || (e.status >= 400 && e.status < 500))) {
|
||||
this.overlay.errorMessage = STRINGS.cart_error
|
||||
this.overlay.frameLoading = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
redeem (voucherCode: string, event?: Event) {
|
||||
@@ -484,7 +500,7 @@ export function createWidgetStore (config: {
|
||||
async resume () {
|
||||
if (!this.cartId && this.keepCart) {
|
||||
// create an empty cart whose id we can persist
|
||||
await this.createCart()
|
||||
if (!await this.createCart()) return
|
||||
}
|
||||
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/`
|
||||
if (this.subevent && this.isButton && this.items.length === 0) {
|
||||
|
||||
@@ -1098,6 +1098,27 @@ def test_question_upload(token_client, organizer, clist, event, order, question)
|
||||
assert order.positions.first().answers.get(question=question[0]).file
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_question_upload_optional(token_client, organizer, clist, event, order, question):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
question[0].type = 'F'
|
||||
question[0].required = False
|
||||
question[0].save()
|
||||
|
||||
resp = _redeem(token_client, organizer, clist, p.pk, {})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'incomplete'
|
||||
with scopes_disabled():
|
||||
assert resp.data['questions'] == [QuestionSerializer(question[0]).data]
|
||||
|
||||
resp = _redeem(token_client, organizer, clist, p.pk, {'answers': {question[0].pk: ""}})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
assert not order.positions.first().answers.filter(question=question[0]).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_store_failed(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -34,7 +34,7 @@ from tests.const import SAMPLE_PNG
|
||||
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.base.models import (
|
||||
Checkin, InvoiceAddress, Order, OrderPosition, ReusableMedium,
|
||||
Checkin, InvoiceAddress, Item, Order, OrderPosition, ReusableMedium,
|
||||
)
|
||||
|
||||
# Lots of this code is overlapping with test_checkin.py, and some of it is arguably redundant since it's triggering
|
||||
@@ -1253,3 +1253,489 @@ def test_annul_failures(device_client, team, organizer, clist, clist_event2, eve
|
||||
with scopes_disabled():
|
||||
ci = p.all_checkins.get()
|
||||
assert ci.successful
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_incomplete_body(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid"
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {
|
||||
'non_field_errors': ['If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.']
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_medium_for_medium(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="barcode",
|
||||
identifier="abcdef",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "abcdef", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "barcode",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'error'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_unknown_media_type(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "unknown",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {"exchange_medium_type": ["\"unknown\" is not a valid choice."]}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_disabled_media_type(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "hijkl",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'error'
|
||||
assert resp.data['reason_explanation'] == 'Medium type is not enabled for organizer.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_mismatch_media_type(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "barcode"
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'product'
|
||||
assert resp.data['reason_explanation'] == 'Incorrect medium type for product.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_no_item_policy(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'product'
|
||||
assert resp.data['reason_explanation'] == 'Product does not support medium exchange.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_new(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_reuse_replace(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.count() == 2
|
||||
assert rm.linked_orderpositions.filter(secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_exists_append(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
rm.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert rm.linked_orderpositions.count() == 2
|
||||
assert rm.linked_orderpositions.filter(secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_expired(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
expires=now() - datetime.timedelta(hours=2),
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_reuse_not_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_new_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.last())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
"exchange_link_action": "append",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_exists'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_new_not_exists(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
"exchange_link_action": "replace",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
assert rm.linked_orderpositions.get().secret == "z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchange_required(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'exchange'
|
||||
assert resp.data['media_policy'] == 'new'
|
||||
assert resp.data['media_type'] == 'nfc_uid'
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
# Force works
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"force": True,
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_original_barcode_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_original_barcode_not_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'already_exchanged'
|
||||
# Force works
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"force": True,
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_scan_medium_ok(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "12345678", {
|
||||
"source_type": "nfc_uid",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_exchanged_double_exchange(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_usage_enforced = False
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
rm.linked_orderpositions.add(order.positions.first())
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "87654321",
|
||||
"exchange_link_action": "replace",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'already_exchanged'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy,media_type",
|
||||
[
|
||||
(Item.MEDIA_POLICY_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_REUSE_OR_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_APPEND_OR_NEW, "nfc_mf0aes"),
|
||||
(Item.MEDIA_POLICY_NEW, "barcode"),
|
||||
(Item.MEDIA_POLICY_REUSE_OR_NEW, "barcode"),
|
||||
(Item.MEDIA_POLICY_APPEND_OR_NEW, "barcode"),
|
||||
]
|
||||
)
|
||||
def test_exchange_unsupported_media_type_for_new(token_client, organizer, clist, event, order, item, media_policy, media_type):
|
||||
organizer.settings.set(f'reusable_media_type_{media_type}', True)
|
||||
# Shouldn't be configurable, but test that the logic is solid anyway
|
||||
item.media_type = media_type
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": media_type,
|
||||
"exchange_medium_identifier": "12345678",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy",
|
||||
[
|
||||
Item.MEDIA_POLICY_NEW,
|
||||
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||
]
|
||||
)
|
||||
def test_exchange_rejected_media_identifier(token_client, organizer, clist, event, order, item, media_policy):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "08RANDOM",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'medium_invalid'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"media_policy",
|
||||
[
|
||||
Item.MEDIA_POLICY_NEW,
|
||||
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||
]
|
||||
)
|
||||
def test_exchange_create_gift_card(token_client, organizer, clist, event, order, item, media_policy):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard = True
|
||||
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard_currency = "EUR"
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = media_policy
|
||||
item.save()
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "0412345",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(identifier="0412345")
|
||||
assert rm.linked_giftcard.currency == "EUR"
|
||||
|
||||
@@ -3141,24 +3141,13 @@ def test_order_create_use_medium(token_client, organizer, event, item, quota, qu
|
||||
@pytest.mark.django_db
|
||||
def test_order_create_add_to_medium(token_client, organizer, event, item, quota, question, medium):
|
||||
item.media_type = medium.type
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||
item.save()
|
||||
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
|
||||
res['positions'][0]['item'] = item.pk
|
||||
res['positions'][0]['use_reusable_medium'] = medium.pk
|
||||
res['positions'][0]['add_to_reusable_medium'] = medium.pk
|
||||
res['positions'][0]['answers'][0]['question'] = question.pk
|
||||
|
||||
# do not use use_reusable_medium and add_to_reusable_medium
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
del res['positions'][0]['use_reusable_medium']
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
@@ -3179,8 +3168,9 @@ def test_order_create_add_to_medium(token_client, organizer, event, item, quota,
|
||||
medium.refresh_from_db()
|
||||
assert medium.linked_orderpositions.count() == 2
|
||||
|
||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||
item.save()
|
||||
res['positions'][0]['use_reusable_medium'] = medium.pk
|
||||
del res['positions'][0]['add_to_reusable_medium']
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
|
||||
@@ -1186,7 +1186,7 @@ def test_rules_reasoning_prefer_number_over_date(event, position, clist):
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_position_queries(django_assert_max_num_queries, position, clist):
|
||||
with django_assert_max_num_queries(13) as captured:
|
||||
with django_assert_max_num_queries(12) as captured:
|
||||
perform_checkin(position, clist, {})
|
||||
if 'sqlite' not in settings.DATABASES['default']['ENGINE']:
|
||||
assert any('FOR UPDATE' in s['sql'] for s in captured)
|
||||
|
||||
@@ -4123,8 +4123,8 @@ def test_giftcard_multiple(event):
|
||||
for p in order.payments.all():
|
||||
p.payment_provider.execute_payment(None, p)
|
||||
|
||||
assert order.payments.get(info__icontains=gc1.pk).amount == Decimal('12.00')
|
||||
assert order.payments.get(info__icontains=gc2.pk).amount == Decimal('11.00')
|
||||
assert order.payments.get(amount=Decimal("12.00")).info_data["gift_card"] == gc1.pk
|
||||
assert order.payments.get(amount=Decimal("11.00")).info_data["gift_card"] == gc2.pk
|
||||
gc1 = GiftCard.objects.get(pk=gc1.pk)
|
||||
assert gc1.value == 0
|
||||
gc2 = GiftCard.objects.get(pk=gc2.pk)
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
from django import urls
|
||||
from django.test import override_settings
|
||||
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
def test_site_url_domain():
|
||||
with override_settings(SITE_URL='https://example.com'):
|
||||
assert build_absolute_uri('control:auth.login') == 'https://example.com/control/login'
|
||||
assert mainreverse_absolute('control:auth.login') == 'https://example.com/control/login'
|
||||
|
||||
|
||||
def test_site_url_subpath():
|
||||
with override_settings(SITE_URL='https://example.com/presale'):
|
||||
old_prefix = urls.get_script_prefix()
|
||||
urls.set_script_prefix('/presale/')
|
||||
assert build_absolute_uri('control:auth.login') == 'https://example.com/presale/control/login'
|
||||
assert mainreverse_absolute('control:auth.login') == 'https://example.com/presale/control/login'
|
||||
urls.set_script_prefix(old_prefix)
|
||||
|
||||
@@ -26,7 +26,7 @@ from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
|
||||
from pretix.testutils.queries import assert_num_queries
|
||||
|
||||
|
||||
@@ -248,20 +248,20 @@ def test_event_custom_domain_cache_clear(env):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_main_domain_absolute(env):
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://example.com/mrmcd/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://example.com/mrmcd/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_custom_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
KnownDomain.objects.create(domainname='barfoo', organizer=env[0], event=env[1])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://barfoo/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://barfoo/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_org_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://foobar/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://foobar/2015/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -269,4 +269,4 @@ def test_event_org_alt_domain_absolute(env):
|
||||
KnownDomain.objects.create(domainname='foobar', organizer=env[0])
|
||||
d = KnownDomain.objects.create(domainname='altfoo', organizer=env[0], mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
d.event_assignments.create(event=env[1])
|
||||
assert build_absolute_uri(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
assert eventreverse_absolute(env[1], 'presale:event.index') == 'http://altfoo/2015/'
|
||||
|
||||
Reference in New Issue
Block a user