Compare commits

..

10 Commits

132 changed files with 1625 additions and 3544 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 -1
View File
@@ -192,7 +192,7 @@ Cart position endpoints
* ``attendee_email`` (optional)
* ``subevent`` (optional)
* ``expires`` (optional)
* ``includes_tax`` (optional, **DEPRECATED**, do not use, will be removed)
* ``includes_tax`` (optional, **deprecated**, do not use, will be removed)
* ``sales_channel`` (optional)
* ``voucher`` (optional, expect a voucher code)
* ``addons`` (optional, expect a list of nested objects of cart positions)
+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.
+1 -1
View File
@@ -1069,7 +1069,7 @@ Creating orders
* ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
* ``use_reusable_medium`` (optional, causes the new ticket to 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)
* ``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``
+9 -30
View File
@@ -21,16 +21,12 @@ id integer Internal ID of
type string Type of medium, e.g. ``"barcode"``, ``"nfc_uid"`` or ``"nfc_mf0aes"``.
organizer string Organizer slug of the organizer who "owns" this medium.
identifier string Unique identifier of the medium. The format depends on the ``type``.
claim_token string Secret token to claim ownership of the medium (or ``null``)
label string Label to identify the medium, usually something human readable (or ``null``)
active boolean Whether this medium may be used.
created datetime Date of creation
updated datetime Date of last modification
expires datetime Expiry date (or ``null``)
customer string Identifier of a customer account this medium belongs to.
linked_orderpositions list of integers Internal IDs of tickets this medium is linked to.
linked_orderposition integer **DEPRECATED.** ID of the ticket the medium is linked to, if it is linked to
only one ticket. ``null``, if the medium is linked to none or multiple tickets.
linked_orderposition integer Internal ID of a ticket this medium is linked to.
linked_giftcard integer Internal ID of a gift card this medium is linked to.
info object Additional data, content depends on the ``type``. Consider
this internal to the system and don't use it for your own data.
@@ -43,14 +39,6 @@ Existing media types are:
- ``nfc_uid``
- ``nfc_mf0aes``
.. versionchanged:: 2026.5
The ``claim_token``, ``label``, ``linked_orderpositions`` attributes have been added, the ``linked_orderposition`` attribute has been
deprecated. Note: To maintain backwards compatibility ``linked_orderposition`` contains the internal ID of the linked order position
if the medium has exactly one order position in ``linked_orderpositions``.
Endpoints
---------
@@ -89,7 +77,6 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [],
"linked_orderposition": None,
"linked_giftcard": None,
"notes": None,
@@ -105,13 +92,10 @@ Endpoints
:query string customer: Only show media linked to the given customer.
:query string created_since: Only show media created since a given date.
:query string updated_since: Only show media updated since a given date.
:query integer linked_orderpositions: Only show media linked to the given tickets. Note: you can pass multiple ticket IDs by passing
``linked_orderpositions`` multiple times. Any medium matching any linked orderposition will be returned.
:query integer linked_orderposition: Only show media linked to the given ticket.
:query integer linked_giftcard: Only show media linked to the given gift card.
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_giftcard.owner_ticket"``, ``"linked_orderpositions"``,
``"linked_orderposition"`` (**DEPRECATED**), or ``"customer"``, the respective field will be shown
as a nested value instead of just an ID.
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_giftcard.owner_ticket"``, ``"linked_orderposition"``,
or ``"customer"``, the respective field will be shown as a nested value instead of just an ID.
The nested objects are identical to the respective resources, except that order positions
will have an attribute of the format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make
matching easier. The parameter can be given multiple times.
@@ -150,7 +134,6 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [],
"linked_orderposition": None,
"linked_giftcard": None,
"notes": None,
@@ -208,7 +191,6 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [],
"linked_orderposition": None,
"linked_giftcard": None,
"notes": None,
@@ -216,9 +198,9 @@ Endpoints
}
:param organizer: The ``slug`` field of the organizer to look up a medium for
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, or ``"customer"``, the respective
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderposition"``, oder ``"customer"``, the respective
field will be shown as a nested value instead of just an ID. The nested objects are identical to
the respective resources, except that the ``linked_orderpositions`` each will have an attribute of the
the respective resources, except that the ``linked_orderposition`` will have an attribute of the
format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make matching easier. The parameter
can be given multiple times.
:statuscode 201: no error
@@ -245,7 +227,6 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [],
"linked_orderposition": None,
"linked_giftcard": None,
"notes": None,
@@ -270,7 +251,6 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [],
"linked_orderposition": None,
"linked_giftcard": None,
"notes": None,
@@ -278,7 +258,7 @@ Endpoints
}
:param organizer: The ``slug`` field of the organizer to create a medium for
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, or ``"customer"``, the respective
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderposition"``, oder ``"customer"``, the respective
field will be shown as a nested value instead of just an ID. The nested objects are identical to
the respective resources, except that the ``linked_orderposition`` will have an attribute of the
format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make matching easier. The parameter
@@ -307,7 +287,7 @@ Endpoints
Content-Length: 94
{
"linked_orderpositions": [13, 29]
"linked_orderposition": 13
}
**Example response**:
@@ -328,8 +308,7 @@ Endpoints
"active": True,
"expires": None,
"customer": None,
"linked_orderpositions": [13, 29],
"linked_orderposition": None,
"linked_orderposition": 13,
"linked_giftcard": None,
"notes": None,
"info": {}
@@ -337,7 +316,7 @@ Endpoints
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the medium to modify
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, or ``"customer"``, the respective
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderposition"``, oder ``"customer"``, the respective
field will be shown as a nested value instead of just an ID. The nested objects are identical to
the respective resources, except that the ``linked_orderposition`` will have an attribute of the
format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make matching easier. The parameter
+2 -2
View File
@@ -64,8 +64,8 @@ Backend
.. automodule:: pretix.control.signals
:members: nav_event, html_head, html_page_start, quota_detail_html, nav_topbar, nav_global, nav_organizer, nav_event_settings,
order_info, order_approve_info, event_settings_widget, oauth_application_registered,
order_position_buttons, subevent_forms, item_formsets, order_search_filter_q, order_search_forms, subevent_detail_html
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms,
item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:no-index:
+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"
+11 -9
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.60.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
@@ -101,17 +101,17 @@ dependencies = [
"tqdm==4.*",
"ua-parser==1.0.*",
"vobject==0.9.*",
"webauthn==2.8.*",
"webauthn==2.7.*",
"zeep==4.3.*"
]
[project.optional-dependencies]
memcached = ["pylibmc"]
dev = [
"aiohttp==3.14.*",
"aiohttp==3.13.*",
"coverage",
"coveralls",
"fakeredis==2.36.*",
"fakeredis==2.35.*",
"flake8==7.3.*",
"freezegun",
"isort==8.0.*",
@@ -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",
]
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.6.0.dev0"
__version__ = "2026.5.2"
-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',
+12 -54
View File
@@ -66,14 +66,13 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
expand_nested = self.context['request'].query_params.getlist('expand')
if 'linked_giftcard' in expand_nested:
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_giftcards"]:
raise PermissionDenied("No permission to access gift card details.")
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
if 'linked_giftcard.owner_ticket' in expand_nested:
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
else:
self.fields['linked_giftcard'] = serializers.PrimaryKeyRelatedField(
@@ -82,27 +81,17 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
queryset=self.context['organizer'].issued_gift_cards.all()
)
# keep linked_orderposition (singular) for backwards compatibility, will be overwritten in self.validate
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
required=False,
allow_null=True,
queryset=OrderPosition.all.filter(order__event__organizer=self.context['organizer']),
)
if 'linked_orderposition' in expand_nested or 'linked_orderpositions' in expand_nested:
self.fields['linked_orderpositions'] = NestedOrderPositionSerializer(
many=True,
read_only=True
)
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
# Permission Check performed in to_representation
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
else:
self.fields['linked_orderpositions'] = serializers.PrimaryKeyRelatedField(
many=True,
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
required=False,
allow_null=True,
queryset=OrderPosition.all.filter(order__event__organizer=self.context['organizer']),
)
if 'customer' in expand_nested:
if 'customer' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_customers"]:
raise PermissionDenied("No permission to access customer details.")
@@ -117,21 +106,6 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
def validate(self, data):
data = super().validate(data)
if 'linked_orderposition' in data:
linked_orderposition = data['linked_orderposition']
# backwards-compatibility
if 'linked_orderpositions' in data:
raise ValidationError({
'linked_orderposition': 'You cannot use linked_orderposition and linked_orderpositions at the same time.'
})
if self.instance and self.instance.linked_orderpositions.count() > 1:
raise ValidationError({
'linked_orderposition': 'There are more than one linked_orderposition. You need to use linked_orderpositions.'
})
data['linked_orderpositions'] = [linked_orderposition] if linked_orderposition else []
del data['linked_orderposition']
if 'type' in data and 'identifier' in data:
qs = self.context['organizer'].reusable_media.filter(
identifier=data['identifier'], type=data['type']
@@ -147,28 +121,14 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
def to_representation(self, instance):
r = super().to_representation(instance)
request = self.context.get('request')
ops = r.get('linked_orderpositions', [])
# late permission evaluations for checks that depend on the actual linked events
expand_nested = self.context['request'].query_params.getlist('expand')
perm_holder = request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user
if ops and 'linked_orderposition' in expand_nested or 'linked_orderpositions' in expand_nested:
ops_noperm = []
for lop in instance.linked_orderpositions.all():
event = lop.order.event
if 'linked_orderposition' in expand_nested:
if instance.linked_orderposition is not None:
event = instance.linked_orderposition.order.event
if not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
ops_noperm.append(lop.id)
if ops_noperm:
ops = [
{'id': op['id']} if op['id'] in ops_noperm
else op
for op in ops
]
r['linked_orderpositions'] = ops
# add linked_orderposition (singular) for backwards compatibility
if len(ops) < 2:
r['linked_orderposition'] = ops[0] if ops else None
r['linked_orderposition'] = {'id': instance.linked_orderposition.id}
if 'linked_giftcard.owner_ticket' in expand_nested:
gc = instance.linked_giftcard
@@ -188,12 +148,10 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
'updated',
'type',
'identifier',
'claim_token',
'label',
'active',
'expires',
'customer',
'linked_orderpositions',
'linked_orderposition',
'linked_giftcard',
'info',
'notes',
+7 -18
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,
})
@@ -1149,7 +1149,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError(
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
)
return data
@@ -1589,7 +1588,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 != 'answers' and k != '_quotas' and k != 'use_reusable_medium'})
if simulate:
pos.order = order._wrapped
else:
@@ -1704,25 +1703,15 @@ 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)
use_reusable_medium.linked_orderposition = pos
use_reusable_medium.save(update_fields=['linked_orderposition'])
use_reusable_medium.log_action(
'pretix.reusable_medium.linked_orderposition.added',
'pretix.reusable_medium.linked_orderposition.changed',
data={
'by_order': order.code,
'linked_orderposition': pos.pk,
}
)
use_reusable_medium.touch()
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',
+25 -138
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,
@@ -495,7 +491,6 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
)
raw_barcode_for_checkin = None
from_revoked_secret = False
reusable_medium_used = None
if simulate:
common_checkin_args['__fake_arg_to_prevent_this_from_being_saved'] = True
@@ -526,12 +521,11 @@ 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(
Exists(ReusableMedium.linked_orderpositions.through.objects.filter(reusablemedium_id=OuterRef('pk')))
).get(
media = ReusableMedium.objects.select_related('linked_orderposition').active().get(
organizer_id=checkinlists[0].event.organizer_id,
type=source_type,
identifier=raw_barcode,
linked_orderposition__isnull=False,
)
raw_barcode_for_checkin = raw_barcode
except ReusableMedium.DoesNotExist:
@@ -634,9 +628,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_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):
if media.linked_orderposition.order.event_id not in list_by_event:
# Medium exists but connected ticket is for the wrong event
if not simulate:
checkinlists[0].event.log_action('pretix.event.checkin.unknown', data={
@@ -662,91 +654,28 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'checkin_texts': [],
'list': MiniCheckinListSerializer(checkinlists[0]).data,
}, status=404)
op_candidates = []
for op in linked_ops:
if op.order.event_id in list_by_event:
reusable_medium_used = medium
op_candidates.append(op)
if list_by_event[op.order.event_id].addon_match:
op_candidates += list(op.addons.all())
op_candidates = [media.linked_orderposition]
if list_by_event[media.linked_orderposition.order.event_id].addon_match:
op_candidates += list(media.linked_orderposition.addons.all())
# 3. Handle the "multiple options found" case: Except for the unlikely case of a secret being also a valid primary
# key on the same list, we're probably dealing with multiple linked_orderpositions or the ``addon_match`` case
# here and need to figure out which op has the right product. This basically is a valid-for-checkin-test on every op.
# key on the same list, we're probably dealing with the ``addon_match`` case here and need to figure out
# which add-on has the right product.
if len(op_candidates) > 1:
op_candidates_matching_product = [
op for op in op_candidates
if (
(list_by_event[op.order.event_id].addon_match or op.secret == raw_barcode or legacy_url_support) and
(list_by_event[op.order.event_id].all_products or op.item_id in {i.pk for i in list_by_event[op.order.event_id].limit_products.all()})
)
]
if not reusable_medium_used:
# 3a. First, we clean up that we made an imprecise query above. If a scan is made for multiple check-in lists,
# we have queried ``addon_to__secret=raw_barcode``, even if some of the lists in question do not allow addon
# matching. So we accept all candidates that match one of these cases:
# - Exactly the ticket secret we scanned (because that's always a possible result)
# - Exactly the ticket pk we scanned (on legacy endpoints)
# - An add-on on a list that allows add-on matching
# This is not necessary when a reusable media was used, since in that case we already obeyed list.addon_match
# correctly above.
op_candidates_filtered = [
op for op in op_candidates
if (
op.secret == raw_barcode or
list_by_event[op.order.event_id].addon_match or
(str(op.pk) == raw_barcode and legacy_url_support and not untrusted_input)
)
]
else:
op_candidates_filtered = op_candidates
if len(op_candidates_filtered) > 1:
# 3b. If we still have multiple candidates, we filter by product based on the check-in list configuration.
# This is relevant for the addon_match scenario where the scanned ticket has multiple add-ons, but only
# one is contained in the check-in list used to scan. It makes sense to filter this first, since it is a
# "static" check, i.e. scanning the same QR code on the same check-in list will always do the same, no matter
# when I scan it, and it is "intentional" filtering in the sense that the admin configured this behaviour
# into the check-in list.
op_candidates_filtered = [
op for op in op_candidates_filtered
if list_by_event[op.order.event_id].all_products or op.item_id in {i.pk for i in list_by_event[op.order.event_id].limit_products.all()}
]
if len(op_candidates_filtered) > 1:
# 3c. If we still have multiple candidates, we filter by validity date. This was introduced for the case where
# a reusable media refers to two tickets, one currently valid and one expired or in the future. Howeer,
# it could in theory also happen with two add-ons being on the same check-in list but without overlapping
# validity. It makes sense to filter this "after" the previous checks since it is not "intentional" filtering
# configured by the admin but "accidental" filtering that depends on the time of execution.
op_candidates_filtered = [
op for op in op_candidates_filtered
if (
(not op.valid_from or op.valid_from <= datetime) and
(not op.valid_until or op.valid_until > datetime)
)
]
if len(op_candidates_filtered) == 0:
# None of the ops is valid today or has the correct product, too bad! We could just error out here, but
if len(op_candidates_matching_product) == 0:
# None of the found add-ons has the correct product, too bad! We could just error out here, but
# instead we just continue with *any* product and have it rejected by the check in perform_checkin.
# To improve the error message, we select the op that will "work next" or - if none matches - "worked last".
op_candidate = None
for op in op_candidates:
if (
op.valid_from and op.valid_from > datetime and
(not op_candidate or op.valid_from < op_candidate.valid_from)
):
op_candidate = op
if not op_candidate:
# no candidate in the future, get closest in the past
for op in op_candidates:
if (
op.valid_until and op.valid_until < datetime and
(not op_candidate or op.valid_until > op_candidate.valid_until)
):
op_candidate = op
if not op_candidate:
op_candidate = op_candidates[0]
op_candidates = [op_candidate]
elif len(op_candidates_filtered) > 1:
# This has the advantage of a better error message.
op_candidates = [op_candidates[0]]
elif len(op_candidates_matching_product) > 1:
# It's still ambiguous, we'll error out.
# We choose the first match (regardless of product) for the logging since it's most likely to be the
# base product according to our order_by above.
@@ -780,7 +709,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400)
else:
op_candidates = op_candidates_filtered
op_candidates = op_candidates_matching_product
op = op_candidates[0]
common_checkin_args['list'] = list_by_event[op.order.event_id]
@@ -792,10 +721,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 +734,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 +752,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 +764,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 +951,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'),
)
+11 -36
View File
@@ -53,12 +53,10 @@ with scopes_disabled():
customer = django_filters.CharFilter(field_name='customer__identifier')
updated_since = django_filters.IsoDateTimeFilter(field_name='updated', lookup_expr='gte')
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
# backwards-compatible
linked_orderposition = django_filters.NumberFilter(field_name='linked_orderpositions__id')
class Meta:
model = ReusableMedium
fields = ['identifier', 'type', 'active', 'customer', 'linked_orderpositions', 'linked_giftcard']
fields = ['identifier', 'type', 'active', 'customer', 'linked_orderposition', 'linked_giftcard']
class ReusableMediaViewSet(viewsets.ModelViewSet):
@@ -77,7 +75,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
).order_by().values('card').annotate(s=Sum('value')).values('s')
return self.request.organizer.reusable_media.prefetch_related(
Prefetch(
'linked_orderpositions',
'linked_orderposition',
queryset=OrderPosition.objects.select_related(
'order', 'order__event', 'order__event__organizer', 'seat',
).prefetch_related(
@@ -119,38 +117,14 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
@transaction.atomic()
def perform_update(self, serializer):
rm = ReusableMedium.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
prev_linked_ops_pks = list(rm.linked_orderpositions.values_list("pk", flat=True))
ReusableMedium.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
inst = serializer.save(identifier=serializer.instance.identifier, type=serializer.instance.type)
linked_ops_pks = inst.linked_orderpositions.values_list("pk", flat=True)
for op_pk in prev_linked_ops_pks:
if op_pk not in linked_ops_pks:
inst.log_action(
'pretix.reusable_medium.linked_orderposition.removed',
user=self.request.user,
auth=self.request.auth,
data={
'linked_orderposition': op_pk,
}
)
for op_pk in linked_ops_pks:
if op_pk not in prev_linked_ops_pks:
inst.log_action(
'pretix.reusable_medium.linked_orderposition.added',
user=self.request.user,
auth=self.request.auth,
data={
'linked_orderposition': op_pk,
}
)
data = {k: v for k, v in self.request.data.items() if k not in ('linked_orderposition', 'linked_orderpositions')}
if data:
inst.log_action(
'pretix.reusable_medium.changed',
user=self.request.user,
auth=self.request.auth,
data=data,
)
inst.log_action(
'pretix.reusable_medium.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data,
)
return inst
def perform_destroy(self, instance):
@@ -183,6 +157,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
type=s.validated_data["type"],
identifier=s.validated_data["identifier"],
)
m.linked_orderposition = None # not relevant for cross-organizer
m.customer = None # not relevant for cross-organizer
s = self.get_serializer(m)
return Response({"result": s.data})
@@ -196,7 +171,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
@@ -194,7 +194,7 @@ with scopes_disabled():
)
).values('id')
matching_media = ReusableMedium.objects.filter(identifier=u).values_list('linked_orderpositions__order_id', flat=True)
matching_media = ReusableMedium.objects.filter(identifier=u).values_list('linked_orderposition__order_id', flat=True)
mainq = (
code
@@ -1034,7 +1034,7 @@ with scopes_disabled():
search = django_filters.CharFilter(method='search_qs')
def search_qs(self, queryset, name, value):
matching_media = ReusableMedium.objects.filter(identifier=value).values_list('linked_orderpositions', flat=True)
matching_media = ReusableMedium.objects.filter(identifier=value).values_list('linked_orderposition', flat=True)
return queryset.filter(
Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value)
+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,
+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
+3 -6
View File
@@ -20,13 +20,12 @@
# <https://www.gnu.org/licenses/>.
#
from django.db.models import Prefetch
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext, pgettext_lazy
from ..exporter import ListExporter, OrganizerLevelExportMixin
from ..models import OrderPosition, ReusableMedium
from ..models import ReusableMedium
from ..signals import register_multievent_data_exporters
@@ -45,9 +44,7 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
media = ReusableMedium.objects.filter(
organizer=self.organizer,
).select_related(
'customer', 'linked_giftcard',
).prefetch_related(
Prefetch('linked_orderpositions', queryset=OrderPosition.objects.select_related("order"))
'customer', 'linked_orderposition', 'linked_giftcard',
).order_by('created')
headers = [
@@ -77,7 +74,7 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
_('Yes') if medium.active else _('No'),
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()]),
f"{medium.linked_orderposition.order.code}-{medium.linked_orderposition.positionid}" if medium.linked_orderposition_id else '',
giftcard_secret,
medium.notes,
]
@@ -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):
@@ -1,35 +0,0 @@
# Generated by Django 4.2.26 on 2025-11-24 11:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0299_itemprogramtime_location"),
]
operations = [
migrations.AddField(
model_name="reusablemedium",
name="claim_token",
field=models.CharField(max_length=200, null=True),
),
migrations.AddField(
model_name="reusablemedium",
name="label",
field=models.CharField(max_length=200, null=True),
),
# use temporary related_name "linked_mediums" for ManyToManyField, so we can migrate existing data
migrations.AddField(
model_name="reusablemedium",
name="linked_orderpositions",
field=models.ManyToManyField(
related_name="linked_mediums", to="pretixbase.orderposition"
),
),
migrations.RunSQL(
sql="INSERT INTO pretixbase_reusablemedium_linked_orderpositions (reusablemedium_id, orderposition_id) SELECT id, linked_orderposition_id FROM pretixbase_reusablemedium WHERE linked_orderposition_id IS NOT NULL;",
reverse_sql="DELETE FROM pretixbase_reusablemedium_linked_orderpositions;",
),
]
@@ -1,44 +0,0 @@
# Generated by Django 4.2.26 on 2025-11-24 11:32
from django.db import migrations, models
def reverse(apps, schema_editor):
ReusableMedium = apps.get_model('pretixbase', 'ReusableMedium')
qs = ReusableMedium.linked_orderpositions.through.objects
objs = []
# get last added orderposition from linked_orderpositions
for rm_id, op_id in qs.filter(id__in=qs.values("reusablemedium_id").annotate(max_id=models.Max('id')).values('max_id')).values_list("reusablemedium_id", "orderposition_id"):
obj = ReusableMedium(
id=rm_id,
linked_orderposition_id=op_id,
)
objs.append(obj)
ReusableMedium.objects.bulk_update(objs, ['linked_orderposition_id'])
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0300_add_reusablemedium_label"),
]
operations = [
# according to the docs, UPDATE FROM should run similarly on sqlite and postgres, but I could not get it to work
# so roll back the data migration with code before deleting data from through-table in 0297
migrations.RunPython(migrations.RunPython.noop, reverse),
migrations.RemoveField(
model_name="reusablemedium",
name="linked_orderposition",
),
# change related_name for new ManyToManyField to previously used linked_media
migrations.AlterField(
model_name="reusablemedium",
name="linked_orderpositions",
field=models.ManyToManyField(
related_name="linked_media", to="pretixbase.orderposition"
),
),
]
+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
+5 -20
View File
@@ -72,16 +72,6 @@ class ReusableMedium(LoggedModel):
max_length=200,
verbose_name=pgettext_lazy('reusable_medium', 'Identifier'),
)
claim_token = models.CharField(
max_length=200,
verbose_name=pgettext_lazy('reusable_medium', 'Claim token'),
null=True, blank=True
)
label = models.CharField(
max_length=200,
verbose_name=pgettext_lazy('reusable_medium', 'Label'),
null=True, blank=True
)
active = models.BooleanField(
verbose_name=_('Active'),
@@ -99,14 +89,12 @@ class ReusableMedium(LoggedModel):
on_delete=models.SET_NULL,
verbose_name=_('Customer account'),
)
linked_orderpositions = models.ManyToManyField(
linked_orderposition = models.ForeignKey(
OrderPosition,
null=True, blank=True,
related_name='linked_media',
verbose_name=_('Linked tickets'),
help_text=_(
'If you link to more than one ticket, make sure there is no overlap in validity. '
'If multiple tickets are valid at once, this will lead to failed check-ins.'
)
on_delete=models.SET_NULL,
verbose_name=_('Linked ticket'),
)
linked_giftcard = models.ForeignKey(
GiftCard,
@@ -129,10 +117,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(),
})
-5
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"),
+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,
+2 -2
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(
@@ -3515,8 +3515,8 @@ def signal_listener_issue_media(sender: Event, order: Order, **kwargs):
identifier=mt.generate_identifier(sender.organizer),
active=True,
customer=order.customer,
linked_orderposition=p,
)
rm.linked_orderpositions.add(p)
rm.log_action(
'pretix.reusable_medium.created',
data={
+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>
-28
View File
@@ -461,31 +461,3 @@ class SalesChannelCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
**super().create_option(name, value, label, selected, index, subindex, attrs),
"plugin_missing": plugin and plugin not in self.event.get_plugins(),
}
class ModelChoiceIteratorWithNone(forms.models.ModelChoiceIterator):
# see django.forms.models.ModelChoiceIterator for original implementation
def __iter__(self):
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
if self.field.none_label is not None:
yield ("_none", self.field.none_label)
queryset = self.queryset
# Can't use iterator() when queryset uses prefetch_related()
if not queryset._prefetch_related_lookups:
queryset = queryset.iterator()
for obj in queryset:
yield self.choice(obj)
class ModelChoiceFieldWithNone(forms.ModelChoiceField):
iterator = ModelChoiceIteratorWithNone
def __init__(self, *args, **kwargs):
self.none_label = kwargs.pop("none_label", None)
super().__init__(*args, **kwargs)
def to_python(self, value):
if value == "_none":
return value
return super().to_python(value)
+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']
+2 -8
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
@@ -1877,7 +1871,7 @@ class ReusableMediaFilterForm(FilterForm):
Q(identifier__icontains=query)
| Q(customer__identifier__icontains=query)
| Q(customer__external_identifier__istartswith=query)
| Q(linked_orderpositions__order__code__icontains=query)
| Q(linked_orderposition__order__code__icontains=query)
| Q(linked_giftcard__secret__icontains=query)
)
+11 -21
View File
@@ -86,9 +86,9 @@ from pretix.control.forms import ExtFileField, SplitDateTimeField
from pretix.control.forms.event import (
SafeEventMultipleChoiceField, multimail_validate,
)
from pretix.control.forms.widgets import Select2, Select2Multiple
from pretix.control.forms.widgets import Select2
from pretix.multidomain.models import KnownDomain
from pretix.multidomain.urlreverse import eventreverse_absolute
from pretix.multidomain.urlreverse import build_absolute_uri
class OrganizerForm(I18nModelForm):
@@ -249,15 +249,6 @@ class SafeOrderPositionChoiceField(forms.ModelChoiceField):
return f'{op.order.code}-{op.positionid} ({str(op.item) + ((" - " + str(op.variation)) if op.variation else "")})'
class SafeOrderPositionMultipleChoiceField(forms.ModelMultipleChoiceField):
def __init__(self, queryset, **kwargs):
queryset = queryset.model.all.none()
super().__init__(queryset, **kwargs)
def label_from_instance(self, op):
return f'{op.order.code}-{op.positionid} ({str(op.item) + ((" - " + str(op.variation)) if op.variation else "")})'
class EventMetaPropertyForm(I18nModelForm):
class Meta:
model = EventMetaProperty
@@ -636,7 +627,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 +781,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)
@@ -973,12 +963,12 @@ class ReusableMediumUpdateForm(forms.ModelForm):
class Meta:
model = ReusableMedium
fields = ['active', 'expires', 'customer', 'linked_giftcard', 'linked_orderpositions', 'notes']
fields = ['active', 'expires', 'customer', 'linked_giftcard', 'linked_orderposition', 'notes']
field_classes = {
'expires': SplitDateTimeField,
'customer': SafeModelChoiceField,
'linked_giftcard': SafeModelChoiceField,
'linked_orderpositions': SafeOrderPositionMultipleChoiceField,
'linked_orderposition': SafeOrderPositionChoiceField,
}
widgets = {
'expires': SplitDateTimePickerWidget,
@@ -988,8 +978,8 @@ class ReusableMediumUpdateForm(forms.ModelForm):
super().__init__(*args, **kwargs)
organizer = self.instance.organizer
self.fields['linked_orderpositions'].queryset = OrderPosition.all.filter(order__event__organizer=organizer).all()
self.fields['linked_orderpositions'].widget = Select2Multiple(
self.fields['linked_orderposition'].queryset = OrderPosition.all.filter(order__event__organizer=organizer).all()
self.fields['linked_orderposition'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={
@@ -997,8 +987,8 @@ class ReusableMediumUpdateForm(forms.ModelForm):
}),
}
)
self.fields['linked_orderpositions'].widget.choices = self.fields['linked_orderpositions'].choices
self.fields['linked_orderpositions'].required = False
self.fields['linked_orderposition'].widget.choices = self.fields['linked_orderposition'].choices
self.fields['linked_orderposition'].required = False
self.fields['linked_giftcard'].queryset = organizer.issued_gift_cards.all()
self.fields['linked_giftcard'].widget = Select2(
@@ -1052,12 +1042,12 @@ class ReusableMediumCreateForm(ReusableMediumUpdateForm):
class Meta:
model = ReusableMedium
fields = ['active', 'type', 'identifier', 'expires', 'linked_orderpositions', 'linked_giftcard', 'customer', 'notes']
fields = ['active', 'type', 'identifier', 'expires', 'linked_orderposition', 'linked_giftcard', 'customer', 'notes']
field_classes = {
'expires': SplitDateTimeField,
'customer': SafeModelChoiceField,
'linked_giftcard': SafeModelChoiceField,
'linked_orderpositions': SafeOrderPositionMultipleChoiceField,
'linked_orderposition': SafeOrderPositionChoiceField,
}
widgets = {
'expires': SplitDateTimePickerWidget,
+11 -24
View File
@@ -29,30 +29,17 @@ class Select2Mixin:
super().__init__(*args, **kwargs)
def options(self, name, value, attrs=None):
if not value or not value[0]:
return
has_none = "_none" in value
if has_none:
value = [v for v in value if v != "_none"]
yield self.create_option(
None,
"_none",
self.choices.field.none_label,
True,
0,
subindex=None,
attrs=attrs
)
for i, selected in enumerate(self.choices.queryset.filter(pk__in=value)):
yield self.create_option(
None,
self.choices.field.prepare_value(selected),
self.choices.field.label_from_instance(selected),
True,
i + (1 if has_none else 0),
subindex=None,
attrs=attrs
)
if value and value[0]:
for i, selected in enumerate(self.choices.queryset.filter(pk__in=value)):
yield self.create_option(
None,
self.choices.field.prepare_value(selected),
self.choices.field.label_from_instance(selected),
True,
i,
subindex=None,
attrs=attrs
)
return
def optgroups(self, name, value, attrs=None):
-3
View File
@@ -743,10 +743,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.reusable_medium.created': _('The reusable medium has been created.'),
'pretix.reusable_medium.created.auto': _('The reusable medium has been created automatically.'),
'pretix.reusable_medium.changed': _('The reusable medium has been changed.'),
'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.'),
-20
View File
@@ -213,16 +213,6 @@ quota as argument in the ``quota`` keyword argument.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
"""
subevent_detail_html = EventPluginSignal()
"""
Arguments: 'subevent'
This signal allows you to append HTML to a SubEvent's detail view. You receive the
subevent as argument in the ``subevent`` keyword argument.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
"""
organizer_edit_tabs = DeprecatedSignal()
"""
Arguments: 'organizer', 'request'
@@ -271,16 +261,6 @@ As with all event plugin signals, the ``sender`` keyword argument will contain t
Additionally, the argument ``order`` and ``request`` are available.
"""
order_approve_info = EventPluginSignal()
"""
Arguments: ``order``, ``request``
This signal is sent out to display additional information on the order approve page
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
Additionally, the argument ``order`` and ``request`` are available.
"""
order_position_buttons = EventPluginSignal()
"""
Arguments: ``order``, ``position``, ``request``
@@ -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 %}
@@ -9,24 +9,23 @@
<h3 class="panel-title">{% trans "Go offline" %}</h3>
</div>
<div class="row panel-body">
<div class="col-sm-12 col-lg-6">
<p>
<div class="col-sm-12 col-md-9 nomargin-bottom">
{% blocktrans trimmed %}
You can take your event offline. Nobody except your team will be able to see or access it any more.
{% endblocktrans %}
</p>
</div>
<form class="col-sm-12 col-lg-6 text-right"
action="{% url "control:event.live" event=request.event.slug organizer=request.organizer.slug %}"
method="post">
{% csrf_token %}
<input type="hidden" name="live" value="false">
</div>
<div class="col-sm-12 col-md-3">
<form action="{% url "control:event.live" event=request.event.slug organizer=request.organizer.slug %}"
method="post">
{% csrf_token %}
<input type="hidden" name="live" value="false">
<button type="submit" class="btn btn-primary btn-lg">
<span class="fa fa-power-off"></span>
{% trans "Go offline" %}
</button>
</form>
<button type="submit" class="btn btn-primary btn-lg btn-block">
<span class="fa fa-power-off"></span>
{% trans "Go offline" %}
</button>
</form>
</div>
</div>
</div>
@@ -35,24 +34,22 @@
<h3 class="panel-title">{% trans "Cancel event" %}</h3>
</div>
<div class="row panel-body">
<div class="col-sm-12 col-lg-6">
<p>
<div class="col-sm-12 col-md-9 nomargin-bottom">
{% blocktrans trimmed %}
If you need to call off your event you want to cancel and refund all tickets, you can do so through
this option.
{% endblocktrans %}
</p>
</div>
<div class="col-sm-12 col-lg-6 text-right">
<a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-danger btn-lg pull-right {% if "event:cancel" not in request.eventpermset %}disabled{% endif %}">
<span class="fa fa-ban"></span>
{% if "event:cancel" in request.eventpermset %}
<div class="col-sm-12 col-md-3 text-center">
{% if "event:cancel" in request.eventpermset %}
<a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-danger btn-block btn-lg">
<span class="fa fa-ban"></span>
{% trans "Cancel event" %}
{% else %}
{% trans "No permission" %}
{% endif %}
</a>
</a>
{% else %}
{% trans "No permission" %}
{% endif %}
</div>
</div>
</div>
@@ -62,16 +59,15 @@
<h3 class="panel-title">{% trans "Delete personal data" %}</h3>
</div>
<div class="row panel-body">
<div class="col-sm-12 col-lg-6">
<p>
<div class="col-sm-12 col-md-9 nomargin-bottom">
{% blocktrans trimmed %}
You can remove personal data such as names and email addresses from your event and only retain the
financial information such as the number and type of tickets sold.
{% endblocktrans %}
</p>
</div>
<div class="col-sm-12 col-lg-6 text-right">
<a href="{% url "control:event.shredder.start" event=request.event.slug organizer=request.organizer.slug %}" class="btn btn-danger btn-lg">
<div class="col-sm-12 col-md-3">
<a href="
{% url "control:event.shredder.start" event=request.event.slug organizer=request.organizer.slug %}" class="btn btn-danger btn-lg btn-block">
<span class="fa fa-eraser"></span>
{% trans "Delete personal data" %}
</a>
@@ -84,17 +80,15 @@
<h3 class="panel-title">{% trans "Delete event" %}</h3>
</div>
<div class="row panel-body">
<div class="col-sm-12 col-lg-6">
<p>
<div class="col-sm-12 col-md-9 nomargin-bottom">
{% blocktrans trimmed %}
You can delete your event completely only as long as it does not contain any undeletable data, such as
orders not performed in test mode.
{% endblocktrans %}
</p>
</div>
<div class="col-sm-12 col-lg-6 text-right">
<div class="col-sm-12 col-md-3">
<a href="{% url "control:event.delete" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-danger btn-lg {% if not request.event.allow_delete %}disabled{% endif %}">
class="btn btn-danger btn-block btn-lg {% if not request.event.allow_delete %}disabled{% endif %}">
<span class="fa fa-trash"></span>
{% trans "Delete event" %}
</a>
@@ -1,5 +1,4 @@
{% extends "pretixcontrol/event/base.html" %}
{% load eventsignal %}
{% load i18n %}
{% block title %}
{% trans "Approve order" %}
@@ -8,9 +7,6 @@
<h1>
{% trans "Approve order" %}
</h1>
{% eventsignal request.event "pretix.control.signals.order_approve_info" order=order request=request %}
<p>{% blocktrans trimmed %}
Do you really want to approve this order?
{% endblocktrans %}</p>
@@ -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">
@@ -58,8 +58,8 @@
<a href="?{% url_replace request 'ordering' 'identifier' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>{% trans "Media type" context "reusable_media" %}
<a href="?{% url_replace request 'ordering' '-type' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'type' %}"><i class="fa fa-caret-up"></i></a></th>
<a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Connections" context "reusable_media" %}</th>
<th></th>
</tr>
@@ -90,13 +90,13 @@
{% endif %}
</span>
{% endif %}
{% for op in m.linked_orderpositions.all %}
{% if m.linked_orderposition %}
<span class="helper-display-block">
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=op.order.event.slug organizer=request.organizer.slug code=op.order.code %}">
{{ op.order.code }}</a>-{{ op.positionid }}
<a href="{% url "control:event.order" event=m.linked_orderposition.order.event.slug organizer=request.organizer.slug code=m.linked_orderposition.order.code %}">
{{ m.linked_orderposition.order.code }}</a>-{{ m.linked_orderposition.positionid }}
</span>
{% endfor %}
{% endif %}
{% if m.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
@@ -26,19 +26,7 @@
<dt>{% trans "Media type" context "reusable_media" %}</dt>
<dd>{{ medium.get_type_display }}</dd>
<dt>{% trans "Identifier" context "reusable_media" %}</dt>
<dd>
<code id="medium_identifier">{{ medium.identifier }}</code>
<button type="button" class="btn btn-default btn-xs btn-clipboard js-only" data-clipboard-target="#medium_identifier">
<i class="fa fa-clipboard" aria-hidden="true"></i>
<span class="sr-only">{% trans "Copy to clipboard" %}</span>
</button>
{% if medium.type == "barcode" %}
<button type="button" class="btn btn-default btn-xs js-only" data-toggle="qrcode" data-qrcode="{{ medium.identifier }}">
<i class="fa fa-qrcode" aria-hidden="true"></i>
<span class="sr-only">{% trans "Create QR code" %}</span>
</button>
{% endif %}
</dd>
<dd><code>{{ medium.identifier }}</code></dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not medium.active %}
@@ -53,34 +41,34 @@
<dd>
{% if medium.customer %}
<span class="helper-display-block">
<span class="fa fa-user fa-fw"></span>
{% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=medium.customer.identifier %}">
{{ medium.customer }}
</a>
{% else %}
<span class="fa fa-user fa-fw"></span>
{% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=medium.customer.identifier %}">
{{ medium.customer }}
{% endif %}
</span>
</a>
{% else %}
{{ medium.customer }}
{% endif %}
</span>
{% endif %}
{% for op in medium.linked_orderpositions.all %}
{% if medium.linked_orderposition %}
<span class="helper-display-block">
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=op.order.event.slug organizer=request.organizer.slug code=op.order.code %}">
{{ op.order.code }}</a>-{{ op.positionid }}
</span>
{% endfor %}
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=medium.linked_orderposition.order.event.slug organizer=request.organizer.slug code=medium.linked_orderposition.order.code %}">
{{ medium.linked_orderposition.order.code }}</a>-{{ medium.linked_orderposition.positionid }}
</span>
{% endif %}
{% if medium.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}
</a>
{% else %}
{{ medium.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span>
<span class="fa fa-credit-card fa-fw"></span>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}
</a>
{% else %}
{{ medium.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span>
{% endif %}
</dd>
{% if medium.notes %}
@@ -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 %}
@@ -4,306 +4,290 @@
{% load formset_tags %}
{% load eventsignal %}
{% load static %}
{% load money %}
{% load icon %}
{% block title %}{% blocktrans trimmed with name=subevent.name context "subevent" %}Date: {{ name }}
{% endblocktrans %}{% endblock %}
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
{% block content %}
<h1>
{% blocktrans trimmed with name=subevent.name context "subevent" %}Date: {{ name }}{% endblocktrans %}
{% if 'event.subevents:write' in request.eventpermset %}
<a href="{% url "control:event.subevent.edit" event=request.event.slug organizer=request.event.organizer.slug subevent=subevent.pk %}"
class="btn btn-default">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
{% endif %}
</h1>
<div class="row">
<div class="{% if "event.orders:read" in request.eventpermset %}col-md-5{% else %}col-md-10{% endif %} col-xs-12">
<fieldset>
<legend>{% trans "General information" %}</legend>
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ subevent.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>#{{ subevent.pk }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if not subevent.active %}
<span class="label label-danger">{% trans "Disabled" %}</span>
{% elif subevent.presale_has_ended %}
<span class="label label-warning">{% trans "Presale over" %}</span>
{% elif not subevent.presale_is_running %}
<span class="label label-warning">{% trans "Presale not started" %}</span>
{% else %}
<span class="label label-success">{% trans "On sale" %}</span>
{% endif %}
</dd>
<dt>{% trans "Event start time" %}</dt>
<dd>{{ subevent.date_from|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Event end time" %}</dt>
<dd>{{ subevent.date_to|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% if subevent.date_admission %}
<dt>{% trans "Admission time" %}</dt>
<dd>{{ subevent.date_admission|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% endif %}
{% if subevent.presale_start %}
<dt>{% trans "Start of presale" %}</dt>
<dd>{{ subevent.presale_start|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% endif %}
{% if subevent.presale_end %}
<dt>{% trans "End of presale" %}</dt>
<dd>{{ subevent.presale_end|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% endif %}
{% if subevent.location %}
<dt>{% trans "Location" %}</dt>
<dd>{{ subevent.location|linebreaksbr }}</dd>
{% endif %}
<dt>{% trans "Show in lists" %}</dt>
<dd>{{ subevent.is_public|yesno }}</dd>
{% for k, v in subevent.meta_data.items %}
<dt>{{ k }}</dt>
<dd>{{ v }}</dd>
{% endfor %}
{% if subevent.comment %}
<dt>{% trans "Internal comment" %}</dt>
<dd>{{ subevent.comment|linebreaksbr }}</dd>
{% endif %}
</dl>
</fieldset>
<fieldset>
<legend>{% trans "Quotas" %}</legend>
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Quota name" %}</th>
<th>{% trans "Products" %}</th>
<th>{% trans "Total capacity" %}</th>
<th>{% trans "Capacity left" %}</th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody>
{% for q in quotas %}
<tr>
<td>
<strong><a
href="{% url "control:event.items.quotas.show" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}">{{ q.name }}</a></strong>
{% if q.ignore_for_event_availability %}
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip"
title="{% trans "Ignore this quota when determining event availability" %}"></span>
{% endif %}
</td>
<td>
<ul>
{% for item in q.cached_items %}
{% if not item.has_variations %}
<li>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a>
</li>
{% endif %}
{% endfor %}
{% for v in q.variations.all %}
<li>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=v.item.id %}#tab-0-3-open">
{{ v.item }} {{ v }}</a></li>
{% endfor %}
</ul>
</td>
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
<td class="text-right flip">
{% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</fieldset>
{% if checkinlists %}
{% if not subevent.pk %}
<h1>{% trans "Create date" context "subevent" %}</h1>
{% else %}
<h1>{% trans "Date" context "subevent" %}</h1>
{% endif %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% for f in itemvar_forms %}
{% bootstrap_form_errors f %}
{% endfor %}
<div class="row">
<div class="col-xs-12 {% if subevent.pk %}col-lg-10{% endif %}">
<fieldset>
<legend>{% trans "Check-in lists" %}</legend>
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Name" %}</th>
{% if "event.orders:read" in request.eventpermset %}
<th>{% trans "Checked in" %}</th>
{% endif %}
<th>{% trans "Products" %}</th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody>
{% for cl in checkinlists %}
<tr>
<td>
<strong><a
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">{{ cl.name }}</a></strong>
</td>
{% if "event.orders:read" in request.eventpermset %}
<td>
<div class="quotabox availability">
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
</div>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %}
{% bootstrap_field form.comment layout="control" %}
{% if meta_forms %}
<div class="form-group metadata-group">
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
<div class="col-md-9">
{% for form in meta_forms %}
<div class="row">
<div class="col-md-4">
<label for="{{ form.value.id_for_label }}">
{{ form.property.name }}
</label>
</div>
<div class="col-md-8">
{% bootstrap_form form layout="inline" error_types="all" %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="control" %}
{% bootstrap_field form.presale_end layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Quotas" %}</legend>
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
{{ formset.management_form }}
{% bootstrap_formset_errors formset %}
<div data-formset-body>
{% for form in formset %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.name layout='inline' form_group_class="" %}
</div>
<div class="numbers">
{{ cl.checkin_count|default_if_none:"0" }} /
{{ cl.position_count|default_if_none:"0" }}
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</td>
{% endif %}
<td>
{% if cl.all_products %}
<em>{% trans "All" %}</em>
{% else %}
<ul>
{% for item in cl.limit_products.all %}
<li>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</td>
<td class="text-right flip">
{% if "event.orders:read" in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
class="btn btn-default btn-sm"><i class="fa fa-eye"></i></a>
{% endif %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ cl.id }}"
class="btn btn-sm btn-default" title="{% trans "Clone" %}"
data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
</td>
</tr>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_form_errors form %}
{% bootstrap_field form.size layout="control" %}
{% bootstrap_field form.itemvars layout="control" %}
{% bootstrap_field form.release_after_exit layout="control" %}
{% bootstrap_field form.ignore_for_event_availability layout="control" %}
</div>
</div>
{% endfor %}
</tbody>
</table>
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ formset.empty_form.id }}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_field formset.empty_form.size layout="control" %}
{% bootstrap_field formset.empty_form.itemvars layout="control" %}
{% bootstrap_field formset.empty_form.release_after_exit layout="control" %}
{% bootstrap_field formset.empty_form.ignore_for_event_availability layout="control" %}
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new quota" %}</button>
</p>
</fieldset>
{% endif %}
{% eventsignal request.event "pretix.control.signals.subevent_detail_html" subevent=subevent %}
</div>
{% if "event.orders:read" in request.eventpermset %}
<div class="col-md-5 col-xs-12">
<fieldset>
<legend>
{% trans "Orders" %}
<span class="badge">
{{ order_count }}
</span>
</legend>
{% if order_count %}
<div class="table-responsive">
<table class="table table-condensed table-hover table-orders">
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "Details" %}</th>
</tr>
</thead>
<tbody>
{% for o in orders %}
<tr>
<td>
<strong>
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code %}">
{{ o.code }}
</a>
</strong>
<br>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if o.status == "p" and o.pcnt == 0 %}
{# Everything related to this subevent is canceled #}
<span class="label label-danger">
<span class="fa fa-times"></span>
{% trans "partially canceled" %}
</span>
{% else %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}
{% endif %}
</td>
<td>
{% if "." in o.sales_channel.icon %}
<img src="{% static o.sales_channel.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ o.sales_channel.label }}">
{% else %}
<span class="fa fa-fw fa-{{ o.sales_channel.icon }} text-muted"
data-toggle="tooltip" title="{{ o.sales_channel.label }}"></span>
{% endif %}
{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if o.email %}
<br>{% icon "envelope-o fa-fw text-muted" %}
{{ o.email|default_if_none:"" }}
{% endif %}
{% if o.invoice_address.name %}
<br>{% icon "user fa-fw text-muted" %} {{ o.invoice_address.name }}
{% endif %}
<br>{% icon "ticket text-muted fa-fw" %} {{ o.pcnt }}
{% if o.comment %}
<br>
<span class="text-muted">
{{ o.comment|linebreaksbr }}
</span>
{% endif %}
{% if o.custom_followup_due %}
<br>
<span class="label label-danger">{% blocktrans trimmed with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}
TODO {{ date }}{% endblocktrans %}</span>
{% elif o.custom_followup_at %}
<br>
<span class="label label-default">{% blocktrans trimmed with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %}
TODO {{ date }}{% endblocktrans %}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<legend>{% trans "Product settings" %}</legend>
<p class="text-muted">
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
</p>
{% for f in itemvar_forms %}
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
{% bootstrap_form_errors f %}
<div class="form-group subevent-itemvar-group">
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
{% if f.variation %}{{ f.item }} {{ f.variation }}{% else %}{{ f.item }}{% endif %}
</label>
<div class="col-md-4">
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
</div>
<div class="col-md-4">
<br>
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
</div>
</div>
<div class="form-group subevent-itemvar-group">
<div class="col-md-4 col-md-offset-3">
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
</div>
<div class="col-md-4">
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
</div>
</div>
</div>
{% if order_count > 10 %}
<p class="text-center">
<a href="{% url "control:event.orders" organizer=request.organizer.slug event=request.event.slug %}?subevent={{ subevent.pk }}"
class="btn btn-default">
{% trans "View all" %}
</a>
</p>
{% endif %}
{% else %}
<div class="empty-collection">
<p>
{% blocktrans trimmed %}
No orders found.
{% endblocktrans %}
</p>
{% endfor %}
</fieldset>
<fieldset>
<legend>{% trans "Check-in lists" %}</legend>
<p class="help-block">
{% blocktrans trimmed %}
You can choose to either add one or more check-in lists for every date in your series individually,
or use just one check-in list for all your dates and limit admission through check-in rules. Which
approach is better depends on multiple factors, such as the number of dates in your series. For a
series with one or less event date per day, individual lists are usually more helpful. If you
use dates to represent many time slots on the same day, or even overlapping time slots, working with
just one large check-in list will be easier.
{% endblocktrans %}
</p>
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
{{ cl_formset.management_form }}
{% bootstrap_formset_errors cl_formset %}
<div data-formset-body>
{% for form in cl_formset %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_form_errors form %}
{% bootstrap_field form.include_pending layout="control" %}
{% bootstrap_field form.all_products layout="control" %}
{% bootstrap_field form.limit_products layout="control" %}
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
{% if form.gates %}
{% bootstrap_field form.gates layout="control" %}
{% endif %}
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ cl_formset.empty_form.id }}
{% bootstrap_field cl_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field cl_formset.empty_form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_field cl_formset.empty_form.include_pending layout="control" %}
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
{% if cl_formset.empty_form.gates %}
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
{% endif %}
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new check-in list" %}
</button>
</p>
</fieldset>
{% for f in plugin_forms %}
{% if f.title %}
<fieldset>
<legend>{{ f.title }}</legend>
{% if f.template %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
{% endif %}
</fieldset>
{% endif %}
{% endfor %}
<fieldset>
<legend>{% trans "Additional settings" %}</legend>
{% for f in plugin_forms %}
{% if not f.title %}
{% if f.template %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
{% endif %}
{% endif %}
{% endfor %}
</fieldset>
</div>
{% endif %}
<div class="col-md-2 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Date history" context "subevent" %}
</h3>
{% if subevent.pk %}
<div class="col-xs-12 col-lg-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Date history" context "subevent" %}
</h3>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=subevent %}
</div>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=subevent %}
</div>
{% endif %}
</div>
</div>
<div class="form-group submit-group submit-group-sticky">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -1,296 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load formset_tags %}
{% load eventsignal %}
{% load static %}
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
{% block content %}
{% if not subevent.pk %}
<h1>{% trans "Create date" context "subevent" %}</h1>
{% else %}
<h1>{% trans "Date" context "subevent" %}</h1>
{% endif %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% for f in itemvar_forms %}
{% bootstrap_form_errors f %}
{% endfor %}
<div class="row">
<div class="col-xs-12 {% if subevent.pk %}col-lg-10{% endif %}">
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %}
{% bootstrap_field form.comment layout="control" %}
{% if meta_forms %}
<div class="form-group metadata-group">
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
<div class="col-md-9">
{% for form in meta_forms %}
<div class="row">
<div class="col-md-4">
<label for="{{ form.value.id_for_label }}">
{{ form.property.name }}
</label>
</div>
<div class="col-md-8">
{% bootstrap_form form layout="inline" error_types="all" %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="control" %}
{% bootstrap_field form.presale_end layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Quotas" %}</legend>
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
{{ formset.management_form }}
{% bootstrap_formset_errors formset %}
<div data-formset-body>
{% for form in formset %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_form_errors form %}
{% bootstrap_field form.size layout="control" %}
{% bootstrap_field form.itemvars layout="control" %}
{% bootstrap_field form.release_after_exit layout="control" %}
{% bootstrap_field form.ignore_for_event_availability layout="control" %}
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ formset.empty_form.id }}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_field formset.empty_form.size layout="control" %}
{% bootstrap_field formset.empty_form.itemvars layout="control" %}
{% bootstrap_field formset.empty_form.release_after_exit layout="control" %}
{% bootstrap_field formset.empty_form.ignore_for_event_availability layout="control" %}
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new quota" %}</button>
</p>
</fieldset>
<fieldset>
<legend>{% trans "Product settings" %}</legend>
<p class="text-muted">
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
</p>
{% for f in itemvar_forms %}
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
{% bootstrap_form_errors f %}
<div class="form-group subevent-itemvar-group">
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
{% if f.variation %}{{ f.item }} {{ f.variation }}{% else %}{{ f.item }}{% endif %}
</label>
<div class="col-md-4">
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
</div>
<div class="col-md-4">
<br>
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
</div>
</div>
<div class="form-group subevent-itemvar-group">
<div class="col-md-4 col-md-offset-3">
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
</div>
<div class="col-md-4">
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
</div>
</div>
</div>
{% endfor %}
</fieldset>
<fieldset>
<legend>{% trans "Check-in lists" %}</legend>
<p class="help-block">
{% blocktrans trimmed %}
You can choose to either add one or more check-in lists for every date in your series individually,
or use just one check-in list for all your dates and limit admission through check-in rules. Which
approach is better depends on multiple factors, such as the number of dates in your series. For a
series with one or less event date per day, individual lists are usually more helpful. If you
use dates to represent many time slots on the same day, or even overlapping time slots, working with
just one large check-in list will be easier.
{% endblocktrans %}
</p>
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
{{ cl_formset.management_form }}
{% bootstrap_formset_errors cl_formset %}
<div data-formset-body>
{% for form in cl_formset %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_form_errors form %}
{% bootstrap_field form.include_pending layout="control" %}
{% bootstrap_field form.all_products layout="control" %}
{% bootstrap_field form.limit_products layout="control" %}
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
{% if form.gates %}
{% bootstrap_field form.gates layout="control" %}
{% endif %}
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ cl_formset.empty_form.id }}
{% bootstrap_field cl_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
<div class="row">
<div class="col-md-10">
{% bootstrap_field cl_formset.empty_form.name layout='inline' form_group_class="" %}
</div>
<div class="col-md-2 text-right flip">
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</h4>
</div>
<div class="panel-body form-horizontal">
{% bootstrap_field cl_formset.empty_form.include_pending layout="control" %}
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
{% if cl_formset.empty_form.gates %}
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
{% endif %}
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new check-in list" %}
</button>
</p>
</fieldset>
{% for f in plugin_forms %}
{% if f.title %}
<fieldset>
<legend>{{ f.title }}</legend>
{% if f.template %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
{% endif %}
</fieldset>
{% endif %}
{% endfor %}
<fieldset>
<legend>{% trans "Additional settings" %}</legend>
{% for f in plugin_forms %}
{% if not f.title %}
{% if f.template %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
{% endif %}
{% endif %}
{% endfor %}
</fieldset>
</div>
{% if subevent.pk %}
<div class="col-xs-12 col-lg-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Date history" context "subevent" %}
</h3>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=subevent %}
</div>
</div>
{% endif %}
</div>
<div class="form-group submit-group submit-group-sticky">
<a href="{{ next_url }}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -133,7 +133,7 @@
</td>
{% endif %}
<td>
<strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}">
<strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}">
{{ s.name }}</a></strong><br>
<small class="text-muted">
#{{ s.pk }}
@@ -182,7 +182,7 @@
{% endif %}
{% if "event.subevents:write" in request.eventpermset %}
<a href="{% url "control:event.subevent.edit" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<div class="btn-group {% if forloop.revcounter0 < 2 %}dropup{% endif %}">
<button type="button" class="btn btn-default btn-sm dropdown-toggle"
data-toggle="dropdown">
@@ -201,7 +201,7 @@
</li>
</ul>
</div>
<a href="{% url "control:event.subevent.delete" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
<a href="{% url "control:event.subevent.delete" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td>
</tr>
+1 -2
View File
@@ -308,8 +308,7 @@ urlpatterns = [
re_path(r'^pdf/editor/(?P<filename>[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'),
re_path(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
re_path(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'),
re_path(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventDetail.as_view(), name='event.subevent'),
re_path(r'^subevents/(?P<subevent>\d+)/edit$', subevents.SubEventUpdate.as_view(), name='event.subevent.edit'),
re_path(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
re_path(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
name='event.subevent.delete'),
re_path(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
+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):
+2 -2
View File
@@ -44,7 +44,7 @@ from pretix.control.permissions import (
from pretix.helpers.models import modelcopy
from ...helpers.compat import CompatDeleteView
from . import CreateView, UpdateView
from . import CreateView, PaginationMixin, UpdateView
class DiscountDelete(EventPermissionRequiredMixin, CompatDeleteView):
@@ -183,7 +183,7 @@ class DiscountCreate(EventPermissionRequiredMixin, CreateView):
return super().form_invalid(form)
class DiscountList(ListView):
class DiscountList(PaginationMixin, ListView):
model = Discount
context_object_name = 'discounts'
template_name = 'pretixcontrol/items/discounts.html'
+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")
+1 -1
View File
@@ -335,7 +335,7 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
return super().form_invalid(form)
class CategoryList(ListView):
class CategoryList(PaginationMixin, ListView):
model = ItemCategory
context_object_name = 'categories'
template_name = 'pretixcontrol/items/categories.html'
+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):
+15 -50
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
@@ -3386,10 +3384,8 @@ class ReusableMediaListView(OrganizerDetailViewMixin, OrganizerPermissionRequire
def get_queryset(self):
qs = self.request.organizer.reusable_media.select_related(
'customer',
'linked_giftcard',
).prefetch_related(
Prefetch('linked_orderpositions', queryset=OrderPosition.objects.select_related("order", "order__event"))
'customer', 'linked_orderposition', 'linked_orderposition__order', 'linked_orderposition__order__event',
'linked_giftcard'
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
@@ -3437,14 +3433,10 @@ class ReusableMediumCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
@transaction.atomic
def form_valid(self, form):
r = super().form_valid(form)
data = {
form.instance.log_action('pretix.reusable_medium.created', user=self.request.user, data={
k: getattr(form.instance, k)
for k in form.changed_data
}
if "linked_orderpositions" in data:
data["linked_orderpositions"] = data["linked_orderpositions"].values_list("pk", flat=True)
form.instance.log_action('pretix.reusable_medium.created', user=self.request.user, data=data)
})
messages.success(self.request, _('Your changes have been saved.'))
return r
@@ -3469,40 +3461,13 @@ class ReusableMediumUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
@transaction.atomic
def form_valid(self, form):
prev_linked_ops_pks = list(getattr(self.object, "linked_orderpositions").values_list("pk", flat=True))
result = super().form_valid(form)
if form.has_changed():
data = {
self.object.log_action('pretix.reusable_medium.changed', user=self.request.user, data={
k: getattr(self.object, k)
for k in form.changed_data
}
if "linked_orderpositions" in data:
# handle changes to linked_orderpositions separately
linked_ops_pks = data["linked_orderpositions"].values_list("pk", flat=True)
del data["linked_orderpositions"]
for op_pk in prev_linked_ops_pks:
if op_pk not in linked_ops_pks:
self.object.log_action(
'pretix.reusable_medium.linked_orderposition.removed',
user=self.request.user,
data={
'linked_orderposition': op_pk,
}
)
for op_pk in linked_ops_pks:
if op_pk not in prev_linked_ops_pks:
self.object.log_action(
'pretix.reusable_medium.linked_orderposition.added',
user=self.request.user,
data={
'linked_orderposition': op_pk,
}
)
if data:
# log change-action only for changes other than linked_orderpositions
self.object.log_action('pretix.reusable_medium.changed', user=self.request.user, data=data)
})
messages.success(self.request, _('Your changes have been saved.'))
return result
return super().form_valid(form)
def get_success_url(self):
return reverse('control:organizer.reusable_medium', kwargs={
+8 -81
View File
@@ -41,9 +41,7 @@ from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import transaction
from django.db.models import (
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Subquery,
)
from django.db.models import Count, F, Prefetch, ProtectedError
from django.db.models.functions import Coalesce, TruncDate, TruncTime
from django.forms import inlineformset_factory
from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -51,21 +49,17 @@ from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.formats import get_format
from django.utils.functional import cached_property
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.views import View
from django.views.generic import (
CreateView, DetailView, FormView, ListView, UpdateView,
)
from django.views.generic import CreateView, FormView, ListView, UpdateView
from pretix.base.models import CartPosition, LogEntry, OrderPosition
from pretix.base.models import CartPosition, LogEntry
from pretix.base.models.checkin import CheckinList
from pretix.base.models.event import SubEvent, SubEventMetaValue
from pretix.base.models.items import (
Item, ItemVariation, Quota, SubEventItem, SubEventItemVariation,
ItemVariation, Quota, SubEventItem, SubEventItemVariation,
)
from pretix.base.models.orders import CancellationRequest
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services import tickets
from pretix.base.services.quotas import QuotaAvailability
@@ -511,68 +505,9 @@ class SubEventEditorMixin(MetaDataEditorMixin):
) and self.cl_formset.is_valid() and all(f.is_valid() for f in self.plugin_forms)
class SubEventDetail(EventPermissionRequiredMixin, DetailView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
permission = None
context_object_name = 'subevent'
def get_object(self, queryset=None) -> SubEvent:
try:
return self.request.event.subevents.get(
id=self.kwargs['subevent']
)
except SubEvent.DoesNotExist:
raise Http404(pgettext_lazy("subevent", "The requested date does not exist."))
def get_context_data(self, **kwargs):
oqs = self.request.event.orders.filter(
Exists(
OrderPosition.objects.filter(
subevent=self.object,
order_id=OuterRef("id"),
)
)
).annotate(
pcnt=Subquery(
OrderPosition.objects.filter(
subevent=self.object,
).values("subevent").annotate(c=Count("*")).values("c")
),
has_cancellation_request=Exists(CancellationRequest.objects.filter(order=OuterRef("pk"))),
).select_related("invoice_address").prefetch_related("sales_channel")
ctx = {
"quotas": self.object.quotas.prefetch_related(
Prefetch(
"items",
queryset=Item.objects.annotate(
has_variations=Exists(ItemVariation.objects.filter(item=OuterRef("pk")))
),
to_attr="cached_items"
),
"variations",
"variations__item",
).order_by("name", "pk"),
"checkinlists": self.object.checkinlist_set.prefetch_related("limit_products"),
"orders": oqs[:11],
"order_count": oqs.count(),
}
qa = QuotaAvailability()
qa.queue(*ctx["quotas"])
qa.compute()
for quota in ctx["quotas"]:
quota.cached_avail = qa.results[quota]
return super().get_context_data(
**kwargs,
**ctx,
)
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/edit.html'
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'event.subevents:write'
context_object_name = 'subevent'
form_class = SubEventForm
@@ -638,28 +573,20 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self) -> str:
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
return self.request.GET.get("next")
return reverse('control:event.subevent', kwargs={
return reverse('control:event.subevents', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
'subevent': self.object.pk,
})
}) + ('?' + self.request.GET.get('returnto') if 'returnto' in self.request.GET else '')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
return kwargs
def get_context_data(self, **kwargs):
return super().get_context_data(
next_url=self.get_success_url()
)
class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/edit.html'
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'event.subevents:write'
context_object_name = 'subevent'
form_class = SubEventForm
+7 -26
View File
@@ -145,21 +145,11 @@ def event_list(request):
if 'can_copy' in request.GET:
qs = EventWizardCopyForm.copy_from_queryset(request.user, request.session)
else:
permission = request.GET.get('permission')
if permission:
qs = request.user.get_events_with_permission(permission, request)
else:
qs = request.user.get_events_with_any_permission(request)
name_slug_q = Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query)
organizer = request.GET.get('organizer')
if organizer:
qs = qs.filter(organizer__slug=organizer)
else:
name_slug_q |= Q(organizer__name__icontains=i18ncomp(query)) | Q(organizer__slug__icontains=query)
qs = request.user.get_events_with_any_permission(request)
qs = qs.filter(
name_slug_q
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query) |
Q(organizer__name__icontains=i18ncomp(query)) | Q(organizer__slug__icontains=query)
).annotate(
min_from=Min('subevents__date_from'),
max_from=Max('subevents__date_from'),
@@ -172,19 +162,10 @@ def event_list(request):
total = qs.count()
pagesize = 20
offset = (page - 1) * pagesize
results = []
if page == 1 and 'include_none' in request.GET and not query:
results.append({
'id': "_none",
'text': _("No event"),
'name': _("No event"),
'type': "event",
})
results += [
serialize_event(e) for e in qs.select_related('organizer')[offset:offset + pagesize]
]
doc = {
'results': results,
'results': [
serialize_event(e) for e in qs.select_related('organizer')[offset:offset + pagesize]
],
'pagination': {
"more": total >= (offset + pagesize)
}
@@ -200,7 +181,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
+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,
-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
+84 -50
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-05-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"PO-Revision-Date: 2026-05-01 21:00+0000\n"
"Last-Translator: Paul Berschick <paul@plainschwarz.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.5\n"
"X-Generator: Weblate 5.17\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -620,17 +620,16 @@ msgstr ""
"como variaciones o paquetes."
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "Se ha modificado la cuota"
msgstr "Gestión de cuotas"
#: 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 ""
"Esto incluye acciones relacionadas, como la creación, la eliminación, la "
"apertura o el cierre de cuotas. No se envía ningún webhook cuando se "
"producen cambios en la disponibilidad resultante."
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -3419,13 +3418,11 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"El campo «%(label)s» no puede contener caracteres especiales como «%(chars)s"
"»."
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr "El campo «%(label)s» no puede contener una URL (%(url)s)."
msgstr ""
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8364,14 +8361,19 @@ msgid "Program times"
msgstr "Horarios del programa"
#: 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 de mayo de 2017, de 10:00 a 12:00, Sala 1\n"
"31 de mayo de 2017, de 14:00 a 16:00, Sala 2\n"
"31 de mayo de 2017, de 14:00 a 1 de junio de 2017, 14:00, Edificio A"
"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"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8901,7 +8903,13 @@ msgid "This voucher code is not known in our database."
msgstr "Este vale de compra no se conoce en nuestra base de datos."
#: 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."
@@ -8909,14 +8917,22 @@ msgid_plural ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching products."
msgstr[0] ""
"El código de descuento «%(voucher)s» solo se puede utilizar si seleccionas "
"al menos%(number)s productos que cumplan los requisitos."
"El vale de compra \"%(voucher)s\" solo se puede utilizar si selecciona al "
"menos %(number)s productos coincidentes."
msgstr[1] ""
"El código de descuento «%(voucher)s» solo se puede utilizar si seleccionas "
"al menos %(number)s productos que cumplan los requisitos."
"Los vales de compra \"%(voucher)s\" solo se pueden utilizar si selecciona al "
"menos %(number)s productos coincidentes."
#: 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 "
@@ -8926,15 +8942,13 @@ 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] ""
"El código promocional «%(voucher)s» solo se puede utilizar si seleccionas al "
"menos %(number)s producto que cumpla los requisitos. Por lo tanto, hemos "
"eliminado de tu carrito algunos artículos que ya no se pueden comprar de "
"esta forma."
"El vale de compra \"%(voucher)s\" solo se puede utilizar si selecciona al "
"menos %(number)s productos coincidentes. Por lo tanto, hemos eliminado "
"algunas posiciones de su carrito que ya no se pueden comprar así."
msgstr[1] ""
"El código promocional «%(voucher)s» solo se puede utilizar si seleccionas al "
"menos %(number)s productos que cumplan los requisitos. Por lo tanto, hemos "
"eliminado de tu carrito algunos artículos que ya no se pueden comprar de "
"esta forma."
"Los vale de compra \"%(voucher)s\" solo se pueden utilizar si selecciona al "
"menos %(number)s productos coincidentes. Por lo tanto, hemos eliminado "
"algunas posiciones de su carrito que ya no se pueden comprar así."
#: pretix/base/services/cart.py:176
msgid ""
@@ -14240,8 +14254,6 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"Ha introducido una URL que no está permitida. Elimina %(match)s de su "
"entrada."
#: pretix/base/views/errors.py:48
msgid ""
@@ -16182,8 +16194,14 @@ msgid "inactive"
msgstr "inactivo"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr "Ejemplo de Centro de Conferencia : Heidelberg, Alemania"
msgstr ""
"Ejemplo de Centro de Conferencia \n"
"Heidelberg, Alemania"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -23641,8 +23659,11 @@ msgid "Quota history"
msgstr "Historial de cuotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "Modificar varias cuotas"
msgstr "Cambiar varias fechas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23692,15 +23713,18 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "Eliminar cuotas"
msgstr "Borrar cuota"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, python-format
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "¿Está seguro de que desea eliminar la siguiente cuota?"
msgstr[1] "¿Está seguro de que desea eliminar las siguientes %(num)s cuotas?"
msgstr[0] "¿Está seguro de que desea borrar las fechas siguientes?"
msgstr[1] "¿Está seguro de que desea borrar las fechas siguientes?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -24305,15 +24329,12 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"Este pedido se modificó después de que se generara la última factura. Aún no "
"se ha generado una nueva factura, ya que las facturas están configuradas "
"para generarse al realizar el pago o si así lo exige la forma de pago. Se "
"generará una nueva factura una vez que el cliente abone la factura o "
"seleccione una forma de pago que requiera una factura."
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "Reemitir factura"
msgstr "Solicitar factura"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24744,16 +24765,23 @@ msgid "How should the refund be sent?"
msgstr "¿Cómo se debe de realizar este reembolso?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"Los pagos que hayas seleccionado para reembolsos automáticos se enviarán "
"inmediatamente al proveedor de pagos correspondiente. Los reembolsos "
"manuales se crearán como reembolsos pendientes, que podrás marcar como "
"completados más adelante, una vez que hayas devuelto el dinero al cliente."
"Cualquier pago que haya seleccionado de manera automática para reembolso "
"será comunicado inmediatamente a la entidad de pago correspondiente. Los "
"devoluciones manuales se crearán como reembolsos pendientes, podrá marcarlos "
"como hechos una vez que se haya transferido el dinero al cliente."
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -29309,8 +29337,11 @@ msgid "The new question has been created."
msgstr "La nueva pregunta ha sido creada."
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "Las cuotas seleccionadas se han eliminado o desactivado."
msgstr "Las fechas seleccionadas se han borrado o desactivado."
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -30042,9 +30073,11 @@ msgstr ""
"Este plugin no está permitido actualmente para su cuenta de organizador."
#: pretix/control/views/organizer.py:832
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
msgid "This plugin cannot be activated for event {}."
msgstr "Este complemento no se puede activar para el evento {}."
msgstr ""
"Este plugin se puede activar o desactivar para eventos de forma individual."
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -31089,9 +31122,10 @@ msgid "{width} x {height} mm label"
msgstr "etiqueta {width} x {height} mm"
#: pretix/plugins/badges/templates.py:265
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
msgid "{width} x {height} inch label"
msgstr "Etiqueta de {width} x {height} pulgadas"
msgstr "etiqueta {width} x {height} mm"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27
+82 -44
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-08 17:00+0000\n"
"Last-Translator: Sébastien BRUNEAU <s.bruneau@beauvaisis.fr>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
"fr/>\n"
"PO-Revision-Date: 2026-05-08 04:00+0000\n"
"Last-Translator: corentin-spec <corentin@spectentaculaire.fr>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 2026.6.1\n"
"X-Generator: Weblate 5.17.1\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -618,17 +618,16 @@ msgstr ""
"aux objets imbriqués tels que les variantes ou les lots."
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "Quota modifié"
msgstr "Traitement des quotas"
#: 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 ""
"Cela inclut les événements associés, tels que la création, la suppression, "
"l'ouverture ou la suppression de quotas. Aucun webhook n'est envoyé en cas "
"de modification de la disponibilité qui en résulte."
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -3423,13 +3422,11 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"Le champ « %(label)s » ne doit pas contenir de caractères spéciaux tels que "
"«%(chars)s »."
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr "Le champ « %(label)s » ne doit pas contenir d'URL (%(url)s)."
msgstr ""
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8412,14 +8409,19 @@ msgid "Program times"
msgstr "Horaires du programme"
#: 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 mai 2017, de 10 h à 12 h, salle 1\n"
"31 mai 2017, de 14 h à 16 h, salle 2\n"
"Du 31 mai 2017 à 1 h du matin au 1er juin 2017 à 14 h, bâtiment A"
"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"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8955,7 +8957,13 @@ msgid "This voucher code is not known in our database."
msgstr "Ce code promotionnel n'est pas connu dans notre base de données."
#: 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."
@@ -8963,14 +8971,22 @@ msgid_plural ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching products."
msgstr[0] ""
"Le code promo « %(voucher)s » ne peut être utilisé que si vous sélectionnez "
"Le code promo \"%(voucher)s\" ne peut être utilisé que si vous sélectionnez "
"au moins %(number)s produit correspondant."
msgstr[1] ""
"Le code promo « %(voucher)s » ne peut être utilisé que si vous sélectionnez "
"Le code promo \"%(voucher)s\" ne peut être utilisé que si vous sélectionnez "
"au moins %(number)s produits correspondants."
#: 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 "
@@ -14363,8 +14379,6 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"Vous avez saisi une URL, ce qui n'est pas autorisé. Veuillez supprimer %"
"(match)s de votre saisie."
#: pretix/base/views/errors.py:48
msgid ""
@@ -16314,8 +16328,14 @@ msgid "inactive"
msgstr "inactif"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr "Centre de conférences d'exemple, Heidelberg, Allemagne"
msgstr ""
"Exemple de centre de conférence\n"
"Centre des Congrès, France"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -23811,8 +23831,11 @@ msgid "Quota history"
msgstr "Historique des quotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "Modifier plusieurs quotas"
msgstr "Modifier plusieurs dates"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23860,15 +23883,18 @@ msgstr "Les produits suivants pourraient ne plus être disponibles à la vente
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "Supprimer les quotas"
msgstr "Supprimer le quota"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, python-format
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "Êtes-vous sûr de vouloir supprimer le quota suivant?"
msgstr[1] "Êtes-vous sûr de vouloir supprimer les %(num)s quotas suivants?"
msgstr[0] "Voulez-vous vraiment supprimer les dates suivantes ?"
msgstr[1] "Voulez-vous vraiment supprimer les dates suivantes ?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -24477,15 +24503,12 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"Cette commande a été modifiée après l'émission de la dernière facture. "
"Aucune nouvelle facture n'a encore été générée, car les factures sont "
"configurées pour être émises lors du paiement ou si le mode de paiement "
"l'exige. Une nouvelle facture sera générée dès que le client aura réglé la "
"facture ou choisi un mode de paiement nécessitant une facture."
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "Réémettre une facture"
msgstr "Demande de facture"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24919,17 +24942,25 @@ msgid "How should the refund be sent?"
msgstr "Comment le remboursement doit-il être envoyé ?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"Pour tous les paiements que vous avez sélectionnés pour un remboursement "
"automatique, la demande de remboursement sera immédiatement transmise au "
"prestataire de paiement concerné. Les remboursements manuels seront "
"enregistrés comme remboursements en attente; vous pourrez les marquer comme "
"effectués une fois que vous aurez effectivement reversé l'argent au client."
"Tous les paiements que vous avez sélectionnés pour des remboursements "
"automatiques seront immédiatement communiqués à la demande de remboursement "
"au fournisseur de paiement respectif. Les remboursements manuels seront "
"créés en tant que remboursements en attente, vous pourrez ensuite les "
"marquer comme terminés une fois que vous aurez effectivement transféré "
"largent au client."
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -29527,8 +29558,11 @@ msgid "The new question has been created."
msgstr "La nouvelle question a été créée."
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "Les quotas sélectionnés ont été supprimés ou désactivés."
msgstr "Les dates sélectionnées ont été supprimées ou désactivées."
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -30268,9 +30302,12 @@ msgstr ""
"Ce plugin n'est actuellement pas autorisé pour ce compte d'organisateur."
#: pretix/control/views/organizer.py:832
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
msgid "This plugin cannot be activated for event {}."
msgstr "Ce plugin ne peut pas être activé pour l'événement {}."
msgstr ""
"Ce plugin peut être activé ou désactivé individuellement pour chaque "
"événement."
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -31325,9 +31362,10 @@ msgid "{width} x {height} mm label"
msgstr "{width} x {height} mm étiquette"
#: pretix/plugins/badges/templates.py:265
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
msgid "{width} x {height} inch label"
msgstr "{width} x {height} pouce étiquette"
msgstr "{width} x {height} mm étiquette"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27
@@ -35123,7 +35161,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
+71 -34
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-01 09:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"PO-Revision-Date: 2026-05-12 06:34+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
"Language: ja\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 2026.5\n"
"X-Generator: Weblate 5.17.1\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -608,20 +608,20 @@ msgstr ""
"更を含みます。"
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "クォータが変更されました"
msgstr "クォータの処理"
#: 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 ""
"これには、クォータの作成、削除、開始または終了といった関連イベントが含まれま"
"す。結果として得られる可用性の変更については、Webhookが送信されません。"
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
msgstr "ショップがオンラインになりました"
msgstr "ショップが公開中になりました"
#: pretix/api/webhooks.py:423
msgid "Shop taken offline"
@@ -3394,13 +3394,11 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"フィールド「%(label)s」には、\"%(chars)s\" のような特殊文字を含めることはでき"
"ません。"
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr "フィールド「%(label)s」には URL (%(url)s) を含めることができません。"
msgstr ""
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8191,14 +8189,19 @@ msgid "Program times"
msgstr "プログラム時間"
#: 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 ""
"2017-05-31 10:00 12:00、部屋1\n"
"2017-05-31 14:00 16:00、部屋2\n"
"2017-05-31 14:00 2017-06-01 14:00、ビルA"
"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"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8707,7 +8710,13 @@ msgid "This voucher code is not known in our database."
msgstr "このバウチャーコードは、当社のデータベースには登録されていません。"
#: 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."
@@ -8719,7 +8728,15 @@ msgstr[0] ""
"した場合にのみ使用できます。"
#: 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 "
@@ -13820,8 +13837,6 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"URL を入力しましたが、許可されていません。入力から %(match)s を削除してくださ"
"い。"
#: pretix/base/views/errors.py:48
msgid ""
@@ -15718,8 +15733,14 @@ msgid "inactive"
msgstr "無効"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr "サンプル・カンファレンスセンター, ドイツ, ハイデルベルク"
msgstr ""
"サンプル・カンファレンスセンター\n"
"ドイツ、ハイデルベルク"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -22960,8 +22981,11 @@ msgid "Quota history"
msgstr "クォータ履歴"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "複数のクォータを変更"
msgstr "複数の日付を変更"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23007,14 +23031,17 @@ msgstr "以下の製品は販売できなくなる可能性があります:"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "クォータを削除"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, python-format
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "以下の%(num)sのクォータを削除してもよろしいですか?"
msgstr[0] "以下の日付を削除してもよろしいですか?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -23607,14 +23634,12 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"この注文は、最後の請求書が生成された後に変更されました。新しい請求書はまだ作"
"成されていません。請求書は支払い時に生成されるか、支払方法によって必要とされ"
"る場合に設定されているためです。お客様が請求書を支払うか、請求書が必要な支払"
"方法を選択すると、新しい請求書が生成されます。"
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "請求書を再発行する"
msgstr "請求書を要求"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24039,15 +24064,22 @@ msgid "How should the refund be sent?"
msgstr "どのように払い戻しますか?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"自動返金をご選択いただいたすべての支払いについては、返金リクエストが直ちに該"
"当する決済プロバイダーへ送信されます。手動返金は保留中の返金として作成され、"
"実際に顧客に返金した後で完了としてマークできます。"
"自動払い戻しに選択した支払いは、該当する決済プロバイダーに払い戻し要求が即座"
"に通知されます。手動払い戻しは保留中の払い戻しとして作成され、実際に顧客に送"
"金した後で完了済みとしてマークできます。"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -28472,8 +28504,11 @@ msgid "The new question has been created."
msgstr "新しい質問が作成されました。"
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "選択したクォータは削除されたか無効す。"
msgstr "選択した日付は削除されたか無効になっています。"
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -29180,9 +29215,10 @@ msgid "This plugin is currently not allowed for this organizer account."
msgstr "このプラグインは現在、この主催者アカウントでは許可されていません。"
#: pretix/control/views/organizer.py:832
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
msgid "This plugin cannot be activated for event {}."
msgstr "このプラグインは、イベント{}に対してアクティベートできません。"
msgstr "このプラグインは、イベントごとに個別に有効化または無効化できま。"
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -30200,9 +30236,10 @@ msgid "{width} x {height} mm label"
msgstr "{width} x {height} mm ラベル"
#: pretix/plugins/badges/templates.py:265
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
msgid "{width} x {height} inch label"
msgstr "{width} x {height} インチラベル"
msgstr "{width} x {height} mm ラベル"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27
+12 -8
View File
@@ -8,16 +8,16 @@ 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-01 09:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/"
"ko/>\n"
"PO-Revision-Date: 2026-02-01 21:00+0000\n"
"Last-Translator: z3rrry <z3rrry@gmail.com>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
">\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 2026.5\n"
"X-Generator: Weblate 5.15.2\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -48,7 +48,7 @@ msgstr "사전판매 시작하지 않음"
#: pretix/control/templates/pretixcontrol/subevents/index.html:176
#: pretix/control/views/dashboards.py:549
msgid "On sale"
msgstr "세일 중"
msgstr ""
#: pretix/_base_settings.py:89
msgid "English"
@@ -427,8 +427,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:495
#: pretix/control/views/organizer.py:1035
#, fuzzy
#| msgid "pretix account invitation"
msgid "Account invitation"
msgstr "계정 초대"
msgstr "프레틱스 계정 초대"
#: pretix/api/serializers/organizer.py:516
#: pretix/control/views/organizer.py:1134
@@ -18085,8 +18087,10 @@ msgid "A payment has been performed."
msgstr "수동 거래가 수행되었습니다."
#: pretix/control/logdisplay.py:807
#, fuzzy
#| msgid "A manual transaction has been performed."
msgid "A refund has been performed. "
msgstr "환불이 처리되었습니다. "
msgstr "수동 거래가 수행되었습니다."
#: pretix/control/logdisplay.py:808
#, python-brace-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
@@ -8,7 +8,7 @@ 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-01 09:00+0000\n"
"PO-Revision-Date: 2026-05-21 15:08+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Chinese (Traditional Han script) <https://translate.pretix.eu/"
"projects/pretix/pretix/zh_Hant/>\n"
@@ -595,16 +595,16 @@ msgid ""
msgstr "這包括新增或刪除的產品,以及對變體或捆綁等巢狀物件的更改。"
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "配額改變了"
msgstr "額度處理"
#: 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 ""
"這包括建立、刪除、開啟或關閉配額等相關事件。 沒有傳送webhook來更改結果的可用"
"性。"
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -650,7 +650,7 @@ msgstr "優惠券已更改"
msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions."
msgstr "僅包括對代金券的明確更改,例如不包括兌換次數的增加。"
msgstr ""
#: pretix/api/webhooks.py:460
msgid "Voucher deleted"
@@ -669,16 +669,22 @@ msgid "Customer account anonymized"
msgstr "客戶帳戶已匿名化"
#: pretix/api/webhooks.py:476
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card added"
msgstr "添加了禮品卡"
msgstr "禮品卡代碼"
#: pretix/api/webhooks.py:480
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card modified"
msgstr "禮品卡修改了"
msgstr "禮品卡代碼"
#: pretix/api/webhooks.py:484
#, fuzzy
#| msgid "Gift card transactions"
msgid "Gift card used in transaction"
msgstr "交易中使用的禮品卡"
msgstr "禮品卡交易"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:1074
@@ -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.
+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)
@@ -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"]:
@@ -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:
if e != last_group:
if last_group_head_idx > 0 and e 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.append(
[
PlainTextParagraph(
group_label,
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")
+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'),

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