Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel e1b25aebd8 Customer export: Add memberships tab (Z#23228422) 2026-06-08 16:06:18 +02:00
117 changed files with 1215 additions and 2209 deletions
+2 -1
View File
@@ -10,7 +10,8 @@ tests:
- cd src
- python manage.py check
- make all compress
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --ignore=tests/e2e --maxfail=100
- playwright install
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --maxfail=100
except:
- '/^v.*$/'
pypi:
+2 -1
View File
@@ -57,7 +57,8 @@ COPY vite.config.ts /pretix/vite.config.ts
RUN pip3 install -U \
pip \
setuptools && \
setuptools \
wheel && \
cd /pretix && \
PRETIX_DOCKER_BUILD=TRUE pip3 install \
-e ".[memcached]" \
+1 -8
View File
@@ -46,14 +46,12 @@ 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"``, ``"exchange"``, or ``"error"``
:>json string status: ``"ok"``, ``"incomplete"``, 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
@@ -69,8 +67,6 @@ 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**:
@@ -228,9 +224,6 @@ 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``
+1 -5
View File
@@ -602,8 +602,7 @@ 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. Advanced features like medium
exchange are only supported on the new API.
URL-safe. We recommend to use our new :ref:`check-in API <rest-checkin>` instead.
: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
@@ -742,9 +741,6 @@ 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``.
+1 -1
View File
@@ -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"``, ``"reuse_or_new"``, ``"append"``, and ``"append_or_new"``.
Possible values are ``null``, ``"new"``, ``"reuse"``, and ``"reuse_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.
+2 -1
View File
@@ -1069,7 +1069,8 @@ 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 be connected to the given reusable medium, identified by its ID)
* ``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)
* ``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``
+1 -1
View File
@@ -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.eventreverse_absolute
.. autofunction:: pretix.multidomain.urlreverse.build_absolute_uri
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::
+91 -109
View File
@@ -370,14 +370,14 @@
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"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==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.2"
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
@@ -427,9 +427,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"version": "0.129.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
"integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
"dev": true,
"license": "MIT",
"funding": {
@@ -758,9 +758,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
"integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
"cpu": [
"arm64"
],
@@ -775,9 +775,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"version": "1.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==",
"cpu": [
"arm64"
],
@@ -792,9 +792,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
"integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
"cpu": [
"x64"
],
@@ -809,9 +809,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
"integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
"cpu": [
"x64"
],
@@ -826,9 +826,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"version": "1.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==",
"cpu": [
"arm"
],
@@ -843,16 +843,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"version": "1.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==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -863,16 +860,13 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
"integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -883,16 +877,13 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"version": "1.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==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -903,16 +894,13 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"version": "1.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==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -923,16 +911,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"version": "1.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==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -943,16 +928,13 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"version": "1.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==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -963,9 +945,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
"integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
"cpu": [
"arm64"
],
@@ -980,9 +962,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"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==",
"cpu": [
"wasm32"
],
@@ -999,9 +981,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"version": "1.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==",
"cpu": [
"arm64"
],
@@ -1016,9 +998,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"version": "1.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==",
"cpu": [
"x64"
],
@@ -3176,9 +3158,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
@@ -3352,9 +3334,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [
{
"type": "opencollective",
@@ -3371,7 +3353,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -3628,14 +3610,14 @@
}
},
"node_modules/rolldown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
"integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0"
"@oxc-project/types": "=0.129.0",
"@rolldown/pluginutils": "1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -3644,27 +3626,27 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.3"
"@rolldown/binding-android-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"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
"integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
"dev": true,
"license": "MIT"
},
@@ -4343,9 +4325,9 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4483,17 +4465,17 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
"integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.15",
"rolldown": "1.0.3",
"tinyglobby": "^0.2.17"
"postcss": "^8.5.14",
"rolldown": "1.0.0",
"tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
+9 -7
View File
@@ -29,12 +29,12 @@ classifiers = [
dependencies = [
"arabic-reshaper==3.0.1", # Support for Arabic in reportlab
"babel",
"BeautifulSoup4==4.15.*",
"bleach==6.4.*",
"BeautifulSoup4==4.14.*",
"bleach==6.3.*",
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=49.0.0",
"css-inline==0.21.*",
"cryptography>=48.0.0",
"css-inline==0.20.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"Django[argon2]==5.2.*",
@@ -93,7 +93,7 @@ dependencies = [
"redis==7.4.*",
"reportlab==4.5.*",
"requests==2.32.*",
"sentry-sdk==2.63.*",
"sentry-sdk==2.61.*",
"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.8.*",
"webauthn==2.7.*",
"zeep==4.3.*"
]
@@ -125,7 +125,7 @@ dev = [
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest-playwright",
"pytest==9.1.*",
"pytest==9.0.*",
"playwright",
"responses",
]
@@ -139,6 +139,8 @@ build-backend = "backend"
backend-path = ["_build"]
requires = [
"setuptools",
"setuptools-rust",
"wheel",
"importlib_metadata",
"tomli",
]
-2
View File
@@ -110,8 +110,6 @@ 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')
)
-8
View File
@@ -88,19 +88,11 @@ 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)
+2 -5
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -173,7 +173,7 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
)
def get_event_url(self, event):
return eventreverse_absolute(event, 'presale:event.index')
return build_absolute_uri(event, 'presale:event.index')
class Meta:
model = Event
@@ -871,7 +871,6 @@ 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',
@@ -886,7 +885,6 @@ 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',
@@ -972,7 +970,6 @@ 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',
+36 -17
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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 eventreverse_absolute(instance.order.event, 'presale:event.order.pay', kwargs={
return build_absolute_uri(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 eventreverse_absolute(instance.event, 'presale:event.order', kwargs={
return build_absolute_uri(instance.event, 'presale:event.order', kwargs={
'order': instance.code,
'secret': instance.secret,
})
@@ -1043,13 +1043,15 @@ 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', 'discount')
'requested_valid_from', 'use_reusable_medium', 'add_to_reusable_medium', 'discount')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1061,6 +1063,8 @@ 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():
@@ -1076,6 +1080,9 @@ 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(
@@ -1150,6 +1157,12 @@ 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
@@ -1589,7 +1602,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')})
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium', 'add_to_reusable_medium')})
if simulate:
pos.order = order._wrapped
else:
@@ -1663,6 +1676,7 @@ 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)
@@ -1704,17 +1718,14 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
answ.options.add(*options)
if use_reusable_medium:
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)
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])
use_reusable_medium.log_action(
'pretix.reusable_medium.linked_orderposition.added',
data={
@@ -1722,7 +1733,15 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
'linked_orderposition': pos.pk,
}
)
use_reusable_medium.touch()
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,
}
)
if not simulate:
for cp in delete_cps:
+4 -5
View File
@@ -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 mainreverse_absolute
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -71,7 +71,7 @@ class OrganizerSerializer(I18nAwareModelSerializer):
slug = serializers.CharField(read_only=True)
def get_organizer_url(self, organizer):
return eventreverse_absolute(organizer, 'presale:organizer.index')
return build_absolute_uri(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': mainreverse_absolute('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
@@ -605,7 +605,6 @@ 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',
+7 -53
View File
@@ -69,10 +69,8 @@ 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, RequiredMediaExchangeError, RequiredQuestionsError, SQLLogic,
perform_checkin,
CheckInError, 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
@@ -456,8 +454,7 @@ 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,
exchange_medium_type=None, exchange_medium_identifier=None):
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False):
if not checkinlists:
raise ValidationError('No check-in list passed.')
@@ -466,7 +463,6 @@ 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,
@@ -526,7 +522,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:
medium = ReusableMedium.objects.active().filter(
media = ReusableMedium.objects.active().filter(
Exists(ReusableMedium.linked_orderpositions.through.objects.filter(reusablemedium_id=OuterRef('pk')))
).get(
organizer_id=checkinlists[0].event.organizer_id,
@@ -634,7 +630,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 = medium.linked_orderpositions.all().select_related("order").prefetch_related("addons")
linked_ops = media.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
@@ -665,7 +661,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 = medium
reusable_medium_used = media
op_candidates.append(op)
if list_by_event[op.order.event_id].addon_match:
op_candidates += list(op.addons.all())
@@ -792,10 +788,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
if str(q.pk) in answers_data:
try:
if q.type == Question.TYPE_FILE:
if answers_data[str(q.pk)]:
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
else:
given_answers[q] = None
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
else:
given_answers[q] = q.clean_answer(answers_data[str(q.pk)])
except (ValidationError, BaseValidationError):
@@ -808,14 +801,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
locale = op.order.event.settings.locale
with language(locale):
try:
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(
perform_checkin(
op=op,
clist=list_by_event[op.order.event_id],
given_answers=given_answers,
@@ -833,25 +819,7 @@ 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',
@@ -863,18 +831,6 @@ 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={
@@ -1062,8 +1018,6 @@ 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'),
)
+1 -1
View File
@@ -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 performance
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
def list(self, request, **kwargs):
date = serializers.DateTimeField().to_representation(now())
queryset = self.filter_queryset(self.get_queryset())
+2 -2
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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': eventreverse_absolute(client.organizer, 'presale:organizer.index').rstrip('/'),
'iss': build_absolute_uri(client.organizer, 'presale:organizer.index').rstrip('/'),
'aud': client.client_id,
'exp': int(expires.timestamp()),
'iat': int(time.time()),
+3 -3
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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: eventreverse_absolute(
lambda order: build_absolute_uri(
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: eventreverse_absolute(
lambda op: build_absolute_uri(
event,
'presale:event.order.position', kwargs={
'order': op.order.code,
+57 -3
View File
@@ -40,11 +40,12 @@ from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from pretix.base.settings import PERSON_NAME_SCHEMES
from ..exporter import ListExporter, OrganizerLevelExportMixin
from ..exporter import MultiSheetListExporter, OrganizerLevelExportMixin
from ..models import Membership
from ..signals import register_multievent_data_exporters
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts')
category = pgettext_lazy('export_category', 'Customer accounts')
@@ -54,13 +55,20 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
def get_required_organizer_permission(cls) -> str:
return 'organizer.customers:write'
@property
def sheets(self):
return (
('customers', _('Customers')),
('memberships', _('Memberships')),
)
@property
def additional_form_fields(self):
return OrderedDict(
[]
)
def iterate_list(self, form_data):
def iterate_customers(self, form_data):
qs = self.organizer.customers.prefetch_related('provider')
headers = [
@@ -109,6 +117,52 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
]
yield row
def iterate_memberships(self, form_data):
qs = Membership.objects.filter(
customer__organizer=self.organizer
).prefetch_related('membership_type').select_related('customer', 'granted_in', 'granted_in__order')
headers = [
_('Customer ID'),
_('External identifier'),
_('Email'),
_('Test mode'),
_('Canceled'),
_('Membership type'),
_('Purchase ticket'),
_('Start date'),
_('Start time'),
_('End date'),
_('End time'),
_('Name'),
]
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
headers.append(_('Name') + ': ' + str(label))
yield headers
tz = get_current_timezone()
for obj in qs:
row = [
obj.customer.identifier,
obj.customer.external_identifier,
obj.customer.email or '',
_('Yes') if obj.testmode else _('No'),
_('Yes') if obj.canceled else _('No'),
str(obj.membership_type.name),
f'{obj.granted_in.order.code}-{obj.granted_in.positionid}' if obj.granted_in else None,
obj.date_start.astimezone(tz).strftime('%Y-%m-%d'),
obj.date_start.astimezone(tz).strftime('%H:%M'),
obj.date_end.astimezone(tz).strftime('%Y-%m-%d'),
obj.date_end.astimezone(tz).strftime('%H:%M'),
obj.attendee_name or '',
]
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
row.append(obj.attendee_name_parts.get(k, ''))
yield row
def get_filename(self):
return '{}_customers'.format(self.organizer.slug)
+3 -3
View File
@@ -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 eventreverse_absolute
from ...multidomain.urlreverse import build_absolute_uri
from ..exporter import (
ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin,
)
@@ -429,7 +429,7 @@ class OrderListExporter(MultiSheetListExporter):
]))
row.append(
eventreverse_absolute(order.event, 'presale:event.order', kwargs={
build_absolute_uri(order.event, 'presale:event.order', kwargs={
'order': order.code,
'secret': order.secret,
})
@@ -855,7 +855,7 @@ class OrderListExporter(MultiSheetListExporter):
]))
row.append(
eventreverse_absolute(order.event, 'presale:event.order.position', kwargs={
build_absolute_uri(order.event, 'presale:event.order.position', kwargs={
'order': order.code,
'secret': op.web_secret,
'position': op.positionid
+1 -7
View File
@@ -64,13 +64,7 @@ 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,
@@ -78,7 +72,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()]),
giftcard_secret,
medium.linked_giftcard.secret if medium.linked_giftcard_id else '',
medium.notes,
]
+90 -76
View File
@@ -22,7 +22,9 @@
import datetime
import logging
import math
import re
import textwrap
import unicodedata
from collections import defaultdict
from decimal import Decimal
from io import BytesIO
@@ -56,8 +58,8 @@ from pretix.base.services.currencies import SOURCE_NAMES
from pretix.base.signals import register_invoice_renderers
from pretix.base.templatetags.money import money_filter
from pretix.helpers.reportlab import (
FontFallbackParagraph, PlainTextParagraph, ThumbnailingImageReader,
normalize_text, register_ttf_font_if_new, reshaper,
FontFallbackParagraph, ThumbnailingImageReader, register_ttf_font_if_new,
reshaper,
)
from pretix.presale.style import get_fonts
@@ -257,8 +259,18 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
register_ttf_font_if_new(family + ' B I', finders.find(styles['bolditalic']['truetype']))
def _normalize(self, text):
# alias kept for plugin compatibility
return normalize_text(text)
# reportlab does not support unicode combination characters
# It's important we do this before we use ArabicReshaper
text = unicodedata.normalize("NFKC", text)
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
# to resolve all ligatures and python-bidi to switch RTL texts.
try:
text = "<br />".join(get_display(reshaper.reshape(l)) for l in re.split("<br ?/>", text))
except:
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
return text
def _upper(self, val):
# We uppercase labels, but not in every language
@@ -339,15 +351,10 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
return 'invoice.pdf', 'application/pdf', buffer.read()
def _clean_text(self, text, tags=None):
# For backwards compatibility with customer content, we need to support tags like <br> and <b> in a few text
# fields. Therefore, we can't use PlainTextParagraph for these, but run bleach instead to limit the allowed
# tags.
return self._normalize(
bleach.clean(
text,
tags=set(tags) if tags else set()
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')
)
return self._normalize(bleach.clean(
text,
tags=set(tags) if tags else set()
).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
class PaidMarker(Flowable):
@@ -398,7 +405,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
invoice_to_top = 52 * mm
def _draw_invoice_to(self, canvas):
p = PlainTextParagraph(self.invoice.address_invoice_to, style=self.stylesheet['Normal'])
p = FontFallbackParagraph(self._clean_text(self.invoice.address_invoice_to),
style=self.stylesheet['Normal'])
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
@@ -409,8 +417,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
invoice_from_top = 17 * mm
def _draw_invoice_from(self, canvas):
p = PlainTextParagraph(
self.invoice.full_invoice_from,
p = FontFallbackParagraph(
self._clean_text(self.invoice.full_invoice_from),
style=self.stylesheet['InvoiceFrom']
)
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
@@ -540,12 +548,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
def _draw_event(self, canvas):
def shorten(txt):
txt = str(txt)
p = PlainTextParagraph(txt, style=self.stylesheet['Normal'])
txt = bleach.clean(txt, tags=set()).strip()
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height)
while p_size[1] > 2 * self.stylesheet['Normal'].leading:
txt = ' '.join(txt.replace('', '').split()[:-1]) + ''
p = PlainTextParagraph(txt, style=self.stylesheet['Normal'])
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height)
return txt
@@ -563,7 +572,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
else:
p_str = shorten(self.invoice.event.name)
p = PlainTextParagraph(p_str, style=self.stylesheet['Normal'])
p = FontFallbackParagraph(self._normalize(p_str.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
p.wrapOn(canvas, self.event_width, self.event_height)
p_size = p.wrap(self.event_width, self.event_height)
p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1])
@@ -636,37 +645,39 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
type_info_text = self.invoice.transmission_type_instance.pdf_info_text()
if type_info_text:
story.append(PlainTextParagraph(
story.append(FontFallbackParagraph(
type_info_text,
self.stylesheet['WarningBlock']
))
if self.invoice.custom_field:
story.append(PlainTextParagraph(
story.append(FontFallbackParagraph(
'{}: {}'.format(
str(self.invoice.event.settings.invoice_address_custom_field),
self.invoice.custom_field,
self._clean_text(str(self.invoice.event.settings.invoice_address_custom_field)),
self._clean_text(self.invoice.custom_field),
),
self.stylesheet['Normal']
))
if self.invoice.internal_reference:
story.append(PlainTextParagraph(
pgettext('invoice', 'Customer reference: {reference}').format(
reference=self.invoice.internal_reference,
),
story.append(FontFallbackParagraph(
self._normalize(pgettext('invoice', 'Customer reference: {reference}').format(
reference=self._clean_text(self.invoice.internal_reference),
)),
self.stylesheet['Normal']
))
if self.invoice.invoice_to_vat_id:
story.append(PlainTextParagraph(
pgettext('invoice', 'Customer VAT ID') + ': ' + self.invoice.invoice_to_vat_id,
story.append(FontFallbackParagraph(
self._normalize(pgettext('invoice', 'Customer VAT ID')) + ': ' +
self._clean_text(self.invoice.invoice_to_vat_id),
self.stylesheet['Normal']
))
if self.invoice.invoice_to_beneficiary:
story.append(PlainTextParagraph(
pgettext('invoice', 'Beneficiary') + ':\n' + self.invoice.invoice_to_beneficiary,
story.append(FontFallbackParagraph(
self._normalize(pgettext('invoice', 'Beneficiary')) + ':<br />' +
self._clean_text(self.invoice.invoice_to_beneficiary),
self.stylesheet['Normal']
))
@@ -696,11 +707,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
story = [
NextPageTemplate('FirstPage'),
PlainTextParagraph(
(
FontFallbackParagraph(
self._normalize(
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
else pgettext('invoice', 'Invoice')
) if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'),
) if not self.invoice.is_cancellation else self._normalize(pgettext('invoice', 'Cancellation')),
self.stylesheet['Heading1']
),
Spacer(1, 5 * mm),
@@ -722,17 +733,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
]
if has_taxes:
tdata = [(
PlainTextParagraph(pgettext('invoice', 'Description'), self.stylesheet['Bold']),
PlainTextParagraph(pgettext('invoice', 'Qty'), self.stylesheet['BoldRightNoSplit']),
PlainTextParagraph(pgettext('invoice', 'Tax rate'), self.stylesheet['BoldRightNoSplit']),
PlainTextParagraph(pgettext('invoice', 'Net'), self.stylesheet['BoldRightNoSplit']),
PlainTextParagraph(pgettext('invoice', 'Gross'), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']),
)]
else:
tdata = [(
PlainTextParagraph(pgettext('invoice', 'Description'), self.stylesheet['Bold']),
PlainTextParagraph(pgettext('invoice', 'Qty'), self.stylesheet['BoldRightNoSplit']),
PlainTextParagraph(pgettext('invoice', 'Amount'), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']),
)]
def _group_key(line):
@@ -769,8 +780,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
max_height = self.stylesheet['Normal'].leading * 5
p_style = self.stylesheet['Normal']
for __ in range(1000):
p = PlainTextParagraph(
curr_description,
p = FontFallbackParagraph(
self._clean_text(curr_description, tags=['br']),
p_style
)
h = p.wrap(max_width, doc.height)[1]
@@ -851,7 +862,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
# Group together at the end of the invoice
request_show_service_date = period_line
elif period_line:
description_p_list.append(PlainTextParagraph(
description_p_list.append(FontFallbackParagraph(
period_line,
self.stylesheet['Fineprint']
))
@@ -863,7 +874,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
net_price=money_filter(net_value, self.invoice.event.currency),
gross_price=money_filter(gross_value, self.invoice.event.currency),
)
description_p_list.append(PlainTextParagraph(
description_p_list.append(FontFallbackParagraph(
single_price_line,
self.stylesheet['Fineprint']
))
@@ -872,11 +883,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
description_p_list.pop(0),
str(len(lines)),
localize(tax_rate) + " %",
PlainTextParagraph(
FontFallbackParagraph(
money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
self.stylesheet['NormalRight']
),
PlainTextParagraph(
FontFallbackParagraph(
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
self.stylesheet['NormalRight']
),
@@ -893,14 +904,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
single_price_line = pgettext('invoice', 'Single price: {price}').format(
price=money_filter(gross_value, self.invoice.event.currency),
)
description_p_list.append(PlainTextParagraph(
description_p_list.append(FontFallbackParagraph(
single_price_line,
self.stylesheet['Fineprint']
))
tdata.append((
description_p_list.pop(0),
str(len(lines)),
PlainTextParagraph(
FontFallbackParagraph(
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
self.stylesheet['NormalRight']
),
@@ -933,12 +944,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
if has_taxes:
tdata.append([
PlainTextParagraph(pgettext('invoice', 'Invoice total'), self.stylesheet['Bold']), '', '', '',
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '',
money_filter(total, self.invoice.event.currency)
])
else:
tdata.append([
PlainTextParagraph(pgettext('invoice', 'Invoice total'), self.stylesheet['Bold']), '',
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '',
money_filter(total, self.invoice.event.currency)
])
@@ -947,12 +958,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
pending_sum = self.invoice.order.pending_sum
if pending_sum != total:
tdata.append(
[PlainTextParagraph(pgettext('invoice', 'Received payments'), self.stylesheet['Normal'])] +
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] +
(['', '', ''] if has_taxes else ['']) +
[money_filter(pending_sum - total, self.invoice.event.currency)]
)
tdata.append(
[PlainTextParagraph(pgettext('invoice', 'Outstanding payments'), self.stylesheet['Bold'])] +
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] +
(['', '', ''] if has_taxes else ['']) +
[money_filter(pending_sum, self.invoice.event.currency)]
)
@@ -969,12 +980,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
s=Sum('amount')
)['s'] or Decimal('0.00')
tdata.append(
[PlainTextParagraph(pgettext('invoice', 'Paid by gift card'), self.stylesheet['Normal'])] +
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] +
(['', '', ''] if has_taxes else ['']) +
[money_filter(giftcard_sum, self.invoice.event.currency)]
)
tdata.append(
[PlainTextParagraph(pgettext('invoice', 'Remaining amount'), self.stylesheet['Bold'])] +
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] +
(['', '', ''] if has_taxes else ['']) +
[money_filter(total - giftcard_sum, self.invoice.event.currency)]
)
@@ -997,14 +1008,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
story.append(Spacer(1, 10 * mm))
if request_show_service_date:
story.append(PlainTextParagraph(
pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date),
story.append(FontFallbackParagraph(
self._normalize(pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date)),
self.stylesheet['Normal']
))
if self.invoice.payment_provider_text:
story.append(FontFallbackParagraph(
self._clean_text(self.invoice.payment_provider_text, tags=['br', 'b']),
self._normalize(self.invoice.payment_provider_text),
self.stylesheet['Normal']
))
@@ -1028,10 +1039,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
]
thead = [
PlainTextParagraph(pgettext('invoice', 'Tax rate'), self.stylesheet['Fineprint']),
PlainTextParagraph(pgettext('invoice', 'Net value'), self.stylesheet['FineprintRight']),
PlainTextParagraph(pgettext('invoice', 'Gross value'), self.stylesheet['FineprintRight']),
PlainTextParagraph(pgettext('invoice', 'Tax'), self.stylesheet['FineprintRight']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']),
''
]
tdata = [thead]
@@ -1042,7 +1053,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
continue
tax = taxvalue_map[idx]
tdata.append([
PlainTextParagraph(localize(rate) + " % " + name, self.stylesheet['Fineprint']),
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
money_filter(gross - tax, self.invoice.event.currency),
money_filter(gross, self.invoice.event.currency),
money_filter(tax, self.invoice.event.currency),
@@ -1061,7 +1072,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
table.setStyle(TableStyle(tstyledata))
story.append(Spacer(5 * mm, 5 * mm))
story.append(KeepTogether([
PlainTextParagraph(pgettext('invoice', 'Included taxes'), self.stylesheet['FineprintHeading']),
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']),
table
]))
@@ -1078,7 +1089,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
net = gross - tax
tdata.append([
PlainTextParagraph(localize(rate) + " % " + name, self.stylesheet['Fineprint']),
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
fmt(net), fmt(gross), fmt(tax), ''
])
@@ -1087,13 +1098,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
story.append(KeepTogether([
Spacer(1, height=2 * mm),
PlainTextParagraph(
pgettext(
FontFallbackParagraph(
self._normalize(pgettext(
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
'{date}, this corresponds to:'
).format(rate=localize(self.invoice.foreign_currency_rate),
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT")),
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"))),
self.stylesheet['Fineprint']
),
Spacer(1, height=3 * mm),
@@ -1102,14 +1113,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate:
foreign_total = round_decimal(total * self.invoice.foreign_currency_rate)
story.append(Spacer(1, 5 * mm))
story.append(PlainTextParagraph(
story.append(FontFallbackParagraph(self._normalize(
pgettext(
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
'{date}, the invoice total corresponds to {total}.'
).format(rate=localize(self.invoice.foreign_currency_rate),
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"),
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
total=fmt(foreign_total)),
total=fmt(foreign_total))),
self.stylesheet['Fineprint']
))
@@ -1151,8 +1162,11 @@ class Modern1Renderer(ClassicInvoiceRenderer):
def _draw_invoice_from(self, canvas):
if not self.invoice.address_invoice_from:
return
c = self.invoice.address_invoice_from.strip().split('\n')
p = PlainTextParagraph(' · '.join(c), style=self.stylesheet['Sender'])
c = [
self._clean_text(l)
for l in self.invoice.address_invoice_from.strip().split('\n')
]
p = FontFallbackParagraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender'])
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
super()._draw_invoice_from(canvas)
@@ -1211,8 +1225,8 @@ class Modern1Renderer(ClassicInvoiceRenderer):
_draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm, **kwargs)
]
p = PlainTextParagraph(
date_format(self.invoice.date, "DATE_FORMAT"),
p = FontFallbackParagraph(
self._normalize(date_format(self.invoice.date, "DATE_FORMAT")),
style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2)
)
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
@@ -1269,7 +1283,7 @@ class Modern1SimplifiedRenderer(Modern1Renderer):
i = []
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
i.append(PlainTextParagraph(
i.append(FontFallbackParagraph(
pgettext('invoice', 'Event date: {date_range}').format(
date_range=self.invoice.event.get_date_range_display(),
),
@@ -44,8 +44,7 @@ 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,
stdin=subprocess.DEVNULL
cwd=Path(__file__).parent.parent.parent.parent.parent
)
def cleanup():
+14 -20
View File
@@ -26,7 +26,6 @@ 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
@@ -57,7 +56,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, force_create=False):
def handle_unknown(self, organizer, identifier, user, auth):
pass
def handle_new(self, organizer, medium, user, auth):
@@ -89,32 +88,23 @@ 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 = True
supports_orderposition = False
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
def handle_unknown(self, organizer, identifier, user, auth):
from pretix.base.models import GiftCard, ReusableMedium
create_giftcard = organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool)
if create_giftcard or force_create:
if organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool):
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():
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
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'),
)
m = ReusableMedium.objects.create(
type=self.identifier,
identifier=identifier,
@@ -126,6 +116,10 @@ class NfcUidMediaType(BaseMediaType):
'pretix.reusable_medium.created.auto',
user=user, auth=auth,
)
gc.log_action(
'pretix.giftcards.created',
user=user, auth=auth,
)
return m
@@ -135,7 +129,7 @@ class NfcMf0aesMediaType(BaseMediaType):
icon = 'pretixbase/img/media/nfc_secure.svg'
medium_created_by_server = False
supports_giftcard = True
supports_orderposition = True
supports_orderposition = False
def handle_new(self, organizer, medium, user, auth):
from pretix.base.models import GiftCard
+2 -4
View File
@@ -282,12 +282,10 @@ def metric_values():
# Throwaway metrics
exact_tables = [
Order, Invoice, Event, Organizer
Order, OrderPosition, Invoice, Event, Organizer
]
for m in apps.get_models(): # Count all models
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):
if 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)
+4 -14
View File
@@ -74,7 +74,6 @@ 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.
@@ -95,16 +94,15 @@ class LocaleMiddleware(MiddlewareMixin):
if '-' not in language and settings_holder.settings.region:
language += '-' + settings_holder.settings.region
if settings_holder.settings.region:
region = settings_holder.settings.region
set_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:
region = gs.settings.region
set_region(gs.settings.region)
translation.activate(language)
set_region(region)
request.LANGUAGE_CODE = get_language_without_region()
tzname = None
@@ -370,16 +368,8 @@ class RejectInvalidInputMiddleware(MiddlewareMixin):
if "\x00" in request.META['QUERY_STRING'] or "%00" in request.META['QUERY_STRING']:
raise BadRequest("Invalid characters in input.")
if request.method in ('POST', 'PUT', 'PATCH') and request.content_type == "application/x-www-form-urlencoded":
try:
post_data = request.POST.lists()
except BadRequest:
# Reading request.POST wasn't possible, probably an invalid charset. Django will crash once we actually
# use request.POST, but if we don't, let's not crash it (required for some weird payment provider
# webhooks, e.g. computop).
pass
else:
if any("\x00" in value for key, value_list in post_data for value in value_list):
raise BadRequest("Invalid characters in input.")
if any("\x00" in value for key, value_list in request.POST.lists() for value in value_list):
raise BadRequest("Invalid characters in input.")
class CustomCommonMiddleware(CommonMiddleware):
+3 -3
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
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': mainreverse_absolute('control:user.settings'),
'url': build_absolute_uri('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': (mainreverse_absolute('control:auth.forgot.recover')
'url': (build_absolute_uri('control:auth.forgot.recover')
+ '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self)))
},
None, locale=self.locale, user=self
-6
View File
@@ -346,14 +346,11 @@ 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')),
@@ -369,9 +366,6 @@ 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(
+4 -4
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
try:
with language(self.locale):
@@ -178,7 +178,7 @@ class Customer(LoggedModel):
{
**self.get_email_context(),
'message': str(message),
'url': eventreverse_absolute(self.organizer, 'presale:organizer.customer.index')
'url': build_absolute_uri(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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.forms.customer import TokenGenerator
ctx = self.get_email_context()
token = TokenGenerator().make_token(self)
ctx['url'] = eventreverse_absolute(
ctx['url'] = build_absolute_uri(
self.organizer,
'presale:organizer.customer.activate'
) + '?id=' + self.identifier + '&token=' + token
+5 -18
View File
@@ -724,7 +724,7 @@ class Event(EventMixin, LoggedModel):
@property
def social_image(self):
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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(eventreverse_absolute(self, 'presale:event.index'), img)
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
def _seats(self, ignore_voucher=None):
from .seating import Seat
@@ -883,8 +883,6 @@ class Event(EventMixin, LoggedModel):
ItemProgramTime, ItemVariationMetaValue, Question, Quota,
)
is_cross_organizer = other.organizer_id != self.organizer_id
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
# Plugins can create data in installed() hook based on existing data of the event.
# Calling set_active_plugins() results in defaults being created while actually data
@@ -913,15 +911,6 @@ class Event(EventMixin, LoggedModel):
for emv in EventMetaValue.objects.filter(event=other):
emv.pk = None
emv.event = self
if is_cross_organizer:
try:
emv.property = self.organizer.meta_properties.get(name=emv.property.name)
except EventMetaProperty.DoesNotExist:
meta_prop = emv.property
meta_prop.pk = None
meta_prop.organizer = self.organizer
meta_prop.save(force_insert=True)
emv.property = meta_prop
emv.save(force_insert=True)
for fl in EventFooterLink.objects.filter(event=other):
@@ -975,13 +964,13 @@ class Event(EventMixin, LoggedModel):
if i.tax_rule_id:
i.tax_rule = tax_map[i.tax_rule_id]
if i.grant_membership_type and is_cross_organizer:
if i.grant_membership_type and other.organizer_id != self.organizer_id:
i.grant_membership_type = None
i.save() # no force_insert since i.picture.save could have already inserted
i.log_action('pretix.object.cloned')
if require_membership_types and not is_cross_organizer:
if require_membership_types and other.organizer_id == self.organizer_id:
i.require_membership_types.set(require_membership_types)
if not i.all_sales_channels:
@@ -996,7 +985,7 @@ class Event(EventMixin, LoggedModel):
v._prefetched_objects_cache = {}
v.save(force_insert=True)
if require_membership_types and not is_cross_organizer:
if require_membership_types and other.organizer_id == self.organizer_id:
v.require_membership_types.set(require_membership_types)
if not v.all_sales_channels:
v.limit_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in limit_sales_channels]))
@@ -1880,8 +1869,6 @@ class EventMetaValue(LoggedModel):
self.event.cache.clear()
def save(self, *args, **kwargs):
if self.event and self.event.organizer != self.property.organizer:
raise ValidationError(_("Property and event must belong to the same organizer."))
super().save(*args, **kwargs)
if self.event:
self.event.cache.clear()
+6 -16
View File
@@ -452,16 +452,11 @@ 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 reusable media, use regular one-off tickets")),
(None, _("Don't use re-usable media, use regular one-off tickets")),
(MEDIA_POLICY_REUSE, _('Require an existing medium to be re-used')),
(MEDIA_POLICY_NEW, _('Require a previously unknown medium to be newly added')),
(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')),
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used')),
)
objects = ItemQuerySetManager()
@@ -774,7 +769,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 reusable physical medium, you can attach a physical media policy. '
'If this product should be stored on a re-usable 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.'
@@ -783,7 +778,7 @@ class Item(LoggedModel):
media_type = models.CharField(
max_length=100,
null=True, blank=True,
choices=[(None, _("Don't use reusable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
choices=[(None, _("Don't use re-usable 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 '
@@ -1000,11 +995,6 @@ 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 '
@@ -2230,7 +2220,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 reused for example in ticket layouts.
for its items. This information can be re-used for example in ticket layouts.
:param event: The event this property is defined for.
:type event: Event
+1 -4
View File
@@ -129,10 +129,7 @@ class ReusableMedium(LoggedModel):
@property
def is_expired(self):
return self.expires and self.expires < now()
def touch(self):
self.save(update_fields=['updated'])
return self.expires and self.expires > now()
class Meta:
unique_together = (("identifier", "type", "organizer"),)
+28 -50
View File
@@ -354,60 +354,38 @@ 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
@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,
)
if not transaction.get_connection().in_atomic_block:
raise Exception('gracefully_delete_bulk should only be called in atomic transaction!')
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)
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):
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,
}
)
Order.gracefully_delete_bulk(self.event, Order.objects.filter(pk=self.pk), user, auth)
order_gracefully_delete.send(self.event, order=self)
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))
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()
def email_confirm_secret(self):
return self.tagged_secret("email_confirm", 9)
+3 -6
View File
@@ -118,10 +118,7 @@ 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.get('position', {}).get('x', 0),
zpos[1] + r.get('position', {}).get('y', 0),
)
rpos = (zpos[0] + r['position']['x'], zpos[1] + r['position']['y'])
row_label = None
if r.get('row_label'):
row_label = r['row_label'].replace("%s", r.get('row_number', str(ri)))
@@ -150,8 +147,8 @@ class SeatingPlan(LoggedModel):
zone=z['name'],
category=s['category'],
sorting_rank=rank,
x=rpos[0] + s.get('position', {}).get('x', 0),
y=rpos[1] + s.get('position', {}).get('y', 0),
x=rpos[0] + s['position']['x'],
y=rpos[1] + s['position']['y'],
)
+2 -2
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
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 = mainreverse_absolute(
order_url = build_absolute_uri(
'control:event.order',
kwargs={
'organizer': logentry.event.organizer.slug,
+2 -2
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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 = eventreverse_absolute(self.event, 'presale:event.payment.unlock', kwargs={
hidden_url = build_absolute_uri(self.event, 'presale:event.payment.unlock', kwargs={
'hash': hashlib.sha256((self.settings._hidden_seed + self.event.slug).encode()).hexdigest(),
})
+1 -6
View File
@@ -372,11 +372,6 @@ DEFAULT_VARIABLES = OrderedDict((
"editor_sample": _("Atlantis"),
"evaluate": lambda op, order, ev: str(getattr(order.invoice_address.country, 'name', '')) if getattr(order, 'invoice_address', None) else ''
}),
("invoice_custom_field", {
"label": _("Invoice custom recipient field"),
"editor_sample": _("Custom recipient field"),
"evaluate": lambda op, order, ev: order.invoice_address.custom_field if getattr(order, 'invoice_address', None) else ''
}),
("addons", {
"label": _("List of Add-Ons"),
"editor_sample": _("Add-on 1\n2x Add-on 2"),
@@ -1067,7 +1062,7 @@ class Renderer:
except:
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
p = Paragraph(text, style=style) # not using AutoEscapeParagraph is safe as we escape above
p = Paragraph(text, style=style)
return p, ad, lineheight
def _draw_textcontainer(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
+2 -2
View File
@@ -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, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
if item.media_policy in (Item.MEDIA_POLICY_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 in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
elif item.media_policy == Item.MEDIA_POLICY_REUSE:
raise CartPositionError(error_messages['media_usage_not_implemented'])
# Item removed from sales channel
+2 -30
View File
@@ -867,15 +867,6 @@ 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:
@@ -948,7 +939,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, reusable_medium=None):
gate=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.
@@ -964,7 +955,6 @@ 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
"""
# !!!!!!!!!
@@ -1045,7 +1035,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.select_related("order", "item")
opqs = OrderPosition.all
if type != Checkin.TYPE_EXIT:
opqs = opqs.select_for_update(of=OF_SELF)
op = opqs.get(pk=op.pk)
@@ -1111,24 +1101,6 @@ 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
+3 -3
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -455,7 +455,7 @@ def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> Non
schedule,
organizer,
exporter,
mainreverse_absolute(
build_absolute_uri(
'control:organizer.export',
kwargs={
'organizer': organizer.slug,
@@ -481,7 +481,7 @@ def scheduled_event_export(self, event: Event, schedule: int) -> None:
schedule,
event,
exporter,
mainreverse_absolute(
build_absolute_uri(
'control:event.orders.export',
kwargs={
'event': event.slug,
+3 -3
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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=eventreverse_absolute(
orderurl=build_absolute_uri(
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=eventreverse_absolute(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.open', kwargs={
'order': order.code,
'secret': order.secret,
+2 -176
View File
@@ -23,13 +23,10 @@ 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.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
from pretix.base.models import GiftCardAcceptance
from pretix.base.models.media import MediumKeySet
def create_nfc_mf0aes_keyset(organizer):
@@ -73,174 +70,3 @@ 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
+3 -3
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
@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': mainreverse_absolute(
'settings_url': build_absolute_uri(
'control:user.settings.notifications',
),
'disable_url': mainreverse_absolute(
'disable_url': build_absolute_uri(
'control:user.settings.notifications.off',
kwargs={
'token': user.notifications_token,
+1 -1
View File
@@ -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, Item.MEDIA_POLICY_APPEND_OR_NEW):
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_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(
+29 -29
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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: eventreverse_absolute(
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
event,
'presale:event.order.open', kwargs={
'order': order.code,
'secret': order.secret,
'hash': order.email_confirm_secret()
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
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: eventreverse_absolute(
url_func=lambda order, event: build_absolute_uri(
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: eventreverse_absolute(
sample_url_func=lambda event: build_absolute_uri(
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: eventreverse_absolute(
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
event,
'presale:event.order.modify', kwargs={
'order': order.code,
'secret': order.secret,
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
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: eventreverse_absolute(
'url_products_change', ['order', 'event'], lambda order, event: build_absolute_uri(
event,
'presale:event.order.change', kwargs={
'order': order.code,
'secret': order.secret,
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
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: eventreverse_absolute(
'url_cancel', ['order', 'event'], lambda order, event: build_absolute_uri(
event,
'presale:event.order.cancel', kwargs={
'order': order.code,
'secret': order.secret,
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
event,
'presale:event.order.cancel', kwargs={
'order': 'F8VVL',
@@ -471,7 +471,7 @@ def base_placeholders(sender, **kwargs):
),
),
SimpleFunctionalTextPlaceholder(
'url', ['event', 'position'], lambda event, position: eventreverse_absolute(
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
event,
'presale:event.order.position',
kwargs={
@@ -480,7 +480,7 @@ def base_placeholders(sender, **kwargs):
'position': position.positionid
}
),
lambda event: eventreverse_absolute(
lambda event: build_absolute_uri(
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: eventreverse_absolute(
url_func=lambda event, position: build_absolute_uri(
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: eventreverse_absolute(
sample_url_func=lambda event: build_absolute_uri(
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: eventreverse_absolute(
'url_info_change', ['position', 'event'], lambda position, event: build_absolute_uri(
event,
'presale:event.order.position.modify', kwargs={
'order': position.order.code,
'secret': position.web_secret,
'position': position.positionid
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
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: eventreverse_absolute(
'url_products_change', ['position', 'event'], lambda position, event: build_absolute_uri(
event,
'presale:event.order.position.change', kwargs={
'order': position.order.code,
'secret': position.web_secret,
'position': position.positionid
}
), lambda event: eventreverse_absolute(
), lambda event: build_absolute_uri(
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: eventreverse_absolute(
lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.waitinglist.remove'
) + '?voucher=' + waiting_list_voucher.code,
lambda event: eventreverse_absolute(
lambda event: build_absolute_uri(
event,
'presale:event.waitinglist.remove',
) + '?voucher=68CYU2H6ZTP3WLK5',
),
SimpleFunctionalTextPlaceholder(
'url', ['waiting_list_voucher', 'event'],
lambda waiting_list_voucher, event: eventreverse_absolute(
lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + waiting_list_voucher.code,
lambda event: eventreverse_absolute(
lambda event: build_absolute_uri(
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,
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
build_absolute_uri(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']),
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
build_absolute_uri(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([
eventreverse_absolute(
build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + c
for c in voucher_list
]),
lambda event: ' \n'.join([
eventreverse_absolute(
build_absolute_uri(
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: eventreverse_absolute(event, 'presale:event.index', kwargs={
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
}), lambda event: eventreverse_absolute(event, 'presale:event.index', kwargs={
}), lambda event: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
})
+2 -2
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
@receiver(signal=periodic_task)
@@ -121,7 +121,7 @@ def send_update_notification_email():
)
),
{
'url': mainreverse_absolute('control:global.update')
'url': build_absolute_uri('control:global.update')
},
)
+3 -16
View File
@@ -211,25 +211,12 @@ DEFAULTS = {
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
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 "
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 "
"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,
+1 -1
View File
@@ -11,7 +11,6 @@
<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 }}" />
@@ -22,4 +21,5 @@
{% block content %}{% endblock %}
</div>
</body>
<script src="{% static "pretixbase/js/errors.js" %}"></script>
</html>
@@ -2,14 +2,13 @@
{% load i18n %}
{% load rich_text %}
{% load static %}
{% load wrap_in %}
{% block title %}{% trans "Redirect" %}{% endblock %}
{% block content %}
<i class="fa fa-link fa-fw big-icon"></i>
<div class="error-details">
<h1>{% trans "Redirect" %}</h1>
<h3>
{% blocktrans trimmed with host=hostname|wrap_in:'strong' %}
{% blocktrans trimmed with host="<strong>"|add:hostname|add:"</strong>"|safe %}
The link you clicked on wants to redirect you to a destination on the website {{ host }}.
{% endblocktrans %}
{% blocktrans trimmed %}
+2 -2
View File
@@ -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 (
eventreverse_absolute, get_organizer_domain,
build_absolute_uri, 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 = eventreverse_absolute(self.organizer, 'presale:organizer.index')
self.fields['slug'].widget.prefix = build_absolute_uri(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']
+1 -7
View File
@@ -1342,13 +1342,7 @@ class QuestionAnswerFilterForm(forms.Form):
opqs = opqs.filter(canceled=False)
if fdata.get("item", "") != "":
i = fdata.get("item", "")
if '-' in i:
opqs = opqs.filter(
item_id=i.split('-')[0],
variation_id=i.split('-')[1],
)
else:
opqs = opqs.filter(item_id=i)
opqs = opqs.filter(item_id__in=(i,))
return opqs
+2 -3
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
class OrganizerForm(I18nModelForm):
@@ -636,7 +636,6 @@ 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',
@@ -791,7 +790,7 @@ class MailSettingsForm(SettingsForm):
}
if 'url' in base_parameters:
placeholders['url'] = eventreverse_absolute(
placeholders['url'] = build_absolute_uri(
self.organizer,
'presale:organizer.customer.activate'
) + '?token=' + get_random_string(30)
-1
View File
@@ -746,7 +746,6 @@ 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.'),
+22 -23
View File
@@ -38,7 +38,6 @@ from pretix import __version__
from pretix.base.models import Order, OrderPayment, Transaction
from pretix.base.plugins import get_all_plugins
from pretix.base.templatetags.money import money_filter
from pretix.helpers.reportlab import PlainTextParagraph
from pretix.plugins.reports.exporters import ReportlabExportMixin
from pretix.settings import DATA_DIR
@@ -80,23 +79,23 @@ class SysReport(ReportlabExportMixin):
style_small.fontSize = 6
story = [
PlainTextParagraph("System report", headlinestyle),
Paragraph("System report", headlinestyle),
Spacer(1, 5 * mm),
PlainTextParagraph("Usage", subheadlinestyle),
Paragraph("Usage", subheadlinestyle),
Spacer(1, 5 * mm),
self._usage_table(),
Spacer(1, 5 * mm),
PlainTextParagraph("Installed versions", subheadlinestyle),
Paragraph("Installed versions", subheadlinestyle),
Spacer(1, 5 * mm),
self._tech_table(),
Spacer(1, 5 * mm),
PlainTextParagraph("Plugins", subheadlinestyle),
Paragraph("Plugins", subheadlinestyle),
Spacer(1, 5 * mm),
PlainTextParagraph(self._get_plugin_versions(), style_small),
Paragraph(self._get_plugin_versions(), style_small),
Spacer(1, 5 * mm),
PlainTextParagraph("Custom templates", subheadlinestyle),
Paragraph("Custom templates", subheadlinestyle),
Spacer(1, 5 * mm),
PlainTextParagraph(self._get_custom_templates(), style_small),
Paragraph(self._get_custom_templates(), style_small),
Spacer(1, 5 * mm),
]
@@ -122,13 +121,13 @@ class SysReport(ReportlabExportMixin):
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
]
tdata = [
[PlainTextParagraph("Site URL:", style), Paragraph(settings.SITE_URL, style)],
[PlainTextParagraph("pretix version:", style), Paragraph(__version__, style)],
[PlainTextParagraph("Python version:", style), Paragraph(sys.version, style)],
[PlainTextParagraph("Platform:", style), Paragraph(platform.platform(), style)],
[Paragraph("Site URL:", style), Paragraph(settings.SITE_URL, style)],
[Paragraph("pretix version:", style), Paragraph(__version__, style)],
[Paragraph("Python version:", style), Paragraph(sys.version, style)],
[Paragraph("Platform:", style), Paragraph(platform.platform(), style)],
[
PlainTextParagraph("Database engine:", style),
PlainTextParagraph(settings.DATABASES["default"]["ENGINE"], style),
Paragraph("Database engine:", style),
Paragraph(settings.DATABASES["default"]["ENGINE"], style),
],
]
table = Table(tdata, colWidths=colwidths, repeatRows=0)
@@ -207,7 +206,7 @@ class SysReport(ReportlabExportMixin):
year_last = now().year
tdata = [
[
PlainTextParagraph(l, style_small_head)
Paragraph(l, style_small_head)
for l in (
"Time frame",
"Currency",
@@ -258,19 +257,19 @@ class SysReport(ReportlabExportMixin):
tdata.append(
(
PlainTextParagraph(
Paragraph(
date_format(first_day, "M Y")
+ " "
+ date_format(after_day - timedelta(days=1), "M Y"),
style_small,
),
PlainTextParagraph(c, style_small),
PlainTextParagraph(str(orders_count), style_small) if i == 0 else "",
PlainTextParagraph(money_filter(revenue_data.get("s_net") or 0, c), style_small),
PlainTextParagraph(str(testmode_count), style_small) if i == 0 else "",
PlainTextParagraph(str(unconfirmed_count), style_small) if i == 0 else "",
PlainTextParagraph(str(revenue_data.get("c") or 0), style_small),
PlainTextParagraph(money_filter(revenue_data.get("s_gross") or 0, c), style_small),
Paragraph(c, style_small),
Paragraph(str(orders_count), style_small) if i == 0 else "",
Paragraph(money_filter(revenue_data.get("s_net") or 0, c), style_small),
Paragraph(str(testmode_count), style_small) if i == 0 else "",
Paragraph(str(unconfirmed_count), style_small) if i == 0 else "",
Paragraph(str(revenue_data.get("c") or 0), style_small),
Paragraph(money_filter(revenue_data.get("s_gross") or 0, c), style_small),
)
)
@@ -66,7 +66,6 @@
<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,8 +54,6 @@
<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>
@@ -81,14 +79,6 @@
</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,7 +222,6 @@
<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,7 +2,6 @@
{% load i18n %}
{% load bootstrap3 %}
{% load getitem %}
{% load icon %}
{% block inner %}
{% if team %}
<h1>{% trans "Team:" %} {{ team.name }}</h1>
@@ -26,18 +25,6 @@
<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 %}
@@ -50,17 +37,6 @@
{% 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 %}
@@ -19,7 +19,9 @@
{% endif %}
</h1>
{{ layout|json_script:"editor-data" }}
<script type="application/json" id="editor-data">
{{ layout|safe }}
</script>
<div class="row">
<div class="col-md-9">
<div class="panel panel-default panel-pdf-editor">
+2 -5
View File
@@ -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, Item, LogEntry, Order, OrderPosition
from pretix.base.models import Checkin, LogEntry, Order, OrderPosition
from pretix.base.models.checkin import CheckinList
from pretix.base.models.orders import PrintLog
from pretix.base.permissions import AnyPermissionOf
@@ -401,14 +401,13 @@ 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.prefetch_related('variations')
} for i in self.request.event.items.filter(active=True).prefetch_related('variations')
],
**super().get_context_data(),
}
@@ -533,8 +532,6 @@ 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):
+7 -11
View File
@@ -41,7 +41,7 @@ from collections import OrderedDict, defaultdict
from decimal import Decimal
from io import BytesIO
from itertools import groupby
from urllib.parse import urlsplit
from urllib.parse import urlparse, urlsplit
from zoneinfo import ZoneInfo
import bleach
@@ -64,6 +64,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import NoReverseMatch, reverse
from django.utils.functional import cached_property
from django.utils.html import conditional_escape, format_html
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
@@ -96,9 +97,7 @@ 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 (
eventreverse_absolute, get_event_domain,
)
from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain
from pretix.presale.views.widget import (
version_default as widget_version_default,
)
@@ -1149,11 +1148,8 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
if request.POST.get("delete") == "yes":
try:
with transaction.atomic():
Order.gracefully_delete_bulk(
request.event,
request.event.orders.filter(testmode=True),
user=self.request.user
)
for order in request.event.orders.filter(testmode=True):
order.gracefully_delete(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.'))
@@ -1738,10 +1734,10 @@ class EventQRCode(EventPermissionRequiredMixin, View):
permission = None
def get(self, request, *args, filetype, **kwargs):
url = eventreverse_absolute(request.event, 'presale:event.index')
url = build_absolute_uri(request.event, 'presale:event.index')
if "url" in request.GET:
if request.GET["url"].startswith(url):
if url_has_allowed_host_and_scheme(request.GET["url"], allowed_hosts=[urlparse(url).netloc]):
url = request.GET["url"]
else:
raise PermissionDenied("Untrusted URL")
+3 -17
View File
@@ -139,7 +139,6 @@ 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
@@ -241,7 +240,7 @@ class BaseOrderBulkActionView(OrderSearchMixin, EventPermissionRequiredMixin, As
raise NotImplementedError()
def execute_bulk(self, queryset: QuerySet, form: forms.Form):
qs = self.allowed_for(self.get_queryset())
qs = self.allowed_for(self.allowed_for(self.get_queryset()))
total = qs.count()
orders_with_successful_action = 0
for i, o in enumerate(qs):
@@ -395,21 +394,8 @@ class OrderDeleteBulkActionView(BaseOrderBulkActionView):
testmode=True,
)
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
def execute_single(self, instance, form: forms.Form):
instance.gracefully_delete(user=self.request.user)
class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
+8 -10
View File
@@ -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 mainreverse_absolute
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
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': mainreverse_absolute('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
@@ -2851,12 +2851,10 @@ class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['redirect_uri'] = eventreverse_absolute(
self.request.organizer, 'presale:organizer.customer.login.return',
kwargs={
'provider': self.object.pk
}
)
ctx['redirect_uri'] = build_absolute_uri(self.request.organizer, 'presale:organizer.customer.login.return',
kwargs={
'provider': self.object.pk
})
return ctx
def get_form_kwargs(self):
@@ -3087,7 +3085,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'] = eventreverse_absolute(
ctx['url'] = build_absolute_uri(
self.request.organizer,
'presale:organizer.customer.recoverpw'
) + '?id=' + self.customer.identifier + '&token=' + token
+1 -1
View File
@@ -284,7 +284,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
ctx['pdf'] = self.get_current_background()
ctx['variables'] = self.get_variables()
ctx['images'] = self.get_images()
ctx['layout'] = self.get_current_layout()
ctx['layout'] = json.dumps(self.get_current_layout())
ctx['title'] = self.title
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
ctx['maxfilesize'] = self.maxfilesize
+1 -1
View File
@@ -200,7 +200,7 @@ def giftcard_select2(request, **kwargs):
except ValueError:
page = 1
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:read', request):
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:write', request):
qs = request.organizer.issued_gift_cards.filter(
Q(secret__icontains=query)
).order_by('secret')
+2 -2
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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'] = eventreverse_absolute(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
ctx['url'] = build_absolute_uri(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
return ctx
-5
View File
@@ -29,8 +29,3 @@ class PretixHelpersConfig(AppConfig):
def ready(self):
from .monkeypatching import monkeypatch_all_at_ready
monkeypatch_all_at_ready()
# Ensure reportlab does not make any calls to the internet or the local disk
from reportlab import rl_config
rl_config.trustedHosts = []
rl_config.trustedSchemes = ['data']
-21
View File
@@ -27,7 +27,6 @@ from datetime import datetime
from http import cookies
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from PIL import Image
from requests.adapters import HTTPAdapter
from urllib3.connection import HTTPConnection, HTTPSConnection
@@ -41,8 +40,6 @@ from urllib3.util.connection import (
)
from urllib3.util.timeout import _DEFAULT_TIMEOUT
from pretix.helpers.reportlab import ThumbnailingImageReader
_cgnat_net = ipaddress.ip_network('100.64.0.0/10')
@@ -233,27 +230,9 @@ def monkeypatch_cookie_morsel():
cookies.Morsel._reserved.setdefault("partitioned", "Partitioned")
def monkeypatch_reportlab_imagereader():
from reportlab.lib import utils
old_init = utils.ImageReader.__init__
def new_init(self, fileName, ident=None): # noqa
if not isinstance(fileName, Image.Image) and not hasattr(fileName, 'read') and not hasattr(fileName, 'str'):
if not isinstance(self, ThumbnailingImageReader):
# ThumbnailingImageReader is only used by us explicitly and not by using <img> in html, so it is safe
raise SuspiciousFileOperation("reportlab should not be reading images from disk")
return types.MethodType(old_init, self)(
fileName, ident
)
utils.ImageReader.__init__ = new_init
def monkeypatch_all_at_ready():
monkeypatch_vobject_performance()
monkeypatch_pillow_safer()
monkeypatch_requests_timeout()
monkeypatch_urllib3_ssrf_protection()
monkeypatch_cookie_morsel()
monkeypatch_reportlab_imagereader()
-39
View File
@@ -20,19 +20,14 @@
# <https://www.gnu.org/licenses/>.
#
import logging
import re
import unicodedata
from arabic_reshaper import ArabicReshaper
from bidi import get_display
from django.conf import settings
from django.utils.functional import SimpleLazyObject
from django.utils.html import escape
from PIL import Image
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Paragraph
from pretix.presale.style import get_fonts
@@ -75,20 +70,6 @@ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
}))
def normalize_text(text: str) -> str:
# reportlab does not support unicode combination characters
# It's important we do this before we use ArabicReshaper
text = unicodedata.normalize("NFKC", text)
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
# to resolve all ligatures and python-bidi to switch RTL texts.
try:
text = "\n".join(get_display(reshaper.reshape(l)) for l in re.split("\n", text))
except:
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
return text
class FontFallbackParagraph(Paragraph):
def __init__(self, text, style=None, *args, **kwargs):
if style is None:
@@ -106,8 +87,6 @@ class FontFallbackParagraph(Paragraph):
if not text:
return True
font = pdfmetrics.getFont(font_name)
if not isinstance(font, TTFont):
return True
return all(
ord(c) in font.face.charToGlyph or not c.isprintable()
for c in text
@@ -123,24 +102,6 @@ class FontFallbackParagraph(Paragraph):
return family
class PlainTextParagraph(FontFallbackParagraph):
def __init__(self, text, style=None, linebreaks=True, *args, **kwargs):
if not isinstance(text, str):
if hasattr(text, '__html__'):
raise ValueError("It is contradictory to pass escaped content to PlainTextParagraph")
text = str(text)
# Normalize unicode and apply reshaping
text = normalize_text(text)
# Escape any HTML in the text
text = escape(text)
if linebreaks:
text = text.strip().replace("\n", "<br />\n")
super().__init__(text, style, *args, **kwargs)
def register_ttf_font_if_new(name, path):
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
+2 -2
View File
@@ -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 mainreverse_absolute
from pretix.helpers.urls import build_absolute_uri
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': mainreverse_absolute('control:user.settings')
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=user,
@@ -1,33 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
from django import template
from django.utils.html import format_html
register = template.Library()
logger = logging.getLogger(__name__)
@register.filter
def wrap_in(content, tag_name):
return format_html(f'<{tag_name}>{{}}</{tag_name}>', content)
-10
View File
@@ -19,7 +19,6 @@
# 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
@@ -27,15 +26,6 @@ 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))
+270 -158
View File
@@ -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-06-20 17:00+0000\n"
"PO-Revision-Date: 2026-05-22 07:03+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.6.1\n"
"X-Generator: Weblate 2026.5\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -612,17 +612,16 @@ msgstr ""
"underordnede objekter som f.eks. varianter eller pakker."
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "Kvoten er ændret"
msgstr "Håndtering af kvoter"
#: 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"
@@ -3395,12 +3394,12 @@ msgstr "Indtast venligst et kortere navn."
msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr "Feltet \"%(label)s\" må ikke indeholde specialtegn såsom \"%(chars)s\"."
msgstr ""
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr "Feltet \"%(label)s\" må ikke indeholde en URL (%(url)s)."
msgstr ""
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8291,14 +8290,19 @@ 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, 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"
"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"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8823,7 +8827,13 @@ msgid "This voucher code is not known in our database."
msgstr "Denne voucherkode genkendes ikke."
#: pretix/base/services/cart.py:165
#, python-format
#, 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."
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product."
@@ -8831,14 +8841,22 @@ 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] ""
"Voucherkoderne \"%(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."
#: pretix/base/services/cart.py:170
#, python-format
#, 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."
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 "
@@ -8848,12 +8866,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] ""
"Voucherkoderne \"%(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."
#: pretix/base/services/cart.py:176
@@ -14087,8 +14105,6 @@ 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 ""
@@ -16011,8 +16027,14 @@ 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, Århus, Danmark"
msgstr ""
"Eksempelkonferencecenter\n"
"Andeby, Danmark"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -17797,11 +17819,12 @@ 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 ""
"Der er sendt en e-mail for at informere brugeren om, at ordren er blevet "
"godkendt."
"En e-mail er blevet sendt for at fortælle brugeren at bestillingen er "
"modtaget."
#: pretix/control/logdisplay.py:574
msgid ""
@@ -17812,12 +17835,13 @@ 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 ""
"Der er sendt en e-mail for at informere brugeren om, at ordren er modtaget "
"og skal godkendes."
"En e-mail er blevet sendt for at fortælle brugeren at bestillingen er "
"modtaget og kræver betaling."
#: pretix/control/logdisplay.py:578
msgid ""
@@ -17827,22 +17851,21 @@ msgstr ""
"bestillingsdetaljer."
#: pretix/control/logdisplay.py:579
#, fuzzy
msgid "An email has been sent to notify the user that the payment failed."
msgstr ""
"Der er sendt en e-mail for at informere brugeren om, at betalingen "
"mislykkedes."
"En e-mail er blevet sendt for at fortælle brugeren at betaling er modtaget."
#: 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 ""
"Kuponen er sat til at udløbe, fordi modtageren har fjernet sig selv fra "
"ventelisten."
msgstr "En rabatkode er blevet sendt til en person på ventelisten."
#: pretix/control/logdisplay.py:590
msgid "The voucher has been changed."
@@ -17853,13 +17876,16 @@ 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 "Indkøbskurvens varer, herunder gavekortet, er blevet slettet."
msgstr "Den valgte rabatkode er blevet slettet."
#: pretix/control/logdisplay.py:593
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "A voucher has been sent to a person on the waiting list."
msgid "The voucher has been assigned to {email} through the waiting list."
msgstr "Kuponen er blevet tildelt {email} via ventelisten."
msgstr "En rabatkode er blevet sendt til en person på ventelisten."
#: pretix/control/logdisplay.py:602
#, python-brace-format
@@ -17879,8 +17905,9 @@ 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 omorganiseret."
msgstr "Kategorien er blevet slettet."
#: pretix/control/logdisplay.py:627
msgid "The tax rule has been added."
@@ -17910,14 +17937,14 @@ msgid "{user} has been invited to the team."
msgstr "{user} er blevet inviteret med i gruppen."
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
msgid "Invite for {user} has been deleted."
msgstr "Invitationen til {user} er blevet slettet."
msgstr "Invitationen til {user} er blevet trukket tilbage."
#: pretix/control/logdisplay.py:645
#, python-brace-format
#, fuzzy, python-brace-format
msgid "Invite for {user} has been resent."
msgstr "Invitationen til {user} er blevet sendt igen."
msgstr "Invitationen til {user} er blevet trukket tilbage."
#: pretix/control/logdisplay.py:656
#, python-brace-format
@@ -17935,245 +17962,294 @@ msgstr "Din e-mailadresse er blevet ændret til {email}."
#: pretix/control/logdisplay.py:673
msgid "Your account has been enabled."
msgstr "Din konto er nu aktiveret."
msgstr ""
#: pretix/control/logdisplay.py:675
msgid "Your account has been disabled."
msgstr "Din konto er blevet deaktiveret."
msgstr ""
#: pretix/control/logdisplay.py:680 pretix/presale/views/customer.py:642
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid ""
#| "The email address has been changed from \"{old_email}\" to \"{new_email}"
#| "\"."
msgid "Your email address has been changed from {old_email} to {email}."
msgstr "Din e-mailadresse er blevet ændret fra {old_email} til {email}."
msgstr ""
"E-mailadressen er blevet ændret fra \"{old_email}\" til \"{new_email}\"."
#: pretix/control/logdisplay.py:681
#, python-brace-format
#, fuzzy, python-brace-format
msgid "Your email address {email} has been confirmed."
msgstr "Din e-mailadresse {email} er blevet bekræftet."
msgstr "Din kurv er blevet opdateret."
#: pretix/control/logdisplay.py:693
#, python-brace-format
msgid "You impersonated {}."
msgstr "Du udgav dig for at være {}."
msgstr ""
#: pretix/control/logdisplay.py:694
#, python-brace-format
msgid "You stopped impersonating {}."
msgstr "Du er holdt op med at udgive dig for at være {}."
msgstr ""
#: 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 "Arrangøren er blevet ændret."
msgstr "Bestillingen er blevet ændret."
#: pretix/control/logdisplay.py:703
#, fuzzy
msgid "The organizer settings have been changed."
msgstr "Indstillingerne for arrangøren er blevet ændret."
msgstr "Gruppeindstillingerne er blevet ændret."
#: pretix/control/logdisplay.py:704 pretix/control/logdisplay.py:849
#, fuzzy
msgid "The footer links have been changed."
msgstr "Linkene i sidefoden er blevet ændret."
msgstr "Bestillingsdetaljerne er blevet ændret."
#: pretix/control/logdisplay.py:705 pretix/control/logdisplay.py:754
#, fuzzy
msgid "A scheduled export has been added."
msgstr "Der er tilføjet en planlagt eksport."
msgstr "Kvoten er blevet tilføjet."
#: pretix/control/logdisplay.py:706 pretix/control/logdisplay.py:755
#, fuzzy
msgid "A scheduled export has been changed."
msgstr "En planlagt eksport er blevet ændret."
msgstr "Produktet er blevet ændret."
#: pretix/control/logdisplay.py:707 pretix/control/logdisplay.py:756
#, fuzzy
msgid "A scheduled export has been deleted."
msgstr "En planlagt eksport er blevet slettet."
msgstr "Det valgte produkt er blevet slettet."
#: pretix/control/logdisplay.py:708 pretix/control/logdisplay.py:757
#, fuzzy
msgid "A scheduled export has been executed."
msgstr "En planlagt eksport er blevet udført."
msgstr "Det valgte produkt er blevet slettet."
#: pretix/control/logdisplay.py:709 pretix/control/logdisplay.py:758
#, python-brace-format
msgid "A scheduled export has failed: {reason}."
msgstr "En planlagt eksport er mislykket: {reason}."
msgstr ""
#: 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 ""
"Der er planlagt et nyt forsøg på at sende de e-mails, der ikke kunne leveres."
msgstr "Denne e-mail er sat i udsendelseskøen."
#: pretix/control/logdisplay.py:711
#, fuzzy
#| msgid "The order details have been changed."
msgid "Queued emails have been aborted."
msgstr "E-mails i køen er blevet afbrudt."
msgstr "Bestillingsdetaljerne er blevet ændret."
#: pretix/control/logdisplay.py:712
#, fuzzy
msgid "Gift card acceptance for another organizer has been added."
msgstr ""
"Der er nu tilføjet mulighed for at acceptere gavekort fra en anden arrangør."
msgstr "Den nye arrangør er blevet oprettet."
#: pretix/control/logdisplay.py:713
#, fuzzy
msgid "Gift card acceptance for another organizer has been removed."
msgstr ""
"Muligheden for at modtage gavekort fra en anden arrangør er blevet fjernet."
msgstr "Den nye arrangør er blevet oprettet."
#: pretix/control/logdisplay.py:714
#, fuzzy
msgid "A new gift card acceptor has been invited."
msgstr "Der er blevet inviteret en ny modtager af gavekort."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:715
#, fuzzy
msgid "A gift card acceptor has been removed."
msgstr "En gavekort-modtager er blevet fjernet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:716
#, fuzzy
msgid "A gift card issuer has been removed or declined."
msgstr "En udsteder af gavekort er blevet fjernet eller afvist."
msgstr "Den valgte kategorien er blevet slettet."
#: pretix/control/logdisplay.py:717
#, fuzzy
msgid "A new gift card issuer has been accepted."
msgstr "En ny udsteder af gavekort er blevet godkendt."
msgstr "Den valgte kategorien er blevet slettet."
#: pretix/control/logdisplay.py:718
#, fuzzy
msgid "The webhook has been created."
msgstr "Webhooken er blevet oprettet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:719
#, fuzzy
msgid "The webhook has been changed."
msgstr "Webhooken er blevet ændret."
msgstr "Kategorien 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 "Opgaverne til gentagelse af webhook-kald er blevet annulleret."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:722
#, fuzzy
msgid "The SSO provider has been created."
msgstr "SSO-udbyderen er blevet oprettet."
msgstr "Bestilling oprettet."
#: pretix/control/logdisplay.py:723
#, fuzzy
msgid "The SSO provider has been changed."
msgstr "SSO-udbyderen er blevet skiftet."
msgstr "Bestillingen er blevet ændret."
#: pretix/control/logdisplay.py:724
#, fuzzy
msgid "The SSO provider has been deleted."
msgstr "SSO-udbyderen er blevet slettet."
msgstr "Bestillingen er blevet tilbagebetalt."
#: pretix/control/logdisplay.py:725
#, fuzzy
msgid "The SSO client has been created."
msgstr "SSO-klienten er oprettet."
msgstr "Arrangementsdatoen er blevet oprettet."
#: pretix/control/logdisplay.py:726
#, fuzzy
msgid "The SSO client has been changed."
msgstr "SSO-klienten er blevet ændret."
msgstr "Kategorien er blevet ændret."
#: pretix/control/logdisplay.py:727
#, fuzzy
msgid "The SSO client has been deleted."
msgstr "SSO-klienten er blevet slettet."
msgstr "Produktet er blevet slettet."
#: pretix/control/logdisplay.py:728 pretix/control/views/organizer.py:2715
#, fuzzy
msgid "The membership type has been created."
msgstr "Medlemskabstypen er oprettet."
msgstr "Arrangementsdatoen er blevet oprettet."
#: pretix/control/logdisplay.py:729
#, fuzzy
msgid "The membership type has been changed."
msgstr "Medlemskabstypen er blevet ændret."
msgstr "Arrangementsdatoen er blevet ændret."
#: pretix/control/logdisplay.py:730
#, fuzzy
msgid "The membership type has been deleted."
msgstr "Medlemskabstypen er blevet slettet."
msgstr "Arrangementsdatoen 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 "Salgskanalen er oprettet."
msgstr "Den nye dato er blevet oprettet."
#: pretix/control/logdisplay.py:732
#, fuzzy
msgid "The sales channel has been changed."
msgstr "Salgskanalen er blevet ændret."
msgstr "Voucheren er blevet ændret."
#: pretix/control/logdisplay.py:733
#, fuzzy
#| msgid "The selected list has been deleted."
msgid "The sales channel has been deleted."
msgstr "Salgskanalen er blevet slettet."
msgstr "Den valgte lister er blevet slettet."
#: pretix/control/logdisplay.py:734
#, fuzzy
msgid "The account has been created."
msgstr "Kontoen er oprettet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:735
#, fuzzy
msgid "The account has been changed."
msgstr "Kontoen er blevet ændret."
msgstr "Kategorien er blevet ændret."
#: pretix/control/logdisplay.py:736
#, fuzzy
msgid "A membership for this account has been added."
msgstr "Der er tilføjet et medlemskab til denne konto."
msgstr "Den valgte konfiguration er blevet slettet."
#: pretix/control/logdisplay.py:737
#, fuzzy
msgid "A membership of this account has been changed."
msgstr "Et medlemskab på denne konto er blevet ændret."
msgstr "En svarmulighed er blevet ændret."
#: pretix/control/logdisplay.py:738
#, fuzzy
msgid "A membership of this account has been deleted."
msgstr "Et medlemskab tilknyttet denne konto er blevet slettet."
msgstr "Den valgte konfiguration er blevet slettet."
#: pretix/control/logdisplay.py:739
#, fuzzy
msgid "The account has been disabled and anonymized."
msgstr "Kontoen er blevet deaktiveret og anonymiseret."
msgstr "Bestillingen er blevet tilbagebetalt."
#: pretix/control/logdisplay.py:740
#, fuzzy
msgid "A new password has been requested."
msgstr "Der er anmodet om en ny adgangskode."
msgstr "Adgangskoden er blevet nulstillet."
#: pretix/control/logdisplay.py:741
#, fuzzy
msgid "A new password has been set."
msgstr "Der er blevet oprettet en ny adgangskode."
msgstr "Adgangskoden er blevet nulstillet."
#: pretix/control/logdisplay.py:743
#, fuzzy
msgid "The reusable medium has been created."
msgstr "Det genanvendelige medie er blevet oprettet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:744
#, fuzzy
msgid "The reusable medium has been created automatically."
msgstr "Det genanvendelige medie er blevet oprettet automatisk."
msgstr "Arrangementsdatoen er blevet oprettet."
#: pretix/control/logdisplay.py:745
#, fuzzy
msgid "The reusable medium has been changed."
msgstr "Det genanvendelige medie er blevet udskiftet."
msgstr "Momsreglen er blevet ændret."
#: pretix/control/logdisplay.py:746
#, fuzzy
msgid "The medium has been connected to a new ticket."
msgstr "Mediet er blevet knyttet til en ny sag."
msgstr "Denne e-mail er sat i udsendelseskøen."
#: pretix/control/logdisplay.py:747
#, fuzzy
msgid "The medium has been connected to a new gift card."
msgstr "Mediet er blevet knyttet til et nyt gavekort."
msgstr "Denne e-mail er sat i udsendelseskøen."
#: 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 "Arrangementet er blevet aflyst."
msgstr "Bestillingen er blevet annulleret."
#: pretix/control/logdisplay.py:751
#, fuzzy
msgid "An event has been deleted."
msgstr "En begivenhed er blevet slettet."
msgstr "Arrangementsdatoen er blevet slettet."
#: pretix/control/logdisplay.py:752
msgid "A removal process for personal data has been started."
msgstr "Der er iværksat en sletningsproces for personoplysninger."
msgstr ""
#: pretix/control/logdisplay.py:753
msgid "A removal process for personal data has been completed."
msgstr "En sletning af personoplysninger er blevet gennemført."
msgstr ""
#: pretix/control/logdisplay.py:759
msgid "The user has been created."
msgstr "Brugeren er blevet oprettet."
msgstr ""
#: pretix/control/logdisplay.py:760
#, python-brace-format
@@ -18181,8 +18257,6 @@ 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
@@ -18199,8 +18273,9 @@ 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 "Der er genereret en tofaktorkode til nødsituationer."
msgstr "Dine sikkerhedskoder til tofaktorgodkendelse er blevet genoprettet."
#: pretix/control/logdisplay.py:766
#, python-brace-format
@@ -18220,19 +18295,20 @@ msgstr ""
#: pretix/control/logdisplay.py:770
msgid "Notifications have been enabled."
msgstr "Notifikationer er blevet aktiveret."
msgstr ""
#: pretix/control/logdisplay.py:771
msgid "Notifications have been disabled."
msgstr "Notifikationer er blevet deaktiveret."
msgstr ""
#: pretix/control/logdisplay.py:772
msgid "Your notification settings have been changed."
msgstr "Dine notifikationsindstillinger er blevet ændret."
msgstr ""
#: pretix/control/logdisplay.py:773
#, fuzzy
msgid "This user has been anonymized."
msgstr "Denne bruger er blevet anonymiseret."
msgstr "Bestillingen er blevet tilbagebetalt."
#: pretix/control/logdisplay.py:777
msgid "Password reset mail sent."
@@ -18247,21 +18323,20 @@ 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
#, python-brace-format
#, fuzzy, python-brace-format
msgid "The organizer \"{name}\" has been deleted."
msgstr "Arrangøren \"{name}\" er blevet slettet."
msgstr "Varianten \"{value}\" 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 ansøgning er blevet flyttet til en anden venteliste."
msgstr "En bruger er føjet til gruppen."
#: pretix/control/logdisplay.py:784
msgid "The team has been created."
@@ -18276,16 +18351,19 @@ 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 "Porten er blevet oprettet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:788
#, fuzzy
msgid "The gate has been changed."
msgstr "Porten er blevet ændret."
msgstr "Kategorien er blevet ændret."
#: pretix/control/logdisplay.py:789
#, fuzzy
msgid "The gate has been deleted."
msgstr "Porten er blevet slettet."
msgstr "Gruppen er blevet slettet."
#: pretix/control/logdisplay.py:790
msgctxt "subevent"
@@ -18293,9 +18371,10 @@ 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 "Begivenhedsdatoen er blevet aflyst."
msgstr "Arrangementsdatoen er blevet ændret."
#: pretix/control/logdisplay.py:792
msgctxt "subevent"
@@ -18323,50 +18402,58 @@ 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 "Enheden er blevet oprettet."
msgstr "Arrangementsdatoen er blevet oprettet."
#: pretix/control/logdisplay.py:798
#, fuzzy
msgid "The device has been changed."
msgstr "Enheden er blevet udskiftet."
msgstr "Voucheren er blevet ændret."
#: pretix/control/logdisplay.py:799
#, fuzzy
msgid "Access of the device has been revoked."
msgstr "Adgangen til enheden er blevet tilbagekaldt."
msgstr "Enheden er blevet fjernet."
#: pretix/control/logdisplay.py:800
#, fuzzy
msgid "The device has been initialized."
msgstr "Enheden er blevet initialiseret."
msgstr "Enheden er blevet fjernet."
#: pretix/control/logdisplay.py:801
#, fuzzy
msgid "The access token of the device has been regenerated."
msgstr "Enhedens adgangstoken er blevet genereret på ny."
msgstr "Fakturaen er blevet genereret igen."
#: 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 "Gavekortet er oprettet."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:804 pretix/control/views/organizer.py:1943
#, fuzzy
msgid "The gift card has been changed."
msgstr "Gavekortet er blevet ændret."
msgstr "Gruppen er blevet oprettet."
#: pretix/control/logdisplay.py:805
#, fuzzy
msgid "A manual transaction has been performed."
msgstr "Der er udført en manuel overførsel."
msgstr "Spørgsmålet er blevet tilføjet."
#: pretix/control/logdisplay.py:806
#, fuzzy
msgid "A payment has been performed."
msgstr "Der er blevet foretaget en betaling."
msgstr "Betalingsbetingelser er blevet ændret."
#: pretix/control/logdisplay.py:807
#, fuzzy
msgid "A refund has been performed. "
msgstr "Der er blevet foretaget en tilbagebetaling. "
msgstr "Bestilling oprettet."
#: pretix/control/logdisplay.py:808
#, python-brace-format
@@ -18379,34 +18466,44 @@ 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 "Status for indtjeknings- og udskriftsloggen er blevet nulstillet."
msgstr "Tjek-ind-listen er blevet slettet."
#: pretix/control/logdisplay.py:817 pretix/control/logdisplay.py:876
#, fuzzy
#| msgid "A plugin has been enabled."
msgid "The plugin has been enabled."
msgstr "Plugin'et er blevet aktiveret."
msgstr "En plugin 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 "Plugin'et er blevet deaktiveret."
msgstr "En plugin er blevet deaktiveret."
#: pretix/control/logdisplay.py:821 pretix/control/logdisplay.py:880
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Question {val}"
msgid "Plugin {val}"
msgstr "Plugin {val}"
msgstr "Spørgsmål {val}"
#: pretix/control/logdisplay.py:836
#, fuzzy
msgid "A meta property has been added to this event."
msgstr "Der er tilføjet en metaegenskab til denne begivenhed."
msgstr "En bruger er føjet til gruppen."
#: pretix/control/logdisplay.py:837
#, fuzzy
msgid "A meta property has been removed from this event."
msgstr "En metaegenskab er blevet fjernet fra denne begivenhed."
msgstr "En bruger er blevet fjernet fra gruppen."
#: pretix/control/logdisplay.py:838
#, fuzzy
msgid "A meta property has been changed on this event."
msgstr "En metaegenskab er blevet ændret i forbindelse med denne begivenhed."
msgstr "En kvote er blevet ændret på arrangementsdatoen."
#: pretix/control/logdisplay.py:839
msgid "The event settings have been changed."
@@ -18418,27 +18515,31 @@ msgstr "Indstillingerne for download af billet er blevet ændret."
#: pretix/control/logdisplay.py:843
msgid "The shop has been taken live."
msgstr "Butikken er nu gået live."
msgstr ""
#: pretix/control/logdisplay.py:844
msgid "The shop has been taken offline."
msgstr "Butikken er blevet taget offline."
msgstr ""
#: pretix/control/logdisplay.py:845
#, fuzzy
msgid "The shop has been taken into test mode."
msgstr "Butikken er nu sat i testtilstand."
msgstr "Bestillingen er blevet tilbagebetalt."
#: pretix/control/logdisplay.py:846
#, fuzzy
msgid "The test mode has been disabled."
msgstr "Testtilstanden er blevet deaktiveret."
msgstr "Den valgte dato er blevet slettet."
#: pretix/control/logdisplay.py:847
#, fuzzy
msgid "The event has been created."
msgstr "Begivenheden er blevet oprettet."
msgstr "Arrangementsdatoen er blevet oprettet."
#: pretix/control/logdisplay.py:848
#, fuzzy
msgid "The event details have been changed."
msgstr "Begivenhedens oplysninger er blevet ændret."
msgstr "Indstillingerne for arrangementet er blevet ændret."
#: pretix/control/logdisplay.py:850
msgid "An answer option has been added to the question."
@@ -18481,9 +18582,10 @@ msgid "The check-in list has been changed."
msgstr "Tjek-ind-listen er blevet ændret."
#: pretix/control/logdisplay.py:869
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Check-in list"
msgid "Check-in list {val}"
msgstr "Tjek-ind-liste {val}"
msgstr "Tjek-ind-liste"
#: pretix/control/logdisplay.py:896
msgid "The product has been created."
@@ -18494,8 +18596,9 @@ 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 genbestilt."
msgstr "Produktet er blevet oprettet."
#: pretix/control/logdisplay.py:899
msgid "The product has been deleted."
@@ -18514,28 +18617,37 @@ 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 "Der er blevet tilføjet en medfølgende vare til dette produkt."
msgstr "En tilføjelse er blevet føjet til dette produkt."
#: pretix/control/logdisplay.py:904
#, fuzzy
msgid "A bundled item has been removed from this product."
msgstr "En medfølgende vare er blevet fjernet fra dette produkt."
msgstr "En tilføjelse er blevet fjernet fra dette produkt."
#: pretix/control/logdisplay.py:905
#, fuzzy
msgid "A bundled item has been changed on this product."
msgstr "En medfølgende vare er blevet ændret for dette produkt."
msgstr "En tilføjelse er blevet ændret 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 "Der er blevet tilføjet en programtid til dette produkt."
msgstr "En tilføjelse er blevet føjet 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 programtid for dette produkt er blevet ændret."
msgstr "En tilføjelse er blevet ændret på dette produkt."
#: 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 programtid er blevet fjernet fra dette produkt."
msgstr "En tilføjelse er blevet fjernet fra dette produkt."
#: pretix/control/logdisplay.py:915
#, python-brace-format
@@ -18553,9 +18665,9 @@ msgid "The variation \"{value}\" has been changed."
msgstr "Varianten \"{value}\" er blevet ændret."
#: pretix/control/logdisplay.py:934
#, python-brace-format
#, fuzzy, python-brace-format
msgid "Payment {local_id} has been confirmed."
msgstr "Betalingen {local_id} er blevet bekræftet."
msgstr "Betalingsmetoden er blevet ændret."
#: pretix/control/logdisplay.py:935
#, fuzzy, python-brace-format
+6 -6
View File
@@ -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-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"
"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"
"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.6.1\n"
"X-Generator: Weblate 2026.5\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 jedes Produktes "
"können die Ticketgenerierung auch in den Einstellungen von 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-06-09 20:00+0000\n"
"Last-Translator: Mira <weller@rami.io>\n"
"PO-Revision-Date: 2026-05-27 15:20+0000\n"
"Last-Translator: Raphael Michel <michel@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.6.1\n"
"X-Generator: Weblate 2026.5\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. Du "
"kannst die Ticketgenerierung auch in den Einstellungen jedes Produktes "
"aktiviert, bei denen die Option \"Berechtigt zum Eintritt\" gesetzt ist. Sie "
"können die Ticketgenerierung auch in den Einstellungen von jedes Produktes "
"einzeln abschalten."
#: pretix/base/settings.py:1813
+4 -4
View File
@@ -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-06-08 17:00+0000\n"
"Last-Translator: Sébastien BRUNEAU <s.bruneau@beauvaisis.fr>\n"
"PO-Revision-Date: 2026-05-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
"fr/>\n"
"Language: 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.6.1\n"
"X-Generator: Weblate 2026.5\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ée)"
msgstr "Adresse de courriel (répété)"
#: pretix/presale/forms/checkout.py:68
msgid ""
+41 -29
View File
@@ -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-06-18 21:00+0000\n"
"Last-Translator: Szurofka Márton <szurofka.marton@sze.hu>\n"
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.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 2026.6.1\n"
"X-Generator: Weblate 5.15.2\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 "PayPal"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
msgid "Venmo"
msgstr "Venmo"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:36
#: pretix/static/pretixpresale/js/walletdetection.js:38
msgid "Apple Pay"
msgstr "Apple Pay"
msgstr ""
#: 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 "PayPal Későbbi Fizetés"
msgstr ""
#: 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 "Fizetésimód nem elérhető"
msgstr ""
#: 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 "Résztvevők (megrendelve)"
msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)"
msgstr "Résztvevők (fizetve)"
msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue"
@@ -187,13 +187,18 @@ 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
@@ -271,8 +276,6 @@ 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"
@@ -299,20 +302,26 @@ 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 elem (elavult)"
msgstr "Szöveg"
#: pretix/static/pretixcontrol/js/ui/editor.js:911
#, fuzzy
#| msgid "Text object"
msgid "Text box"
msgstr "Szövegdoboz"
msgstr "Szöveg"
#: 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 "Kép helye"
msgstr "Vonalkód terület"
#: pretix/static/pretixcontrol/js/ui/editor.js:917
msgid "Powered by pretix"
@@ -352,16 +361,19 @@ 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 elegendő a minimum "
"követelménynek."
"A választott színek kontrasztja elégséges, és valószínűleg jól olvasható!"
#: pretix/static/pretixcontrol/js/ui/main.js:318
msgid ""
@@ -372,7 +384,7 @@ msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:446
#: pretix/static/pretixcontrol/js/ui/main.js:466
msgid "Search query"
msgstr "Keresési lekérdezés"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:464
msgid "All"
@@ -392,7 +404,7 @@ msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:845
msgid "Invalid page number."
msgstr "Helytelen oldalszám."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1003
msgid "Use a different name internally"
@@ -453,7 +465,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 "Kosara tartalma hamarosan lejár."
msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:62
#, fuzzy
@@ -520,32 +532,32 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget"
msgid "Quantity"
msgstr "Mennyiség"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget"
msgid "Decrease quantity"
msgstr "Mennyiség csökkentése"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget"
msgid "Increase quantity"
msgstr "Mennyiség növelése"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr "Események szűrése a következő szerint"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr "Szűrés"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
msgid "Price"
msgstr "Ár"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:22
#, javascript-format
+174 -200
View File
@@ -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: 2026-06-20 17:00+0000\n"
"Last-Translator: Nikita Mitasov <me@ch4og.com>\n"
"PO-Revision-Date: 2025-10-17 16:55+0000\n"
"Last-Translator: fd <fd@denkena-consulting.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 2026.6.1\n"
"X-Generator: Weblate 5.13.3\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,50 +565,65 @@ 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
@@ -851,13 +866,14 @@ msgid "<a {a_attr}>powered by {name} based on pretix</a>"
msgstr "на базе <a %(a_attr)s>pretix</a>"
#: pretix/base/context.py:55
#, python-format
#, fuzzy, python-format
#| msgid "<a %(a_attr)s>event ticketing powered by pretix</a>"
msgid "<a %(a_attr)s>ticketing powered by pretix</a>"
msgstr "<a %(a_attr)s>продажа билетов на базе pretix</a>"
msgstr "<a %(a_attr)s>event ticketing powered by pretix</a>"
#: pretix/base/context.py:64
msgid "source code"
msgstr "исходный код"
msgstr "Промокод"
#: pretix/base/customersso/oidc.py:61
#, python-brace-format
@@ -2761,8 +2777,10 @@ 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
@@ -3158,7 +3176,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"
@@ -4588,7 +4606,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
@@ -4690,7 +4708,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"
@@ -4942,8 +4960,11 @@ 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
@@ -7740,9 +7761,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
@@ -7787,7 +7808,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
@@ -7803,7 +7824,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
@@ -7822,7 +7843,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
@@ -7836,7 +7857,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
@@ -7858,7 +7879,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."
@@ -7892,7 +7913,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
@@ -8062,8 +8083,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."
@@ -9352,7 +9373,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
@@ -9386,7 +9407,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
@@ -9396,16 +9417,15 @@ 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 ""
@@ -9418,20 +9438,23 @@ 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 ""
@@ -9443,17 +9466,16 @@ 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"
@@ -9556,8 +9578,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 ""
@@ -9724,9 +9746,11 @@ msgid "This order is not yet approved."
msgstr "Этот заказ не ожидает утверждения."
#: pretix/base/services/checkin.py:999 pretix/base/services/checkin.py:1003
#, python-brace-format
#, fuzzy, python-brace-format
#| msgctxt "subevent"
#| msgid "This voucher is not valid for this event date."
msgid "This ticket is only valid after {datetime}."
msgstr "Этот билет действителен только после {datetime}."
msgstr "Этот ваучер не действителен на эту дату мероприятия."
#: pretix/base/services/checkin.py:1013 pretix/base/services/checkin.py:1017
#, fuzzy, python-brace-format
@@ -9884,12 +9908,6 @@ 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
@@ -10145,8 +10163,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 ""
@@ -10154,24 +10172,30 @@ 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
@@ -10179,12 +10203,14 @@ 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 ""
@@ -12195,16 +12221,6 @@ 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
@@ -12224,15 +12240,6 @@ 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
@@ -12262,17 +12269,6 @@ 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
@@ -12289,18 +12285,6 @@ 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
@@ -12319,19 +12303,6 @@ 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
@@ -12383,15 +12354,6 @@ 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
@@ -12413,17 +12375,6 @@ 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
@@ -12475,17 +12426,6 @@ 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
@@ -13708,8 +13648,10 @@ msgid "created by"
msgstr "создано"
#: pretix/base/templates/pretixbase/email/order_details.html:34
#, fuzzy
#| msgid "Bancontact"
msgid "Contact:"
msgstr "Связаться:"
msgstr "Bancontact"
#: pretix/base/templates/pretixbase/email/order_details.html:54
#, fuzzy, python-format
@@ -13742,8 +13684,10 @@ 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 "Связаться"
msgstr "Bancontact"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
@@ -13814,17 +13758,16 @@ 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
@@ -15759,7 +15702,6 @@ 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"
@@ -15850,8 +15792,10 @@ 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 ""
@@ -16817,8 +16761,10 @@ 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 "Аккаунт с таким ID клиента уже существует."
msgstr "Ваучер с этим кодом уже существует."
#: pretix/control/forms/organizer.py:1076
#: pretix/control/templates/pretixcontrol/organizers/customer.html:62
@@ -16941,8 +16887,10 @@ 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"
@@ -17225,8 +17173,10 @@ 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
@@ -18862,7 +18812,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
@@ -22154,7 +22104,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html:3
#, python-format
msgid "Currently available: %(num)s"
msgstr "Сейчас доступно: %(num)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/global_license.html:8
msgid ""
@@ -24077,7 +24027,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
@@ -27874,8 +27824,10 @@ 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 ""
@@ -30809,8 +30761,6 @@ 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 ""
@@ -30821,27 +30771,23 @@ 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
@@ -30864,7 +30810,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 ""
@@ -31003,7 +30949,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
@@ -34417,9 +34363,11 @@ 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
@@ -34611,8 +34559,10 @@ 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
@@ -34664,8 +34614,10 @@ 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
@@ -34997,12 +34949,21 @@ msgstr[1] ""
msgstr[2] ""
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "You can choose between %(min_count)s and %(max_count)s options from this "
#| "category."
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] "Вы можете выбрать не более %(max_count)s варианта из этой категории."
msgstr[1] "Вы можете выбрать не более %(max_count)s вариантов из этой категории."
msgstr[2] "Вы можете выбрать не более %(max_count)s вариантов из этой категории."
msgstr[0] ""
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
"категории."
msgstr[1] ""
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
"категории."
msgstr[2] ""
"Вы можете выбрать между вариантами %(min_count)s и %(max_count)s из этой "
"категории."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
#, python-format
@@ -35402,7 +35363,8 @@ msgid ""
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:516
#, python-format
#, fuzzy, python-format
#| msgid "The items in your cart are reserved for you for %(minutes)s minutes."
msgid "The items in your cart are reserved for you for %(minutes)s minutes."
msgstr "Позиции в вашей корзине зарезервированы для вас на %(minutes)s минут."
@@ -35451,7 +35413,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..."
@@ -35460,7 +35422,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
@@ -35784,7 +35746,7 @@ msgstr "Изменить цену для %(item)s"
#: pretix/presale/templates/pretixpresale/event/fragment_quota_left.html:4
#, python-format
msgid "%(num)s currently available"
msgstr "%(num)s доступно сейчас"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:5
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:5
@@ -36037,23 +35999,32 @@ 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
@@ -36580,11 +36551,14 @@ 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 ""
@@ -36603,8 +36577,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 eventreverse_absolute, mainreverse
from pretix.multidomain.urlreverse import build_absolute_uri, mainreverse
register = template.Library()
@@ -49,7 +49,7 @@ class EventURLNode(URLNode):
url = ''
try:
if self.absolute:
url = eventreverse_absolute(event, view_name, kwargs=kwargs)
url = build_absolute_uri(event, view_name, kwargs=kwargs)
elif self.event is False:
url = mainreverse(view_name, kwargs)
else:
-10
View File
@@ -32,7 +32,6 @@
# 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
@@ -191,15 +190,6 @@ 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.
+9 -9
View File
@@ -64,7 +64,7 @@ from pretix.base.timeframes import (
from pretix.control.forms.widgets import Select2
from pretix.helpers.filenames import safe_for_filename
from pretix.helpers.iter import chunked_iterable
from pretix.helpers.reportlab import PlainTextParagraph
from pretix.helpers.reportlab import FontFallbackParagraph
from pretix.helpers.templatetags.jsonfield import JSONExtract
from pretix.plugins.reports.exporters import ReportlabExportMixin
@@ -343,7 +343,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
]
story = [
PlainTextParagraph(
FontFallbackParagraph(
cl.name,
headlinestyle
),
@@ -351,7 +351,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
if cl.subevent:
story += [
Spacer(1, 3 * mm),
PlainTextParagraph(
FontFallbackParagraph(
'{} ({} {})'.format(
cl.subevent.name,
cl.subevent.get_date_range_display(),
@@ -381,10 +381,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
headrowstyle.fontName = 'OpenSansBd'
for q in questions:
txt = str(q.question)
p = PlainTextParagraph(txt, headrowstyle)
p = FontFallbackParagraph(txt, headrowstyle)
while p.wrap(colwidths[len(tdata[0])], 5000)[1] > 30 * mm:
txt = txt[:len(txt) - 50] + "..."
p = PlainTextParagraph(txt, headrowstyle)
p = FontFallbackParagraph(txt, headrowstyle)
tdata[0].append(p)
qs = self._get_queryset(cl, form_data)
@@ -431,8 +431,8 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
CBFlowable(bool(op.last_checked_in)) if not op.blocked else '',
'' if op.order.status != Order.STATUS_PAID else '',
op.order.code,
PlainTextParagraph(name, self.get_style()),
PlainTextParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('<br>', '<br/>'), self.get_style()),
FontFallbackParagraph(name, self.get_style()),
FontFallbackParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('<br>', '<br/>'), self.get_style()),
]
acache = {}
if op.addon_to:
@@ -443,10 +443,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
for q in questions:
txt = acache.get(q.pk, '')
txt = bleach.clean(txt, tags={'br'}).strip().replace('<br>', '<br/>')
p = PlainTextParagraph(txt, self.get_style())
p = FontFallbackParagraph(txt, self.get_style())
while p.wrap(colwidths[len(row)], 5000)[1] > 50 * mm:
txt = txt[:len(txt) - 50] + "..."
p = PlainTextParagraph(txt, self.get_style())
p = FontFallbackParagraph(txt, self.get_style())
row.append(p)
if op.order.status != Order.STATUS_PAID:
tstyledata += [
+6 -6
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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": eventreverse_absolute(request.event, 'plugins:paypal:return', kwargs=kwargs),
"cancel_url": eventreverse_absolute(request.event, 'plugins:paypal:abort', kwargs=kwargs),
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return', kwargs=kwargs),
"cancel_url": build_absolute_uri(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 (
eventreverse_absolute(request.event, 'plugins:paypal:redirect') + '?url=' +
build_absolute_uri(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": eventreverse_absolute(request.event, 'plugins:paypal:return'),
"cancel_url": eventreverse_absolute(request.event, 'plugins:paypal:abort'),
"return_url": build_absolute_uri(request.event, 'plugins:paypal:return'),
"cancel_url": build_absolute_uri(request.event, 'plugins:paypal:abort'),
},
"transactions": [
{
+6 -6
View File
@@ -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 mainreverse_absolute
from pretix.multidomain.urlreverse import eventreverse, eventreverse_absolute
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
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.'),
mainreverse_absolute('plugins:paypal2:webhook')
build_global_uri('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": mainreverse_absolute('plugins:paypal2:isu.return', kwargs={
"return_url": build_global_uri('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': eventreverse_absolute(request.event, 'plugins:paypal2:return', kwargs=kwargs),
'cancel_url': eventreverse_absolute(request.event, 'plugins:paypal2:abort', kwargs=kwargs),
'return_url': build_absolute_uri(request.event, 'plugins:paypal2:return', kwargs=kwargs),
'cancel_url': build_absolute_uri(request.event, 'plugins:paypal2:abort', kwargs=kwargs),
},
})
response = self.client.execute(paymentreq)
+87 -87
View File
@@ -36,7 +36,7 @@ from reportlab.lib import colors, pagesizes
from reportlab.lib.enums import TA_CENTER, TA_RIGHT
from reportlab.lib.units import mm
from reportlab.platypus import (
KeepTogether, PageTemplate, Spacer, Table, TableStyle,
KeepTogether, PageTemplate, Paragraph, Spacer, Table, TableStyle,
)
from pretix.base.exporter import BaseExporter
@@ -49,7 +49,7 @@ from pretix.base.timeframes import (
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
from pretix.control.forms.filter import get_all_payment_providers
from pretix.helpers.reportlab import PlainTextParagraph
from pretix.helpers.reportlab import FontFallbackParagraph
from pretix.plugins.reports.exporters import ReportlabExportMixin
@@ -270,18 +270,18 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
def _transaction_group_header_label(self):
return _("Event") + " / " + _("Product")
def _transaction_group(self, form_data, r):
def _transaction_group_label(self, form_data, r):
if not self.is_multievent and not form_data.get("split_subevents"):
return None, None
return 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"]), f"event-{r['order__event__slug']}"
return "{} [{}]".format(r["order__event__name"], r["order__event__slug"])
def _transaction_row_label(self, r):
if r["item_id"]:
@@ -311,13 +311,13 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tdata = [
[
PlainTextParagraph(self._transaction_group_header_label(), tstyle_bold),
PlainTextParagraph(_("Price"), tstyle_bold_right),
PlainTextParagraph(_("Tax rate"), tstyle_bold_right),
PlainTextParagraph("#", tstyle_bold_right),
PlainTextParagraph(_("Net total"), tstyle_bold_right),
PlainTextParagraph(_("Tax total"), tstyle_bold_right),
PlainTextParagraph(_("Gross total"), tstyle_bold_right),
FontFallbackParagraph(self._transaction_group_header_label(), tstyle_bold),
FontFallbackParagraph(_("Price"), tstyle_bold_right),
FontFallbackParagraph(_("Tax rate"), tstyle_bold_right),
FontFallbackParagraph("#", tstyle_bold_right),
FontFallbackParagraph(_("Net total"), tstyle_bold_right),
FontFallbackParagraph(_("Tax total"), tstyle_bold_right),
FontFallbackParagraph(_("Gross total"), tstyle_bold_right),
]
]
@@ -343,17 +343,17 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
last_group = None
last_group_head_idx = 0
for r in qs:
group_label, group_id = self._transaction_group(form_data, r)
e = self._transaction_group_label(form_data, r)
if group_id != last_group:
if last_group_head_idx > 0 and group_id is not None:
tdata[last_group_head_idx][4] = PlainTextParagraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
tdata[last_group_head_idx][5] = PlainTextParagraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
tdata[last_group_head_idx][6] = PlainTextParagraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
if e != last_group:
if last_group_head_idx > 0 and e 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(
[
PlainTextParagraph(
group_label,
FontFallbackParagraph(
e,
tstyle_bold,
),
"",
@@ -367,7 +367,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tstyledata.append(
("SPAN", (0, len(tdata) - 1), (3, len(tdata) - 1)),
)
last_group = group_id
last_group = e
last_group_head_idx = len(tdata) - 1
sum_price_by_group = Decimal("0.00")
sum_tax_by_group = Decimal("0.00")
@@ -375,20 +375,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
text = self._transaction_row_label(r)
tdata.append(
[
PlainTextParagraph(text, tstyle),
PlainTextParagraph(
FontFallbackParagraph(text, tstyle),
Paragraph(
money_filter(r["price"], currency)
if "price" in r and r["price"] is not None
else "",
tstyle_right,
),
PlainTextParagraph(localize(r["tax_rate"].normalize()) + " %", tstyle_right),
PlainTextParagraph(str(r["sum_cont"]), tstyle_right),
PlainTextParagraph(
Paragraph(localize(r["tax_rate"].normalize()) + " %", tstyle_right),
Paragraph(str(r["sum_cont"]), tstyle_right),
Paragraph(
money_filter(r["sum_price"] - r["sum_tax"], currency), tstyle_right
),
PlainTextParagraph(money_filter(r["sum_tax"], currency), tstyle_right),
PlainTextParagraph(money_filter(r["sum_price"], currency), tstyle_right),
Paragraph(money_filter(r["sum_tax"], currency), tstyle_right),
Paragraph(money_filter(r["sum_price"], currency), tstyle_right),
]
)
sum_cnt_by_tax_rate[r["tax_rate"]] += r["sum_cont"]
@@ -398,19 +398,19 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
sum_tax_by_group += r["sum_tax"]
if last_group_head_idx > 0 and last_group is not None:
tdata[last_group_head_idx][4] = PlainTextParagraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
tdata[last_group_head_idx][5] = PlainTextParagraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
tdata[last_group_head_idx][6] = PlainTextParagraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
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),
if len(sum_tax_by_tax_rate) > 1:
for tax_rate in sorted(sum_tax_by_tax_rate.keys(), reverse=True):
tdata.append(
[
PlainTextParagraph(_("Sum"), tstyle),
PlainTextParagraph("", tstyle_right),
PlainTextParagraph(localize(tax_rate.normalize()) + " %", tstyle_right),
PlainTextParagraph("", tstyle_right),
PlainTextParagraph(
FontFallbackParagraph(_("Sum"), tstyle),
Paragraph("", tstyle_right),
Paragraph(localize(tax_rate.normalize()) + " %", tstyle_right),
Paragraph("", tstyle_right),
Paragraph(
money_filter(
sum_price_by_tax_rate[tax_rate]
- sum_tax_by_tax_rate[tax_rate],
@@ -418,10 +418,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
),
tstyle_right,
),
PlainTextParagraph(
Paragraph(
money_filter(sum_tax_by_tax_rate[tax_rate], currency), tstyle_right
),
PlainTextParagraph(
Paragraph(
money_filter(sum_price_by_tax_rate[tax_rate], currency),
tstyle_right,
),
@@ -439,11 +439,11 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tdata.append(
[
PlainTextParagraph(_("Sum"), tstyle_bold),
PlainTextParagraph("", tstyle_right),
PlainTextParagraph("", tstyle_right),
PlainTextParagraph("", tstyle_bold_right),
PlainTextParagraph(
FontFallbackParagraph(_("Sum"), tstyle_bold),
Paragraph("", tstyle_right),
Paragraph("", tstyle_right),
Paragraph("", tstyle_bold_right),
Paragraph(
money_filter(
sum(sum_price_by_tax_rate.values())
- sum(sum_tax_by_tax_rate.values()),
@@ -451,11 +451,11 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
),
tstyle_bold_right,
),
PlainTextParagraph(
Paragraph(
money_filter(sum(sum_tax_by_tax_rate.values()), currency),
tstyle_bold_right,
),
PlainTextParagraph(
Paragraph(
money_filter(sum(sum_price_by_tax_rate.values()), currency),
tstyle_bold_right,
),
@@ -493,10 +493,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tdata = [
[
PlainTextParagraph(_("Payment method"), tstyle_bold),
PlainTextParagraph(_("Payments"), tstyle_bold_right),
PlainTextParagraph(_("Refunds"), tstyle_bold_right),
PlainTextParagraph(_("Total"), tstyle_bold_right),
FontFallbackParagraph(_("Payment method"), tstyle_bold),
FontFallbackParagraph(_("Payments"), tstyle_bold_right),
FontFallbackParagraph(_("Refunds"), tstyle_bold_right),
FontFallbackParagraph(_("Total"), tstyle_bold_right),
]
]
@@ -537,20 +537,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
for p in providers:
tdata.append(
[
PlainTextParagraph(provider_names.get(p, p), tstyle),
PlainTextParagraph(
Paragraph(provider_names.get(p, p), tstyle),
FontFallbackParagraph(
money_filter(payments_by_provider[p], currency)
if p in payments_by_provider
else "",
tstyle_right,
),
PlainTextParagraph(
Paragraph(
money_filter(refunds_by_provider[p], currency)
if p in refunds_by_provider
else "",
tstyle_right,
),
PlainTextParagraph(
Paragraph(
money_filter(
payments_by_provider.get(p, Decimal("0.00"))
- refunds_by_provider.get(p, Decimal("0.00")),
@@ -563,20 +563,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tdata.append(
[
PlainTextParagraph(_("Sum"), tstyle_bold),
PlainTextParagraph(
FontFallbackParagraph(_("Sum"), tstyle_bold),
Paragraph(
money_filter(
sum(payments_by_provider.values(), Decimal("0.00")), currency
),
tstyle_bold_right,
),
PlainTextParagraph(
Paragraph(
money_filter(
sum(refunds_by_provider.values(), Decimal("0.00")), currency
),
tstyle_bold_right,
),
PlainTextParagraph(
Paragraph(
money_filter(
sum(payments_by_provider.values(), Decimal("0.00"))
- sum(refunds_by_provider.values(), Decimal("0.00")),
@@ -641,7 +641,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
open_before = tx_before - p_before + r_before
tdata.append(
[
PlainTextParagraph(
FontFallbackParagraph(
_("Pending payments at {datetime}").format(
datetime=date_format(
(df_start - datetime.timedelta.resolution).astimezone(
@@ -653,7 +653,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
tstyle,
),
"",
PlainTextParagraph(money_filter(open_before, currency), tstyle_right),
Paragraph(money_filter(open_before, currency), tstyle_right),
]
)
else:
@@ -670,30 +670,30 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
] or Decimal("0.00")
tdata.append(
[
PlainTextParagraph(_("Orders"), tstyle),
PlainTextParagraph("+", tstyle_center),
PlainTextParagraph(money_filter(tx_during, currency), tstyle_right),
FontFallbackParagraph(_("Orders"), tstyle),
Paragraph("+", tstyle_center),
Paragraph(money_filter(tx_during, currency), tstyle_right),
]
)
tdata.append(
[
PlainTextParagraph(_("Payments"), tstyle),
PlainTextParagraph("-", tstyle_center),
PlainTextParagraph(money_filter(p_during, currency), tstyle_right),
FontFallbackParagraph(_("Payments"), tstyle),
Paragraph("-", tstyle_center),
Paragraph(money_filter(p_during, currency), tstyle_right),
]
)
tdata.append(
[
PlainTextParagraph(_("Refunds"), tstyle),
PlainTextParagraph("+", tstyle_center),
PlainTextParagraph(money_filter(r_during, currency), tstyle_right),
FontFallbackParagraph(_("Refunds"), tstyle),
Paragraph("+", tstyle_center),
Paragraph(money_filter(r_during, currency), tstyle_right),
]
)
open_after = open_before + tx_during - p_during + r_during
tdata.append(
[
PlainTextParagraph(
Paragraph(
_("Pending payments at {datetime}").format(
datetime=date_format(
((df_end or now()) - datetime.timedelta.resolution).astimezone(
@@ -704,8 +704,8 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
),
tstyle_bold,
),
PlainTextParagraph("=", tstyle_center),
PlainTextParagraph(money_filter(open_after, currency), tstyle_bold_right),
Paragraph("=", tstyle_center),
Paragraph(money_filter(open_after, currency), tstyle_bold_right),
]
)
tstyledata += [
@@ -752,7 +752,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
)
tdata.append(
[
PlainTextParagraph(
Paragraph(
_("Total gift card value at {datetime}").format(
datetime=date_format(
(df_start - datetime.timedelta.resolution).astimezone(
@@ -763,7 +763,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
),
tstyle,
),
PlainTextParagraph(money_filter(tx_before, currency), tstyle_right),
Paragraph(money_filter(tx_before, currency), tstyle_right),
]
)
else:
@@ -774,8 +774,8 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
] or Decimal("0.00")
tdata.append(
[
PlainTextParagraph(_("Gift card transactions (credit)"), tstyle),
PlainTextParagraph(money_filter(tx_during_pos, currency), tstyle_right),
FontFallbackParagraph(_("Gift card transactions (credit)"), tstyle),
Paragraph(money_filter(tx_during_pos, currency), tstyle_right),
]
)
@@ -784,15 +784,15 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
] or Decimal("0.00")
tdata.append(
[
PlainTextParagraph(_("Gift card transactions (debit)"), tstyle),
PlainTextParagraph(money_filter(tx_during_neg, currency), tstyle_right),
FontFallbackParagraph(_("Gift card transactions (debit)"), tstyle),
Paragraph(money_filter(tx_during_neg, currency), tstyle_right),
]
)
open_after = tx_before + tx_during_pos + tx_during_neg
tdata.append(
[
PlainTextParagraph(
Paragraph(
_("Total gift card value at {datetime}").format(
datetime=date_format(
((df_end or now()) - datetime.timedelta.resolution).astimezone(
@@ -803,7 +803,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
),
tstyle_bold,
),
PlainTextParagraph(money_filter(open_after, currency), tstyle_bold_right),
Paragraph(money_filter(open_after, currency), tstyle_bold_right),
]
)
tstyledata += [
@@ -854,10 +854,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
style_small.leading = 10
story = [
PlainTextParagraph(self.verbose_name, style_h1),
FontFallbackParagraph(self.verbose_name, style_h1),
Spacer(0, 3 * mm),
PlainTextParagraph(
"\n".join(escape(f) for f in self.describe_filters(form_data)),
FontFallbackParagraph(
"<br />".join(escape(f) for f in self.describe_filters(form_data)),
style_small,
),
]
@@ -870,7 +870,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
if s:
story += [
Spacer(0, 3 * mm),
PlainTextParagraph(_("Orders") + c_head, style_h2),
FontFallbackParagraph(_("Orders") + c_head, style_h2),
Spacer(0, 3 * mm),
*s
]
@@ -881,7 +881,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
if s:
story += [
Spacer(0, 8 * mm),
PlainTextParagraph(_("Payments") + c_head, style_h2),
FontFallbackParagraph(_("Payments") + c_head, style_h2),
Spacer(0, 3 * mm),
*s
]
@@ -894,7 +894,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
Spacer(0, 8 * mm),
KeepTogether(
[
PlainTextParagraph(_("Open items") + c_head, style_h2),
FontFallbackParagraph(_("Open items") + c_head, style_h2),
Spacer(0, 3 * mm),
*s
]
@@ -912,7 +912,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
Spacer(0, 8 * mm),
KeepTogether(
[
PlainTextParagraph(_("Gift cards") + c_head, style_h2),
FontFallbackParagraph(_("Gift cards") + c_head, style_h2),
Spacer(0, 3 * mm),
*s,
]
+13 -13
View File
@@ -70,7 +70,7 @@ from pretix.base.timeframes import (
)
from pretix.control.forms.filter import OverviewFilterForm
from pretix.helpers.reportlab import (
PlainTextParagraph, register_ttf_font_if_new,
FontFallbackParagraph, register_ttf_font_if_new,
)
from pretix.presale.style import get_fonts
@@ -282,7 +282,7 @@ class OverviewReport(Report):
headlinestyle.fontSize = 15
headlinestyle.fontName = 'OpenSansBd'
story = [
PlainTextParagraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle),
FontFallbackParagraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle),
Spacer(1, 5 * mm)
]
return story
@@ -292,7 +292,7 @@ class OverviewReport(Report):
if form_data.get('date_axis') and form_data.get('date_range'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
story += [
PlainTextParagraph(_('{axis} between {start} and {end}').format(
FontFallbackParagraph(_('{axis} between {start} and {end}').format(
axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')],
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '',
end=date_format(d_end, 'SHORT_DATE_FORMAT') if d_end else '',
@@ -305,13 +305,13 @@ class OverviewReport(Report):
subevent = self.event.subevents.get(pk=self.form_data.get('subevent'))
except SubEvent.DoesNotExist:
subevent = self.form_data.get('subevent')
story.append(PlainTextParagraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style()))
story.append(FontFallbackParagraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style()))
story.append(Spacer(1, 5 * mm))
if form_data.get('subevent_date_range'):
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['subevent_date_range'], self.timezone)
story += [
PlainTextParagraph(_('{axis} between {start} and {end}').format(
FontFallbackParagraph(_('{axis} between {start} and {end}').format(
axis=_('Event date'),
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '',
end=date_format(d_end - timedelta(hours=1), 'SHORT_DATE_FORMAT') if d_end else '',
@@ -384,13 +384,13 @@ class OverviewReport(Report):
tdata = [
[
_('Product'),
PlainTextParagraph(_('Canceled'), tstyle_th),
FontFallbackParagraph(_('Canceled'), tstyle_th),
'',
PlainTextParagraph(_('Expired'), tstyle_th),
FontFallbackParagraph(_('Expired'), tstyle_th),
'',
PlainTextParagraph(_('Approval pending'), tstyle_th),
FontFallbackParagraph(_('Approval pending'), tstyle_th),
'',
PlainTextParagraph(_('Purchased'), tstyle_th),
FontFallbackParagraph(_('Purchased'), tstyle_th),
'', '', '', '', ''
],
[
@@ -421,14 +421,14 @@ class OverviewReport(Report):
for tup in items_by_category:
if tup[0]:
tdata.append([
PlainTextParagraph(str(tup[0]), tstyle_bold)
FontFallbackParagraph(str(tup[0]), tstyle_bold)
])
for l, s in states:
tdata[-1].append(str(tup[0].num[l][0]))
tdata[-1].append(floatformat(tup[0].num[l][2 if net else 1], places))
for item in tup[1]:
tdata.append([
PlainTextParagraph(str(item), tstyle)
FontFallbackParagraph(str(item), tstyle)
])
for l, s in states:
tdata[-1].append(str(item.num[l][0]))
@@ -436,7 +436,7 @@ class OverviewReport(Report):
if item.has_variations:
for var in item.all_variations:
tdata.append([
PlainTextParagraph(" " + str(var), tstyle)
FontFallbackParagraph(" " + str(var), tstyle)
])
for l, s in states:
tdata[-1].append(str(var.num[l][0]))
@@ -568,7 +568,7 @@ class OrderTaxListReportPDF(Report):
tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0)))
story = [
PlainTextParagraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle),
FontFallbackParagraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle),
Spacer(1, 5 * mm)
]
tdata = [
+9 -9
View File
@@ -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 mainreverse_absolute
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
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(mainreverse_absolute('plugins:stripe:oauth.return')),
urllib.parse.quote(build_global_uri('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.'),
mainreverse_absolute('plugins:stripe:webhook')
build_global_uri('plugins:stripe:webhook')
)
@property
@@ -746,7 +746,7 @@ class StripeMethod(BasePaymentProvider):
def redirect(self, request, url):
if request.session.get('iframe_session', False):
return (
eventreverse_absolute(request.event, 'plugins:stripe:redirect') +
build_absolute_uri(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=eventreverse_absolute(self.event, 'plugins:stripe:sca.return', kwargs={
return_url=build_absolute_uri(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 = eventreverse_absolute(self.event, 'plugins:stripe:sca', kwargs={
url = build_absolute_uri(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 eventreverse_absolute(self.event, 'plugins:stripe:redirect') + '?data=' + signing.dumps({
return build_absolute_uri(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=eventreverse_absolute(self.event, 'plugins:stripe:sca.return', kwargs={
return_url=build_absolute_uri(self.event, 'plugins:stripe:sca.return', kwargs={
'order': payment.order.code,
'payment': payment.pk,
'hash': payment.order.tagged_secret('plugins:stripe'),
+2 -2
View File
@@ -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 eventreverse, eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
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': eventreverse_absolute(request.event, 'plugins:stripe:redirect') + '?' + urllib.parse.urlencode(params),
'url': build_absolute_uri(request.event, 'plugins:stripe:redirect') + '?' + urllib.parse.urlencode(params),
})
r._csp_ignore = True
return r
+1 -2
View File
@@ -51,7 +51,6 @@ from django.http import HttpResponseNotAllowed, JsonResponse
from django.shortcuts import redirect
from django.utils import translation
from django.utils.functional import cached_property
from django.utils.html import conditional_escape
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
)
@@ -1635,7 +1634,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
meta_info = {
'contact_form_data': self.cart_session.get('contact_form_data', {}),
'confirm_messages': [
conditional_escape(str(m)) for m in self.confirm_messages.values()
str(m) for m in self.confirm_messages.values()
]
}
api_meta = {}
+2 -2
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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(
eventreverse_absolute(False, 'presale:organizer.customer.resetpw', kwargs={
build_absolute_uri(False, 'presale:organizer.customer.resetpw', kwargs={
'organizer': request.organizer.slug,
}),
_('Forgot your password?')
+6 -6
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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 = eventreverse_absolute(event, 'presale:event.index')
url = build_absolute_uri(event, 'presale:event.index')
else:
url = eventreverse_absolute(event, 'presale:event.index', {
url = build_absolute_uri(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 = eventreverse_absolute(event, 'presale:event.index')
url = build_absolute_uri(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 = eventreverse_absolute(event, 'presale:event.index', {
url = build_absolute_uri(event, 'presale:event.index', {
'subevent': p.subevent.pk
})
else:
url = eventreverse_absolute(event, 'presale:event.index')
url = build_absolute_uri(event, 'presale:event.index')
if event.settings.mail_attach_ical_description:
ctx = get_email_context(event=event, event_or_subevent=ev)
+1 -1
View File
@@ -144,7 +144,7 @@ checkout_confirm_messages = EventPluginSignal()
This signal is sent out to retrieve short messages that need to be acknowledged by the user before the
order can be completed. This is typically used for something like "accept the terms and conditions".
Receivers are expected to return a dictionary where the keys are globally unique identifiers for the
message and the values can be a SafeString containing arbitrary HTML, or a string that will be HTML-escaped.
message and the values can be arbitrary HTML.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
"""
@@ -176,7 +176,7 @@
<div class="checkbox">
<label for="input_confirm_{{ key }}">
<input type="checkbox" class="checkbox" value="yes" name="confirm_{{ key }}" id="input_confirm_{{ key }}" required>
{{ desc }}
{{ desc|safe }}
</label>
</div>
{% endfor %}
@@ -4,7 +4,6 @@
{% load eventsignal %}
{% load money %}
{% load eventurl %}
{% load wrap_in %}
{% block title %}{% trans "Registration details" %}{% endblock %}
{% block content %}
<h2 class="h1">
@@ -49,7 +48,7 @@
</div>
<div class="panel-body">
<p>
{% blocktrans trimmed with email=order.email|wrap_in:"strong" %}
{% blocktrans trimmed with email="<strong>"|add:order.email|add:"</strong>"|safe %}
This order is managed for you by {{ email }}. Please contact them for any questions regarding
payment, cancellation or changes to this order.
{% endblocktrans %}
@@ -22,5 +22,4 @@
<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 %}
+2 -2
View File
@@ -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 (
eventreverse_absolute, get_event_domain, get_organizer_domain,
build_absolute_uri, 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(eventreverse_absolute(request.event, "presale:event.index") + path)
r = redirect_to_url(build_absolute_uri(request.event, "presale:event.index") + path)
r['Access-Control-Allow-Origin'] = '*'
return r
elif domain and domain != request.host:
+6 -8
View File
@@ -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 eventreverse, eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.presale.forms.customer import (
AuthenticationForm, ChangeInfoForm, ChangePasswordForm, RegistrationForm,
ResetPasswordForm, SetPasswordForm, TokenGenerator,
@@ -316,10 +316,8 @@ class ResetPasswordView(FormView):
customer.log_action('pretix.customer.password.resetrequested', {})
ctx = customer.get_email_context()
token = TokenGenerator().make_token(customer)
ctx['url'] = eventreverse_absolute(
self.request.organizer,
'presale:organizer.customer.recoverpw'
) + '?id=' + customer.identifier + '&token=' + token
ctx['url'] = build_absolute_uri(self.request.organizer,
'presale:organizer.customer.recoverpw') + '?id=' + customer.identifier + '&token=' + token
mail(
customer.email,
self.request.organizer.settings.mail_subject_customer_reset,
@@ -576,7 +574,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'] = eventreverse_absolute(
ctx['url'] = build_absolute_uri(
self.request.organizer,
'presale:organizer.customer.change.confirm'
) + '?token=' + dumps({
@@ -705,7 +703,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 = eventreverse_absolute(self.request.organizer, 'presale:organizer.customer.login.return', kwargs={
redirect_uri = build_absolute_uri(self.request.organizer, 'presale:organizer.customer.login.return', kwargs={
'provider': self.provider.pk
})
@@ -779,7 +777,7 @@ class SSOLoginReturnView(RedirectBackMixin, View):
),
popup_origin,
)
redirect_uri = eventreverse_absolute(
redirect_uri = build_absolute_uri(
self.request.organizer, 'presale:organizer.customer.login.return',
kwargs={
'provider': self.provider.pk
+6 -6
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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': eventreverse_absolute(request.organizer, 'presale:organizer.index').rstrip('/'),
'authorization_endpoint': eventreverse_absolute(
'issuer': build_absolute_uri(request.organizer, 'presale:organizer.index').rstrip('/'),
'authorization_endpoint': build_absolute_uri(
request.organizer, 'presale:organizer.oauth2.v1.authorize'
),
'token_endpoint': eventreverse_absolute(
'token_endpoint': build_absolute_uri(
request.organizer, 'presale:organizer.oauth2.v1.token'
),
'userinfo_endpoint': eventreverse_absolute(
'userinfo_endpoint': build_absolute_uri(
request.organizer, 'presale:organizer.oauth2.v1.userinfo'
),
'jwks_uri': eventreverse_absolute(
'jwks_uri': build_absolute_uri(
request.organizer, 'presale:organizer.oauth2.v1.jwks'
),
'scopes_supported': [k for k, v in CustomerSSOClient.SCOPE_CHOICES],
+2 -2
View File
@@ -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 eventreverse, eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
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'] = eventreverse_absolute(
ctx['url'] = build_absolute_uri(
self.request.event, 'presale:event.order', kwargs={
'order': self.order.code,
'secret': self.order.secret
+2 -2
View File
@@ -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 eventreverse, eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
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(eventreverse_absolute(self.request.organizer, "presale:organizer.index"))
return redirect_to_url(build_absolute_uri(self.request.organizer, "presale:organizer.index"))
class AccessibilityView(OrganizerViewMixin, EventListMixin, TemplateView):
+6 -9
View File
@@ -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 eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
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,9 +128,6 @@ 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
@@ -303,7 +300,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(eventreverse_absolute(event, 'presale:event.index'), thumb)
return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb)
class WidgetAPIProductList(EventListMixin, View):
@@ -547,7 +544,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': eventreverse_absolute(event, 'presale:event.index'),
'event_url': build_absolute_uri(event, 'presale:event.index'),
'subevent': ev.pk if isinstance(ev, SubEvent) else None,
})
return events
@@ -748,7 +745,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': eventreverse_absolute(ev.event, 'presale:event.index'),
'event_url': build_absolute_uri(ev.event, 'presale:event.index'),
'subevent': ev.pk,
} for ev in evs
]
@@ -771,7 +768,7 @@ class WidgetAPIProductList(EventListMixin, View):
'location': str(event.location),
'date_range': dr,
'availability': avail,
'event_url': eventreverse_absolute(event, 'presale:event.index'),
'event_url': build_absolute_uri(event, 'presale:event.index'),
})
cache.set(cache_key, data, 30)
@@ -796,7 +793,7 @@ class WidgetAPIProductList(EventListMixin, View):
return self.response(cached_data)
data = {
'target_url': eventreverse_absolute(request.event, 'presale:event.index'),
'target_url': build_absolute_uri(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,

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