mirror of
https://github.com/pretix/pretix.git
synced 2026-06-24 03:26:14 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91926cffd7 | |||
| d14dc4c5ff | |||
| 5db1a5b8af | |||
| 86a51afe9e | |||
| ea9c85a1b4 | |||
| 226ff9b044 | |||
| d8991d8138 | |||
| 83642ec9f3 | |||
| 9f0ce28ce4 | |||
| 28722fecbd | |||
| 10a5d4ac68 | |||
| ba36f6d2ce | |||
| 1e301da26c | |||
| d0307b9936 | |||
| c083ce904a | |||
| 694b915d89 | |||
| ea928ea7d4 | |||
| 36d49fbd77 | |||
| f0cb451c34 | |||
| 2c7fcd0599 | |||
| 034722fa39 | |||
| 5a6870fce1 | |||
| de28425993 | |||
| f3eb0d2dba | |||
| 0630e05d50 | |||
| f868507670 | |||
| 04032078c1 | |||
| 8af2714a04 | |||
| c4b9cc4143 | |||
| 8c132d8342 | |||
| 8e63fafc62 | |||
| 63ebe16fd3 | |||
| 775fdd1ccb | |||
| 784577d86f | |||
| 07d27e66d1 | |||
| b404316dfd | |||
| edf97a13cd | |||
| c384bc2e7a | |||
| f16034d0cc | |||
| 93469d33e5 | |||
| 329b118810 | |||
| 748054de56 | |||
| 721b179521 | |||
| d3151f978d | |||
| 3a25af6496 | |||
| 6f1512f200 | |||
| d555b23275 | |||
| 375c42dff5 | |||
| 21225e7753 | |||
| 759ced7268 | |||
| 5920419e6b | |||
| 7c00383b62 | |||
| 4361641857 | |||
| ac8f40353e | |||
| d648c83e4c | |||
| 5cd1775e1d | |||
| 15d4676f98 | |||
| dfd388ddeb | |||
| 8af010e5b7 | |||
| 4f4ea01bc0 | |||
| 9e4cbcb372 | |||
| 07d7264bc6 | |||
| f3b3eba8b3 | |||
| a75dbb5d62 | |||
| 254f46d991 | |||
| 59d15b0411 | |||
| f0778df911 | |||
| eb4d85c83d | |||
| 0fe00fed27 | |||
| 7b9d095f4e | |||
| 94aec6f511 | |||
| ed25a8b073 | |||
| c9eb936d45 | |||
| 7237ece1ca | |||
| 18485f5d95 | |||
| 909ce5b27d | |||
| c7b82fdc97 | |||
| da380ed75e | |||
| 687c7e3ccf | |||
| 484b7141d9 | |||
| f60031d67b | |||
| dd29063a84 | |||
| f37dfbd21a | |||
| bb8ef00d49 | |||
| d13c654596 | |||
| 2cc73baa99 | |||
| f740d46d47 | |||
| 412a5adf8f |
+3
-3
@@ -10,9 +10,9 @@ tests:
|
||||
- cd src
|
||||
- python manage.py check
|
||||
- make all compress
|
||||
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --maxfail=100
|
||||
- PRETIX_CONFIG_FILE=tests/ci_sqlite.cfg py.test -n 3 tests --ignore=tests/e2e --maxfail=100
|
||||
except:
|
||||
- pypi
|
||||
- '/^v.*$/'
|
||||
pypi:
|
||||
stage: release
|
||||
image:
|
||||
@@ -35,7 +35,7 @@ pypi:
|
||||
- twine check dist/*
|
||||
- twine upload dist/*
|
||||
only:
|
||||
- pypi
|
||||
- '/^v.*$/'
|
||||
artifacts:
|
||||
paths:
|
||||
- src/dist/
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
17
|
||||
24
|
||||
|
||||
+1
-2
@@ -57,8 +57,7 @@ COPY vite.config.ts /pretix/vite.config.ts
|
||||
|
||||
RUN pip3 install -U \
|
||||
pip \
|
||||
setuptools \
|
||||
wheel && \
|
||||
setuptools && \
|
||||
cd /pretix && \
|
||||
PRETIX_DOCKER_BUILD=TRUE pip3 install \
|
||||
-e ".[memcached]" \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -46,12 +46,14 @@ Checking a ticket in
|
||||
this request twice with the same nonce, the second request will also succeed but will always
|
||||
create only one check-in object even when the previous request was successful as well. This
|
||||
allows for a certain level of idempotency and enables you to re-try after a connection failure.
|
||||
:<json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
|
||||
:<json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
|
||||
:<json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
|
||||
order) when building texts (currently only the ``reason_explanation`` response field).
|
||||
Defaults to ``false`` in which case the server will determine the language (currently
|
||||
the event default language, might change in the future with support for the
|
||||
``Accept-Language`` header).
|
||||
:>json string status: ``"ok"``, ``"incomplete"``, or ``"error"``
|
||||
:>json string status: ``"ok"``, ``"incomplete"``, ``"exchange"``, or ``"error"``
|
||||
:>json string reason: Reason code, only set on status ``"error"``, see below for possible values.
|
||||
:>json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null.
|
||||
:>json object position: Copy of the matching order position (if any was found). The contents are the same as the
|
||||
@@ -67,6 +69,8 @@ Checking a ticket in
|
||||
:>json object list: Excerpt of information about the matching :ref:`check-in list <rest-checkinlists>` (if any was found),
|
||||
including the attributes ``id``, ``name``, ``event``, ``subevent``, and ``include_pending``.
|
||||
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
|
||||
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
|
||||
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -224,6 +228,9 @@ Checking a ticket in
|
||||
* ``ambiguous`` - Multiple tickets match scan, rejected.
|
||||
* ``revoked`` - Ticket code has been revoked.
|
||||
* ``unapproved`` - Order has not yet been approved.
|
||||
* ``already_exchanged`` - Ticket already has been exchanged for a reusable medium that must now be used for check-in.
|
||||
* ``medium_invalid`` - Reusable medium identifier given was not found or is not valid.
|
||||
* ``medium_exists`` - Reusable medium identifier already exists, but expected to be new.
|
||||
* ``error`` - Internal error.
|
||||
|
||||
In case of reason ``rules`` and ``invalid_time``, there might be an additional response field ``reason_explanation``
|
||||
|
||||
@@ -602,7 +602,8 @@ Order position endpoints
|
||||
|
||||
We no longer recommend using this API if you're building a ticket scanning application, as it has a few design
|
||||
flaws that can lead to `security issues`_ or compatibility issues due to barcode content characters that are not
|
||||
URL-safe. We recommend to use our new :ref:`check-in API <rest-checkin>` instead.
|
||||
URL-safe. We recommend to use our new :ref:`check-in API <rest-checkin>` instead. Advanced features like medium
|
||||
exchange are only supported on the new API.
|
||||
|
||||
:query boolean untrusted_input: If set to true, the lookup parameter is **always** interpreted as a ``secret``, never
|
||||
as an ``id``. This should be always set if you are passing through untrusted, scanned
|
||||
@@ -741,6 +742,9 @@ Order position endpoints
|
||||
* ``ambiguous`` - Multiple tickets match scan, rejected.
|
||||
* ``revoked`` - Ticket code has been revoked.
|
||||
* ``unapproved`` - Order has not yet been approved.
|
||||
* ``already_exchanged`` - Ticket already has been exchanged for a reusable medium that must now be used for check-in.
|
||||
* ``medium_invalid`` - Reusable medium identifier given was not found and could not be automatically created.
|
||||
* ``medium_exists`` - Reusable medium identifier already exists, but expected to be new.
|
||||
|
||||
In case of reason ``rules`` or ``invalid_time``, there might be an additional response field ``reason_explanation``
|
||||
with a human-readable description of the violated rules. However, that field can also be missing or be ``null``.
|
||||
|
||||
@@ -131,7 +131,7 @@ allow_waitinglist boolean If ``false``,
|
||||
product when it is sold out.
|
||||
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
||||
media_policy string Policy on how to handle reusable media (experimental feature).
|
||||
Possible values are ``null``, ``"new"``, ``"reuse"``, and ``"reuse_or_new"``.
|
||||
Possible values are ``null``, ``"new"``, ``"reuse"``, ``"reuse_or_new"``, ``"append"``, and ``"append_or_new"``.
|
||||
media_type string Type of reusable media to work on (experimental feature). See :ref:`rest-reusablemedia` for possible choices.
|
||||
show_quota_left boolean Publicly show how many tickets are still available.
|
||||
If this is ``null``, the event default is used.
|
||||
|
||||
@@ -1069,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 take over the given reusable medium, identified by its ID)
|
||||
* ``use_reusable_medium`` (optional, causes the new ticket to be connected to the given reusable medium, identified by its ID)
|
||||
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
|
||||
* ``answers``
|
||||
|
||||
|
||||
@@ -21,12 +21,16 @@ 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_orderposition integer Internal ID of a ticket this medium is linked 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_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.
|
||||
@@ -39,6 +43,14 @@ 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
|
||||
---------
|
||||
|
||||
@@ -77,6 +89,7 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
@@ -92,10 +105,13 @@ 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_orderposition"``,
|
||||
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_orderpositions"``,
|
||||
``"linked_orderposition"`` (**DEPRECATED**), 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.
|
||||
@@ -134,6 +150,7 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
@@ -191,6 +208,7 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
@@ -198,9 +216,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_orderposition"``, oder ``"customer"``, the respective
|
||||
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, 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 the ``linked_orderposition`` will have an attribute of the
|
||||
the respective resources, except that the ``linked_orderpositions`` each 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
|
||||
@@ -227,6 +245,7 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
@@ -251,6 +270,7 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderpositions": [],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
@@ -258,7 +278,7 @@ Endpoints
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to create a medium for
|
||||
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderposition"``, oder ``"customer"``, the respective
|
||||
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, 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 the ``linked_orderposition`` will have an attribute of the
|
||||
format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make matching easier. The parameter
|
||||
@@ -287,7 +307,7 @@ Endpoints
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"linked_orderposition": 13
|
||||
"linked_orderpositions": [13, 29]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -308,7 +328,8 @@ Endpoints
|
||||
"active": True,
|
||||
"expires": None,
|
||||
"customer": None,
|
||||
"linked_orderposition": 13,
|
||||
"linked_orderpositions": [13, 29],
|
||||
"linked_orderposition": None,
|
||||
"linked_giftcard": None,
|
||||
"notes": None,
|
||||
"info": {}
|
||||
@@ -316,7 +337,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_orderposition"``, oder ``"customer"``, the respective
|
||||
:query string expand: If you pass ``"linked_giftcard"``, ``"linked_orderpositions"``, 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 the ``linked_orderposition`` will have an attribute of the
|
||||
format ``"order": {"code": "ABCDE", "event": "eventslug"}`` to make matching easier. The parameter
|
||||
|
||||
@@ -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, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms,
|
||||
item_formsets, order_search_filter_q, order_search_forms
|
||||
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
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:no-index:
|
||||
|
||||
@@ -81,7 +81,7 @@ is a python method that emulates a behavior similar to ``reverse``:
|
||||
|
||||
If you need to communicate the URL externally, you can use a different method to ensure that it is always an absolute URL:
|
||||
|
||||
.. autofunction:: pretix.multidomain.urlreverse.build_absolute_uri
|
||||
.. autofunction:: pretix.multidomain.urlreverse.eventreverse_absolute
|
||||
|
||||
In addition, there is a template tag that works similar to ``url`` but takes an event or organizer object
|
||||
as its first argument and can be used like this::
|
||||
|
||||
Generated
+109
-91
@@ -370,14 +370,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
|
||||
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
"@tybys/wasm-util": "^0.10.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -427,9 +427,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.129.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
|
||||
"integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -758,9 +758,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -775,9 +775,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -792,9 +792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -809,9 +809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -826,9 +826,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
|
||||
"integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -843,13 +843,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -860,13 +863,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
|
||||
"integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -877,13 +883,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -894,13 +903,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -911,13 +923,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
|
||||
"integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -928,13 +943,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
|
||||
"integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -945,9 +963,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -962,9 +980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
|
||||
"integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -981,9 +999,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
|
||||
"integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -998,9 +1016,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
|
||||
"integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3158,9 +3176,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3334,9 +3352,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -3353,7 +3371,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@@ -3610,14 +3628,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
|
||||
"integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.129.0",
|
||||
"@rolldown/pluginutils": "1.0.0"
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -3626,27 +3644,27 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0"
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
|
||||
"integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4325,9 +4343,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4465,17 +4483,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
|
||||
"integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.14",
|
||||
"rolldown": "1.0.0",
|
||||
"tinyglobby": "^0.2.16"
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
||||
+12
-14
@@ -29,18 +29,18 @@ classifiers = [
|
||||
dependencies = [
|
||||
"arabic-reshaper==3.0.1", # Support for Arabic in reportlab
|
||||
"babel",
|
||||
"BeautifulSoup4==4.14.*",
|
||||
"bleach==6.3.*",
|
||||
"BeautifulSoup4==4.15.*",
|
||||
"bleach==6.4.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=48.0.0",
|
||||
"cryptography>=49.0.0",
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=3.0.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==5.2.*",
|
||||
"django-bootstrap3==26.1",
|
||||
"django-compressor==4.6.0",
|
||||
"django-countries==8.2.*",
|
||||
"django-countries==9.0.*",
|
||||
"django-filter==25.1",
|
||||
"django-formset-js-improved==0.5.0.5",
|
||||
"django-formtools==2.6.1",
|
||||
@@ -74,11 +74,11 @@ dependencies = [
|
||||
"packaging",
|
||||
"paypalrestsdk==1.13.*",
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.12.*",
|
||||
"PyJWT==2.13.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==12.2.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==7.34.*",
|
||||
"protobuf==7.35.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==3.0",
|
||||
@@ -93,7 +93,7 @@ dependencies = [
|
||||
"redis==7.4.*",
|
||||
"reportlab==4.5.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.60.*",
|
||||
"sentry-sdk==2.62.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
@@ -101,23 +101,23 @@ dependencies = [
|
||||
"tqdm==4.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.7.*",
|
||||
"webauthn==2.8.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
memcached = ["pylibmc"]
|
||||
dev = [
|
||||
"aiohttp==3.13.*",
|
||||
"aiohttp==3.14.*",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"fakeredis==2.35.*",
|
||||
"fakeredis==2.36.*",
|
||||
"flake8==7.3.*",
|
||||
"freezegun",
|
||||
"isort==8.0.*",
|
||||
"pep8-naming==0.15.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
"pytest-asyncio>=1.4.0",
|
||||
"pytest-cache",
|
||||
"pytest-cov",
|
||||
"pytest-django==4.*",
|
||||
@@ -125,7 +125,7 @@ dev = [
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.8.*",
|
||||
"pytest-playwright",
|
||||
"pytest==9.0.*",
|
||||
"pytest==9.1.*",
|
||||
"playwright",
|
||||
"responses",
|
||||
]
|
||||
@@ -139,8 +139,6 @@ build-backend = "backend"
|
||||
backend-path = ["_build"]
|
||||
requires = [
|
||||
"setuptools",
|
||||
"setuptools-rust",
|
||||
"wheel",
|
||||
"importlib_metadata",
|
||||
"tomli",
|
||||
]
|
||||
|
||||
@@ -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.5.0.dev0"
|
||||
__version__ = "2026.6.0.dev0"
|
||||
|
||||
@@ -110,6 +110,8 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
('POST', 'api-v1:checkinrpc.redeem'),
|
||||
('GET', 'api-v1:checkinrpc.search'),
|
||||
('GET', 'api-v1:reusablemedium-list'),
|
||||
('POST', 'api-v1:reusablemedium-lookup'),
|
||||
('PATCH', 'api-v1:reusablemedium-detail')
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -88,11 +88,19 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer):
|
||||
nonce = serializers.CharField(required=False, allow_null=True)
|
||||
datetime = serializers.DateTimeField(required=False, allow_null=True)
|
||||
answers = serializers.JSONField(required=False, allow_null=True)
|
||||
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
|
||||
exchange_medium_identifier = serializers.CharField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
||||
|
||||
def validate(self, attrs):
|
||||
exchange_fields = ["exchange_medium_type", "exchange_medium_identifier"]
|
||||
if any(attrs.get(k) is None for k in exchange_fields) and not all(attrs.get(k) is None for k in exchange_fields):
|
||||
raise ValidationError("If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.")
|
||||
return attrs
|
||||
|
||||
|
||||
class MiniCheckinListSerializer(I18nAwareModelSerializer):
|
||||
event = serializers.SlugRelatedField(slug_field='slug', read_only=True)
|
||||
|
||||
@@ -73,7 +73,7 @@ from pretix.base.settings import (
|
||||
LazyI18nStringList, validate_event_settings,
|
||||
)
|
||||
from pretix.base.signals import api_event_settings_fields
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -173,7 +173,7 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
)
|
||||
|
||||
def get_event_url(self, event):
|
||||
return build_absolute_uri(event, 'presale:event.index')
|
||||
return eventreverse_absolute(event, 'presale:event.index')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@@ -871,6 +871,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'og_image',
|
||||
'name_scheme',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -885,6 +886,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
readonly_fields = [
|
||||
# These are read-only since they are currently only settable on organizers, not events
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -970,6 +972,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'reusable_media_type_nfc_uid',
|
||||
'reusable_media_type_nfc_mf0aes',
|
||||
'reusable_media_type_nfc_mf0aes_random_uid',
|
||||
'reusable_media_usage_enforced',
|
||||
'system_question_order',
|
||||
'tax_rule_payment',
|
||||
'tax_rule_cancellation',
|
||||
|
||||
@@ -66,13 +66,14 @@ 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 self.context['request'].query_params.getlist('expand'):
|
||||
if 'linked_giftcard' in expand_nested:
|
||||
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 self.context['request'].query_params.getlist('expand'):
|
||||
if 'linked_giftcard.owner_ticket' in expand_nested:
|
||||
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
|
||||
else:
|
||||
self.fields['linked_giftcard'] = serializers.PrimaryKeyRelatedField(
|
||||
@@ -81,17 +82,27 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
queryset=self.context['organizer'].issued_gift_cards.all()
|
||||
)
|
||||
|
||||
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)
|
||||
# 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
|
||||
)
|
||||
else:
|
||||
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
|
||||
self.fields['linked_orderpositions'] = serializers.PrimaryKeyRelatedField(
|
||||
many=True,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
queryset=OrderPosition.all.filter(order__event__organizer=self.context['organizer']),
|
||||
)
|
||||
|
||||
if 'customer' in self.context['request'].query_params.getlist('expand'):
|
||||
if 'customer' in expand_nested:
|
||||
if not self.context["can_read_customers"]:
|
||||
raise PermissionDenied("No permission to access customer details.")
|
||||
|
||||
@@ -106,6 +117,21 @@ 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']
|
||||
@@ -121,14 +147,28 @@ 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 'linked_orderposition' in expand_nested:
|
||||
if instance.linked_orderposition is not None:
|
||||
event = instance.linked_orderposition.order.event
|
||||
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 not perm_holder.has_event_permission(event.organizer, event, 'event.orders:read', request):
|
||||
r['linked_orderposition'] = {'id': instance.linked_orderposition.id}
|
||||
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
|
||||
|
||||
if 'linked_giftcard.owner_ticket' in expand_nested:
|
||||
gc = instance.linked_giftcard
|
||||
@@ -148,10 +188,12 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
||||
'updated',
|
||||
'type',
|
||||
'identifier',
|
||||
'claim_token',
|
||||
'label',
|
||||
'active',
|
||||
'expires',
|
||||
'customer',
|
||||
'linked_orderposition',
|
||||
'linked_orderpositions',
|
||||
'linked_giftcard',
|
||||
'info',
|
||||
'notes',
|
||||
|
||||
@@ -76,7 +76,7 @@ from pretix.base.settings import (
|
||||
)
|
||||
from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -757,7 +757,7 @@ class PaymentURLField(serializers.URLField):
|
||||
def to_representation(self, instance: OrderPayment):
|
||||
if instance.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||
return None
|
||||
return build_absolute_uri(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||
return eventreverse_absolute(instance.order.event, 'presale:event.order.pay', kwargs={
|
||||
'order': instance.order.code,
|
||||
'secret': instance.order.secret,
|
||||
'payment': instance.pk,
|
||||
@@ -806,7 +806,7 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class OrderURLField(serializers.URLField):
|
||||
def to_representation(self, instance: Order):
|
||||
return build_absolute_uri(instance.event, 'presale:event.order', kwargs={
|
||||
return eventreverse_absolute(instance.event, 'presale:event.order', kwargs={
|
||||
'order': instance.code,
|
||||
'secret': instance.secret,
|
||||
})
|
||||
@@ -1149,6 +1149,7 @@ 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
|
||||
|
||||
|
||||
@@ -1588,7 +1589,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
pos_data['attendee_name_parts'] = {
|
||||
'_legacy': attendee_name
|
||||
}
|
||||
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k != 'answers' and k != '_quotas' and k != 'use_reusable_medium'})
|
||||
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium')})
|
||||
if simulate:
|
||||
pos.order = order._wrapped
|
||||
else:
|
||||
@@ -1703,15 +1704,25 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
answ.options.add(*options)
|
||||
|
||||
if use_reusable_medium:
|
||||
use_reusable_medium.linked_orderposition = pos
|
||||
use_reusable_medium.save(update_fields=['linked_orderposition'])
|
||||
if pos.item.media_policy not in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
for op_pk in use_reusable_medium.linked_orderpositions.values_list('pk', flat=True):
|
||||
use_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.removed',
|
||||
data={
|
||||
'linked_orderposition': op_pk,
|
||||
}
|
||||
)
|
||||
use_reusable_medium.linked_orderpositions.set([pos])
|
||||
else:
|
||||
use_reusable_medium.linked_orderpositions.add(pos)
|
||||
use_reusable_medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.changed',
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
data={
|
||||
'by_order': order.code,
|
||||
'linked_orderposition': pos.pk,
|
||||
}
|
||||
)
|
||||
use_reusable_medium.touch()
|
||||
|
||||
if not simulate:
|
||||
for cp in delete_cps:
|
||||
|
||||
@@ -58,8 +58,8 @@ from pretix.helpers.permission_migration import (
|
||||
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_EVENT_MIGRATION,
|
||||
OLD_TO_NEW_ORGANIZER_COMPAT, OLD_TO_NEW_ORGANIZER_MIGRATION,
|
||||
)
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -71,7 +71,7 @@ class OrganizerSerializer(I18nAwareModelSerializer):
|
||||
slug = serializers.CharField(read_only=True)
|
||||
|
||||
def get_organizer_url(self, organizer):
|
||||
return build_absolute_uri(organizer, 'presale:organizer.index')
|
||||
return eventreverse_absolute(organizer, 'presale:organizer.index')
|
||||
|
||||
class Meta:
|
||||
model = Organizer
|
||||
@@ -499,7 +499,7 @@ class TeamInviteSerializer(serializers.ModelSerializer):
|
||||
'user': self,
|
||||
'organizer': self.context['organizer'].name,
|
||||
'team': instance.team.name,
|
||||
'url': build_global_uri('control:auth.invite', kwargs={
|
||||
'url': mainreverse_absolute('control:auth.invite', kwargs={
|
||||
'token': instance.token
|
||||
})
|
||||
},
|
||||
@@ -605,6 +605,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
|
||||
'cookie_consent_dialog_button_yes',
|
||||
'cookie_consent_dialog_button_no',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
|
||||
+138
-25
@@ -69,8 +69,10 @@ from pretix.base.models import (
|
||||
from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.permissions import AnyPermissionOf
|
||||
from pretix.base.services.checkin import (
|
||||
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
|
||||
CheckInError, RequiredMediaExchangeError, RequiredQuestionsError, SQLLogic,
|
||||
perform_checkin,
|
||||
)
|
||||
from pretix.base.services.media import perform_media_exchange
|
||||
from pretix.base.signals import checkin_annulled
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
@@ -454,7 +456,8 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
||||
|
||||
def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, checkin_type, ignore_unpaid, nonce,
|
||||
untrusted_input, user, auth, expand, pdf_data, request, questions_supported, canceled_supported,
|
||||
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False):
|
||||
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False,
|
||||
exchange_medium_type=None, exchange_medium_identifier=None):
|
||||
if not checkinlists:
|
||||
raise ValidationError('No check-in list passed.')
|
||||
|
||||
@@ -463,6 +466,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
|
||||
device = auth if isinstance(auth, Device) else None
|
||||
gate = gate or (auth.gate if isinstance(auth, Device) else None)
|
||||
medium = None
|
||||
|
||||
context = {
|
||||
'request': request,
|
||||
@@ -491,6 +495,7 @@ 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
|
||||
|
||||
@@ -521,11 +526,12 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
# with respecting the force option), or it's a reusable medium (-> proceed with that)
|
||||
if not op_candidates:
|
||||
try:
|
||||
media = ReusableMedium.objects.select_related('linked_orderposition').active().get(
|
||||
medium = ReusableMedium.objects.active().filter(
|
||||
Exists(ReusableMedium.linked_orderpositions.through.objects.filter(reusablemedium_id=OuterRef('pk')))
|
||||
).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:
|
||||
@@ -628,7 +634,9 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
'list': MiniCheckinListSerializer(list_by_event[revoked_matches[0].event_id]).data,
|
||||
}, status=400)
|
||||
else:
|
||||
if media.linked_orderposition.order.event_id not in list_by_event:
|
||||
linked_ops = medium.linked_orderpositions.all().select_related("order").prefetch_related("addons")
|
||||
linked_event_ids = {op.order.event_id for op in linked_ops}
|
||||
if not any(event_id in list_by_event for event_id in linked_event_ids):
|
||||
# Medium exists but connected ticket is for the wrong event
|
||||
if not simulate:
|
||||
checkinlists[0].event.log_action('pretix.event.checkin.unknown', data={
|
||||
@@ -654,28 +662,91 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
'checkin_texts': [],
|
||||
'list': MiniCheckinListSerializer(checkinlists[0]).data,
|
||||
}, status=404)
|
||||
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())
|
||||
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())
|
||||
|
||||
# 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 the ``addon_match`` case here and need to figure out
|
||||
# which add-on has the right product.
|
||||
# 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.
|
||||
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 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
|
||||
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
|
||||
# instead we just continue with *any* product and have it rejected by the check in perform_checkin.
|
||||
# This has the advantage of a better error message.
|
||||
op_candidates = [op_candidates[0]]
|
||||
elif len(op_candidates_matching_product) > 1:
|
||||
# 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:
|
||||
# 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.
|
||||
@@ -709,7 +780,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_matching_product
|
||||
op_candidates = op_candidates_filtered
|
||||
|
||||
op = op_candidates[0]
|
||||
common_checkin_args['list'] = list_by_event[op.order.event_id]
|
||||
@@ -721,7 +792,10 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
if str(q.pk) in answers_data:
|
||||
try:
|
||||
if q.type == Question.TYPE_FILE:
|
||||
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
|
||||
if answers_data[str(q.pk)]:
|
||||
given_answers[q] = _handle_file_upload(answers_data[str(q.pk)], user, auth)
|
||||
else:
|
||||
given_answers[q] = None
|
||||
else:
|
||||
given_answers[q] = q.clean_answer(answers_data[str(q.pk)])
|
||||
except (ValidationError, BaseValidationError):
|
||||
@@ -734,7 +808,14 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
locale = op.order.event.settings.locale
|
||||
with language(locale):
|
||||
try:
|
||||
perform_checkin(
|
||||
if exchange_medium_identifier and medium:
|
||||
# Cannot scan a medium and then request to exchange it
|
||||
raise CheckInError(
|
||||
gettext('You cannot exchange a medium for a medium.'),
|
||||
'error'
|
||||
)
|
||||
|
||||
checkin_args = dict(
|
||||
op=op,
|
||||
clist=list_by_event[op.order.event_id],
|
||||
given_answers=given_answers,
|
||||
@@ -752,7 +833,25 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
from_revoked_secret=from_revoked_secret,
|
||||
simulate=simulate,
|
||||
gate=gate,
|
||||
reusable_medium=medium,
|
||||
)
|
||||
|
||||
if exchange_medium_identifier: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
|
||||
with transaction.atomic():
|
||||
# Do exchange and check-in atomically, i.e. both succeed or both fail
|
||||
medium = perform_media_exchange(
|
||||
organizer=request.organizer,
|
||||
media_type=exchange_medium_type,
|
||||
identifier=exchange_medium_identifier,
|
||||
link_orderposition=op,
|
||||
user=user,
|
||||
auth=auth,
|
||||
)
|
||||
source_type = medium.media_type.identifier
|
||||
checkin_args['reusable_medium'] = medium
|
||||
perform_checkin(**checkin_args)
|
||||
else:
|
||||
perform_checkin(**checkin_args)
|
||||
except RequiredQuestionsError as e:
|
||||
return Response({
|
||||
'status': 'incomplete',
|
||||
@@ -764,6 +863,18 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
],
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
}, status=400)
|
||||
except RequiredMediaExchangeError as e:
|
||||
return Response({
|
||||
'status': 'exchange',
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'checkin_texts': op.checkin_texts,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'media_policy': e.media_policy,
|
||||
'media_type': e.media_type,
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
'reason': e.code,
|
||||
'reason_explanation': e.msg,
|
||||
}, status=400)
|
||||
except CheckInError as e:
|
||||
if not simulate:
|
||||
op.order.log_action('pretix.event.checkin.denied', data={
|
||||
@@ -951,6 +1062,8 @@ class CheckinRPCRedeemView(views.APIView):
|
||||
canceled_supported=True,
|
||||
request=self.request, # this is not clean, but we need it in the serializers for URL generation
|
||||
legacy_url_support=False,
|
||||
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
|
||||
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -53,10 +53,12 @@ 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_orderposition', 'linked_giftcard']
|
||||
fields = ['identifier', 'type', 'active', 'customer', 'linked_orderpositions', 'linked_giftcard']
|
||||
|
||||
|
||||
class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||
@@ -75,7 +77,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_orderposition',
|
||||
'linked_orderpositions',
|
||||
queryset=OrderPosition.objects.select_related(
|
||||
'order', 'order__event', 'order__event__organizer', 'seat',
|
||||
).prefetch_related(
|
||||
@@ -117,14 +119,38 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
ReusableMedium.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
|
||||
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))
|
||||
inst = serializer.save(identifier=serializer.instance.identifier, type=serializer.instance.type)
|
||||
inst.log_action(
|
||||
'pretix.reusable_medium.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
)
|
||||
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,
|
||||
)
|
||||
return inst
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
@@ -157,7 +183,6 @@ 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})
|
||||
@@ -171,7 +196,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||
|
||||
return Response({"result": None})
|
||||
|
||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some perforamnce
|
||||
@scopes_disabled() # we are sure enough that get_queryset() is correct, so we save some performance
|
||||
def list(self, request, **kwargs):
|
||||
date = serializers.DateTimeField().to_representation(now())
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
@@ -194,7 +194,7 @@ with scopes_disabled():
|
||||
)
|
||||
).values('id')
|
||||
|
||||
matching_media = ReusableMedium.objects.filter(identifier=u).values_list('linked_orderposition__order_id', flat=True)
|
||||
matching_media = ReusableMedium.objects.filter(identifier=u).values_list('linked_orderpositions__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_orderposition', flat=True)
|
||||
matching_media = ReusableMedium.objects.filter(identifier=value).values_list('linked_orderpositions', flat=True)
|
||||
return queryset.filter(
|
||||
Q(secret__istartswith=value)
|
||||
| Q(attendee_name_cached__icontains=value)
|
||||
|
||||
@@ -36,7 +36,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from requests import RequestException
|
||||
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -313,7 +313,7 @@ def _get_or_create_server_keypair(organizer):
|
||||
|
||||
def generate_id_token(customer, client, auth_time, nonce, scope, expires: datetime, scope_claims=False, with_code=None, with_access_token=None):
|
||||
payload = {
|
||||
'iss': build_absolute_uri(client.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'iss': eventreverse_absolute(client.organizer, 'presale:organizer.index').rstrip('/'),
|
||||
'aud': client.client_id,
|
||||
'exp': int(expires.timestamp()),
|
||||
'iat': int(time.time()),
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import Checkin, InvoiceAddress, Order, Question
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
def get_answer(op, question_identifier=None):
|
||||
@@ -545,7 +545,7 @@ def get_data_fields(event, for_model=None):
|
||||
_("Order link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: build_absolute_uri(
|
||||
lambda order: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
@@ -560,7 +560,7 @@ def get_data_fields(event, for_model=None):
|
||||
_("Ticket link"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda op: build_absolute_uri(
|
||||
lambda op: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': op.order.code,
|
||||
|
||||
@@ -68,7 +68,7 @@ from ...control.forms.filter import get_all_payment_providers
|
||||
from ...helpers import GroupConcat
|
||||
from ...helpers.iter import chunked_iterable
|
||||
from ...helpers.safe_openpyxl import remove_invalid_excel_chars
|
||||
from ...multidomain.urlreverse import build_absolute_uri
|
||||
from ...multidomain.urlreverse import eventreverse_absolute
|
||||
from ..exporter import (
|
||||
ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin,
|
||||
)
|
||||
@@ -429,7 +429,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
]))
|
||||
|
||||
row.append(
|
||||
build_absolute_uri(order.event, 'presale:event.order', kwargs={
|
||||
eventreverse_absolute(order.event, 'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
})
|
||||
@@ -855,7 +855,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
]))
|
||||
|
||||
row.append(
|
||||
build_absolute_uri(order.event, 'presale:event.order.position', kwargs={
|
||||
eventreverse_absolute(order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': op.web_secret,
|
||||
'position': op.positionid
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
# <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 ReusableMedium
|
||||
from ..models import OrderPosition, ReusableMedium
|
||||
from ..signals import register_multievent_data_exporters
|
||||
|
||||
|
||||
@@ -44,7 +45,9 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
media = ReusableMedium.objects.filter(
|
||||
organizer=self.organizer,
|
||||
).select_related(
|
||||
'customer', 'linked_orderposition', 'linked_giftcard',
|
||||
'customer', 'linked_giftcard',
|
||||
).prefetch_related(
|
||||
Prefetch('linked_orderpositions', queryset=OrderPosition.objects.select_related("order"))
|
||||
).order_by('created')
|
||||
|
||||
headers = [
|
||||
@@ -61,18 +64,23 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
yield headers
|
||||
yield self.ProgressSetTotal(total=media.count())
|
||||
|
||||
can_read_giftcards = self.permission_holder.has_organizer_permission(self.organizer, 'organizer.giftcards:read')
|
||||
|
||||
for medium in media.iterator(chunk_size=1000):
|
||||
row = [
|
||||
giftcard_secret = medium.linked_giftcard.secret if medium.linked_giftcard_id else ''
|
||||
if giftcard_secret and not can_read_giftcards:
|
||||
giftcard_secret = giftcard_secret[:3] + "…"
|
||||
|
||||
yield [
|
||||
medium.type,
|
||||
medium.identifier,
|
||||
_('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 '',
|
||||
f"{medium.linked_orderposition.order.code}-{medium.linked_orderposition.positionid}" if medium.linked_orderposition_id else '',
|
||||
medium.linked_giftcard.secret if medium.linked_giftcard_id else '',
|
||||
', '.join([f"{op.order.code}-{op.positionid}" for op in medium.linked_orderpositions.all()]),
|
||||
giftcard_secret,
|
||||
medium.notes,
|
||||
]
|
||||
yield row
|
||||
|
||||
def get_filename(self):
|
||||
return f'{self.organizer.slug}_media'
|
||||
|
||||
@@ -44,7 +44,8 @@ class Command(Parent):
|
||||
# Start the vite server in the background
|
||||
vite_server = subprocess.Popen(
|
||||
["npm", "run", "dev:control"],
|
||||
cwd=Path(__file__).parent.parent.parent.parent.parent
|
||||
cwd=Path(__file__).parent.parent.parent.parent.parent,
|
||||
stdin=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
def cleanup():
|
||||
|
||||
+20
-14
@@ -26,6 +26,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class BaseMediaType:
|
||||
medium_created_by_server = False
|
||||
medium_created_from_unknown_supported = False
|
||||
supports_orderposition = False
|
||||
supports_giftcard = False
|
||||
|
||||
@@ -56,7 +57,7 @@ class BaseMediaType:
|
||||
def is_active(self, organizer):
|
||||
return organizer.settings.get(f'reusable_media_type_{self.identifier}', as_type=bool, default=False)
|
||||
|
||||
def handle_unknown(self, organizer, identifier, user, auth):
|
||||
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||
pass
|
||||
|
||||
def handle_new(self, organizer, medium, user, auth):
|
||||
@@ -88,23 +89,32 @@ class NfcUidMediaType(BaseMediaType):
|
||||
verbose_name = _('NFC UID-based')
|
||||
icon = 'pretixbase/img/media/nfc_uid.svg'
|
||||
medium_created_by_server = False
|
||||
medium_created_from_unknown_supported = True
|
||||
supports_giftcard = True
|
||||
supports_orderposition = False
|
||||
supports_orderposition = True
|
||||
|
||||
def handle_unknown(self, organizer, identifier, user, auth):
|
||||
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||
from pretix.base.models import GiftCard, ReusableMedium
|
||||
|
||||
if organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool):
|
||||
create_giftcard = organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool)
|
||||
if create_giftcard or force_create:
|
||||
if identifier.startswith("08"):
|
||||
# Don't create gift cards for NFC UIDs that start with 08, which represents NFC cards that issue random
|
||||
# UIDs on every read, so they won't be useful.
|
||||
return
|
||||
with transaction.atomic():
|
||||
gc = GiftCard.objects.create(
|
||||
issuer=organizer,
|
||||
expires=organizer.default_gift_card_expiry,
|
||||
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
||||
)
|
||||
if create_giftcard:
|
||||
gc = GiftCard.objects.create(
|
||||
issuer=organizer,
|
||||
expires=organizer.default_gift_card_expiry,
|
||||
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
||||
)
|
||||
gc.log_action(
|
||||
'pretix.giftcards.created',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
else:
|
||||
gc = None
|
||||
m = ReusableMedium.objects.create(
|
||||
type=self.identifier,
|
||||
identifier=identifier,
|
||||
@@ -116,10 +126,6 @@ class NfcUidMediaType(BaseMediaType):
|
||||
'pretix.reusable_medium.created.auto',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
gc.log_action(
|
||||
'pretix.giftcards.created',
|
||||
user=user, auth=auth,
|
||||
)
|
||||
return m
|
||||
|
||||
|
||||
@@ -129,7 +135,7 @@ class NfcMf0aesMediaType(BaseMediaType):
|
||||
icon = 'pretixbase/img/media/nfc_secure.svg'
|
||||
medium_created_by_server = False
|
||||
supports_giftcard = True
|
||||
supports_orderposition = False
|
||||
supports_orderposition = True
|
||||
|
||||
def handle_new(self, organizer, medium, user, auth):
|
||||
from pretix.base.models import GiftCard
|
||||
|
||||
@@ -282,10 +282,12 @@ def metric_values():
|
||||
|
||||
# Throwaway metrics
|
||||
exact_tables = [
|
||||
Order, OrderPosition, Invoice, Event, Organizer
|
||||
Order, Invoice, Event, Organizer
|
||||
]
|
||||
for m in apps.get_models(): # Count all models
|
||||
if any(issubclass(m, p) for p in exact_tables):
|
||||
if issubclass(m, OrderPosition):
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = m.all.count()
|
||||
elif any(issubclass(m, p) for p in exact_tables):
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = m.objects.count()
|
||||
else:
|
||||
metrics['pretix_model_instances']['{model="%s"}' % m._meta] = estimate_count_fast(m)
|
||||
|
||||
@@ -74,6 +74,7 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
|
||||
def process_request(self, request: HttpRequest):
|
||||
language = get_language_from_request(request)
|
||||
region = None
|
||||
# Normally, this middleware runs *before* the event is set. However, on event frontend pages it
|
||||
# might be run a second time by pretix.presale.EventMiddleware and in this case the event is already
|
||||
# set and can be taken into account for the decision.
|
||||
@@ -94,15 +95,16 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
if '-' not in language and settings_holder.settings.region:
|
||||
language += '-' + settings_holder.settings.region
|
||||
if settings_holder.settings.region:
|
||||
set_region(settings_holder.settings.region)
|
||||
region = settings_holder.settings.region
|
||||
else:
|
||||
gs = global_settings_object(request)
|
||||
if '-' not in language and gs.settings.region:
|
||||
language += '-' + gs.settings.region
|
||||
if gs.settings.region:
|
||||
set_region(gs.settings.region)
|
||||
region = gs.settings.region
|
||||
|
||||
translation.activate(language)
|
||||
set_region(region)
|
||||
request.LANGUAGE_CODE = get_language_without_region()
|
||||
|
||||
tzname = None
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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;",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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"
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -57,7 +57,7 @@ from django_otp.models import Device
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
from ...helpers.countries import FastCountryField
|
||||
from ...helpers.u2f import pub_key_from_der, websafe_decode
|
||||
@@ -378,7 +378,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
{
|
||||
'user': self,
|
||||
'messages': msg,
|
||||
'url': build_absolute_uri('control:user.settings'),
|
||||
'url': mainreverse_absolute('control:user.settings'),
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
},
|
||||
event=None,
|
||||
@@ -466,7 +466,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
{
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'user': self,
|
||||
'url': (build_absolute_uri('control:auth.forgot.recover')
|
||||
'url': (mainreverse_absolute('control:auth.forgot.recover')
|
||||
+ '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self)))
|
||||
},
|
||||
None, locale=self.locale, user=self
|
||||
|
||||
@@ -346,11 +346,14 @@ class Checkin(models.Model):
|
||||
REASON_INCOMPLETE = 'incomplete'
|
||||
REASON_ALREADY_REDEEMED = 'already_redeemed'
|
||||
REASON_AMBIGUOUS = 'ambiguous'
|
||||
REASON_MEDIUM_INVALID = 'medium_invalid'
|
||||
REASON_MEDIUM_EXISTS = 'medium_exists'
|
||||
REASON_ERROR = 'error'
|
||||
REASON_BLOCKED = 'blocked'
|
||||
REASON_UNAPPROVED = 'unapproved'
|
||||
REASON_INVALID_TIME = 'invalid_time'
|
||||
REASON_ANNULLED = 'annulled'
|
||||
REASON_ALREADY_EXCHANGED = 'already_exchanged'
|
||||
REASONS = (
|
||||
(REASON_CANCELED, _('Order canceled')),
|
||||
(REASON_INVALID, _('Unknown ticket')),
|
||||
@@ -366,6 +369,9 @@ class Checkin(models.Model):
|
||||
(REASON_UNAPPROVED, _('Order not approved')),
|
||||
(REASON_INVALID_TIME, _('Ticket not valid at this time')),
|
||||
(REASON_ANNULLED, _('Check-in annulled')),
|
||||
(REASON_ALREADY_EXCHANGED, _('Ticket already exchanged')),
|
||||
(REASON_MEDIUM_INVALID, _('Reusable medium invalid')),
|
||||
(REASON_MEDIUM_EXISTS, _('Reusable medium already exists')),
|
||||
)
|
||||
|
||||
successful = models.BooleanField(
|
||||
|
||||
@@ -167,7 +167,7 @@ class Customer(LoggedModel):
|
||||
|
||||
def send_security_notice(self, message, email=None):
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
try:
|
||||
with language(self.locale):
|
||||
@@ -178,7 +178,7 @@ class Customer(LoggedModel):
|
||||
{
|
||||
**self.get_email_context(),
|
||||
'message': str(message),
|
||||
'url': build_absolute_uri(self.organizer, 'presale:organizer.customer.index')
|
||||
'url': eventreverse_absolute(self.organizer, 'presale:organizer.customer.index')
|
||||
},
|
||||
customer=self,
|
||||
organizer=self.organizer,
|
||||
@@ -299,12 +299,12 @@ class Customer(LoggedModel):
|
||||
|
||||
def send_activation_mail(self):
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
|
||||
ctx = self.get_email_context()
|
||||
token = TokenGenerator().make_token(self)
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.organizer,
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?id=' + self.identifier + '&token=' + token
|
||||
|
||||
@@ -724,7 +724,7 @@ class Event(EventMixin, LoggedModel):
|
||||
|
||||
@property
|
||||
def social_image(self):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
img = None
|
||||
logo_file = self.settings.get('logo_image', as_type=str, default='')[7:]
|
||||
@@ -742,7 +742,7 @@ class Event(EventMixin, LoggedModel):
|
||||
logger.exception(f'Failed to create thumbnail of {logo_file}')
|
||||
img = default_storage.url(logo_file)
|
||||
if img:
|
||||
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
|
||||
return urljoin(eventreverse_absolute(self, 'presale:event.index'), img)
|
||||
|
||||
def _seats(self, ignore_voucher=None):
|
||||
from .seating import Seat
|
||||
|
||||
@@ -452,11 +452,16 @@ class Item(LoggedModel):
|
||||
MEDIA_POLICY_REUSE = 'reuse'
|
||||
MEDIA_POLICY_NEW = 'new'
|
||||
MEDIA_POLICY_REUSE_OR_NEW = 'reuse_or_new'
|
||||
MEDIA_POLICY_APPEND = 'append'
|
||||
MEDIA_POLICY_APPEND_OR_NEW = 'append_or_new'
|
||||
MEDIA_POLICIES = (
|
||||
(None, _("Don't use re-usable media, use regular one-off tickets")),
|
||||
(MEDIA_POLICY_REUSE, _('Require an existing medium to be re-used')),
|
||||
(None, _("Don't use reusable media, use regular one-off tickets")),
|
||||
(MEDIA_POLICY_NEW, _('Require a previously unknown medium to be newly added')),
|
||||
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used')),
|
||||
(MEDIA_POLICY_REUSE, _('Require an existing medium to be reused, replacing any previous tickets')),
|
||||
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used, replacing any previous tickets')),
|
||||
(MEDIA_POLICY_APPEND, _('Require an existing medium to be reused, adding to any previous tickets')),
|
||||
(MEDIA_POLICY_APPEND_OR_NEW,
|
||||
_('Require either an existing or a new medium to be used, adding to any previous tickets')),
|
||||
)
|
||||
|
||||
objects = ItemQuerySetManager()
|
||||
@@ -769,7 +774,7 @@ class Item(LoggedModel):
|
||||
null=True, blank=True, max_length=16,
|
||||
verbose_name=_('Reusable media policy'),
|
||||
help_text=_(
|
||||
'If this product should be stored on a re-usable physical medium, you can attach a physical media policy. '
|
||||
'If this product should be stored on a reusable physical medium, you can attach a physical media policy. '
|
||||
'This is not required for regular tickets, which just use a one-time barcode, but only for products like '
|
||||
'renewable season tickets or re-chargeable gift card wristbands. '
|
||||
'This is an advanced feature that also requires specific configuration of ticketing and printing settings.'
|
||||
@@ -778,7 +783,7 @@ class Item(LoggedModel):
|
||||
media_type = models.CharField(
|
||||
max_length=100,
|
||||
null=True, blank=True,
|
||||
choices=[(None, _("Don't use re-usable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
|
||||
choices=[(None, _("Don't use reusable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
|
||||
verbose_name=_('Reusable media type'),
|
||||
help_text=_(
|
||||
'Select the type of physical medium that should be used for this product. Note that not all media types '
|
||||
@@ -995,6 +1000,11 @@ class Item(LoggedModel):
|
||||
raise ValidationError(_('The selected media type does not support usage for tickets currently.'))
|
||||
if not mt.supports_giftcard and issue_giftcard:
|
||||
raise ValidationError(_('The selected media type does not support usage for gift cards currently.'))
|
||||
if media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if not mt.medium_created_by_server and not mt.medium_created_from_unknown_supported:
|
||||
raise ValidationError(_('The selected media type requires all media to be registered in the system '
|
||||
'prior to their usage. Therefore, the selected media policy does not make '
|
||||
'sense for this media type.'))
|
||||
if issue_giftcard:
|
||||
raise ValidationError(_('You currently cannot create gift cards with a reusable media policy. Instead, '
|
||||
'gift cards for some reusable media types can be created or re-charged directly '
|
||||
@@ -2220,7 +2230,7 @@ class Quota(LoggedModel):
|
||||
class ItemMetaProperty(LoggedModel):
|
||||
"""
|
||||
An event can have ItemMetaProperty objects attached to define meta information fields
|
||||
for its items. This information can be re-used for example in ticket layouts.
|
||||
for its items. This information can be reused for example in ticket layouts.
|
||||
|
||||
:param event: The event this property is defined for.
|
||||
:type event: Event
|
||||
|
||||
@@ -72,6 +72,16 @@ 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'),
|
||||
@@ -89,12 +99,14 @@ class ReusableMedium(LoggedModel):
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_('Customer account'),
|
||||
)
|
||||
linked_orderposition = models.ForeignKey(
|
||||
linked_orderpositions = models.ManyToManyField(
|
||||
OrderPosition,
|
||||
null=True, blank=True,
|
||||
related_name='linked_media',
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_('Linked ticket'),
|
||||
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.'
|
||||
)
|
||||
)
|
||||
linked_giftcard = models.ForeignKey(
|
||||
GiftCard,
|
||||
@@ -117,7 +129,10 @@ class ReusableMedium(LoggedModel):
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
return self.expires and self.expires > now()
|
||||
return self.expires and self.expires < now()
|
||||
|
||||
def touch(self):
|
||||
self.save(update_fields=['updated'])
|
||||
|
||||
class Meta:
|
||||
unique_together = (("identifier", "type", "organizer"),)
|
||||
|
||||
@@ -354,38 +354,60 @@ class Order(LockModel, LoggedModel):
|
||||
def _transaction_key_reset(self):
|
||||
self.__initial_status_paid_or_pending = self.status in (Order.STATUS_PENDING, Order.STATUS_PAID) and not self.require_approval
|
||||
|
||||
def gracefully_delete(self, user=None, auth=None):
|
||||
from . import GiftCard, GiftCardTransaction, Membership, Voucher
|
||||
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
self.log_action(
|
||||
'pretix.event.order.deleted', user=user, auth=auth,
|
||||
data={
|
||||
'code': self.code,
|
||||
}
|
||||
@classmethod
|
||||
def gracefully_delete_bulk(cls, event, orders, user=None, auth=None):
|
||||
# Expects to be called in a transaction
|
||||
from . import (
|
||||
GiftCard, GiftCardTransaction, LogEntry, Membership, Voucher,
|
||||
)
|
||||
|
||||
order_gracefully_delete.send(self.event, order=self)
|
||||
if not transaction.get_connection().in_atomic_block:
|
||||
raise Exception('gracefully_delete_bulk should only be called in atomic transaction!')
|
||||
|
||||
if self.status != Order.STATUS_CANCELED:
|
||||
for position in self.positions.all():
|
||||
if position.voucher:
|
||||
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
|
||||
logs_create = []
|
||||
for o in orders:
|
||||
if not o.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
order_gracefully_delete.send(event, order=o)
|
||||
logs_create.append(o.log_action(
|
||||
'pretix.event.order.deleted', user=user, auth=auth,
|
||||
data={
|
||||
'code': o.code,
|
||||
},
|
||||
save=False,
|
||||
))
|
||||
LogEntry.bulk_create_and_postprocess(logs_create)
|
||||
|
||||
GiftCardTransaction.objects.filter(payment__in=self.payments.all()).update(payment=None)
|
||||
GiftCardTransaction.objects.filter(refund__in=self.refunds.all()).update(refund=None)
|
||||
GiftCardTransaction.objects.filter(order=self).update(order=None)
|
||||
GiftCard.objects.filter(issued_in__in=self.positions.all()).update(issued_in=None)
|
||||
Membership.objects.filter(granted_in__order=self, testmode=True).update(granted_in=None)
|
||||
OrderPosition.all.filter(order=self, addon_to__isnull=False).delete()
|
||||
OrderPosition.all.filter(order=self).delete()
|
||||
OrderFee.all.filter(order=self).delete()
|
||||
Transaction.objects.filter(order=self).delete()
|
||||
self.refunds.all().delete()
|
||||
self.payments.all().delete()
|
||||
self.event.cache.delete('complain_testmode_orders')
|
||||
self.delete()
|
||||
voucher_ids = OrderPosition.objects.filter(
|
||||
order__in=orders,
|
||||
voucher__isnull=False
|
||||
).exclude(order__status=Order.STATUS_CANCELED).values_list("voucher_id", flat=True)
|
||||
voucher_usages = Counter(voucher_ids)
|
||||
for v_id, usage_count in voucher_usages.items():
|
||||
Voucher.objects.filter(pk=v_id).update(redeemed=Greatest(0, F('redeemed') - usage_count))
|
||||
|
||||
GiftCardTransaction.objects.filter(payment__order__in=orders).update(payment=None)
|
||||
GiftCardTransaction.objects.filter(refund__order__in=orders).update(refund=None)
|
||||
GiftCardTransaction.objects.filter(order__in=orders).update(order=None)
|
||||
GiftCard.objects.filter(issued_in__order__in=orders).update(issued_in=None)
|
||||
Membership.objects.filter(granted_in__order__in=orders, testmode=True).update(granted_in=None)
|
||||
OrderPosition.all.filter(order__in=orders, addon_to__isnull=False).delete()
|
||||
OrderPosition.all.filter(order__in=orders).delete()
|
||||
OrderFee.all.filter(order__in=orders).delete()
|
||||
Transaction.objects.filter(order__in=orders).delete()
|
||||
OrderRefund.objects.filter(order__in=orders).delete()
|
||||
OrderPayment.objects.filter(order__in=orders).delete()
|
||||
if isinstance(orders, models.QuerySet):
|
||||
orders.delete()
|
||||
else:
|
||||
Order.objects.filter(pk__in=[o.pk for o in orders]).delete()
|
||||
event.cache.delete('complain_testmode_orders')
|
||||
|
||||
def gracefully_delete(self, user=None, auth=None):
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
|
||||
Order.gracefully_delete_bulk(self.event, Order.objects.filter(pk=self.pk), user, auth)
|
||||
|
||||
def email_confirm_secret(self):
|
||||
return self.tagged_secret("email_confirm", 9)
|
||||
|
||||
@@ -118,7 +118,10 @@ class SeatingPlan(LoggedModel):
|
||||
for zi, z in enumerate(self.layout_data['zones']):
|
||||
zpos = (z['position']['x'], z['position']['y'])
|
||||
for ri, r in enumerate(z['rows']):
|
||||
rpos = (zpos[0] + r['position']['x'], zpos[1] + r['position']['y'])
|
||||
rpos = (
|
||||
zpos[0] + r.get('position', {}).get('x', 0),
|
||||
zpos[1] + r.get('position', {}).get('y', 0),
|
||||
)
|
||||
row_label = None
|
||||
if r.get('row_label'):
|
||||
row_label = r['row_label'].replace("%s", r.get('row_number', str(ri)))
|
||||
@@ -147,8 +150,8 @@ class SeatingPlan(LoggedModel):
|
||||
zone=z['name'],
|
||||
category=s['category'],
|
||||
sorting_rank=rank,
|
||||
x=rpos[0] + s['position']['x'],
|
||||
y=rpos[1] + s['position']['y'],
|
||||
x=rpos[0] + s.get('position', {}).get('x', 0),
|
||||
y=rpos[1] + s.get('position', {}).get('y', 0),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from pretix.base.models import Event, LogEntry
|
||||
from pretix.base.signals import register_notification_types
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_ALL_TYPES = None
|
||||
@@ -170,7 +170,7 @@ class ParametrizedOrderNotificationType(NotificationType):
|
||||
def build_notification(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
|
||||
order_url = build_absolute_uri(
|
||||
order_url = mainreverse_absolute(
|
||||
'control:event.order',
|
||||
kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
|
||||
@@ -71,7 +71,7 @@ from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.money import DecimalTextInput
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.views import get_cart
|
||||
from pretix.presale.views.cart import cart_session, get_or_create_cart_id
|
||||
|
||||
@@ -379,7 +379,7 @@ class BasePaymentProvider:
|
||||
|
||||
if not self.settings.get('_hidden_seed'):
|
||||
self.settings.set('_hidden_seed', get_random_string(64))
|
||||
hidden_url = build_absolute_uri(self.event, 'presale:event.payment.unlock', kwargs={
|
||||
hidden_url = eventreverse_absolute(self.event, 'presale:event.payment.unlock', kwargs={
|
||||
'hash': hashlib.sha256((self.settings._hidden_seed + self.event.slug).encode()).hexdigest(),
|
||||
})
|
||||
|
||||
|
||||
@@ -287,11 +287,11 @@ def _check_position_constraints(
|
||||
raise CartPositionError(error_messages['unavailable'])
|
||||
|
||||
# Invalid media policy for online sale
|
||||
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
mt = MEDIA_TYPES[item.media_type]
|
||||
if not mt.medium_created_by_server:
|
||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||
elif item.media_policy == Item.MEDIA_POLICY_REUSE:
|
||||
elif item.media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||
|
||||
# Item removed from sales channel
|
||||
|
||||
@@ -867,6 +867,15 @@ class RequiredQuestionsError(Exception):
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class RequiredMediaExchangeError(Exception):
|
||||
def __init__(self, msg, code, media_policy, media_type):
|
||||
self.msg = msg
|
||||
self.code = code
|
||||
self.media_policy = media_policy
|
||||
self.media_type = media_type
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
def _save_answers(op, answers, given_answers):
|
||||
def _create_answer(question, answer):
|
||||
try:
|
||||
@@ -939,7 +948,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
ignore_unpaid=False, nonce=None, datetime=None, questions_supported=True,
|
||||
user=None, auth=None, canceled_supported=False, type=Checkin.TYPE_ENTRY,
|
||||
raw_barcode=None, raw_source_type=None, from_revoked_secret=False, simulate=False,
|
||||
gate=None):
|
||||
gate=None, reusable_medium=None):
|
||||
"""
|
||||
Create a checkin for this particular order position and check-in list. Fails with CheckInError if the check in is
|
||||
not valid at this time.
|
||||
@@ -955,6 +964,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
:param datetime: The datetime of the checkin, defaults to now.
|
||||
:param simulate: If true, the check-in is not saved.
|
||||
:param gate: The gate the check-in was performed at.
|
||||
:param reusable_medium: The medium that is available for an exchange
|
||||
"""
|
||||
|
||||
# !!!!!!!!!
|
||||
@@ -1035,7 +1045,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
|
||||
with transaction.atomic():
|
||||
# Lock order positions, if it is an entry. We don't need it for exits, as a race condition wouldn't be problematic
|
||||
opqs = OrderPosition.all
|
||||
opqs = OrderPosition.all.select_related("order", "item")
|
||||
if type != Checkin.TYPE_EXIT:
|
||||
opqs = opqs.select_for_update(of=OF_SELF)
|
||||
op = opqs.get(pk=op.pk)
|
||||
@@ -1101,6 +1111,24 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
require_answers
|
||||
)
|
||||
|
||||
required_media_policy = op.item.media_policy
|
||||
required_media_type = op.item.media_type
|
||||
require_a_medium = required_media_policy and required_media_type
|
||||
linked_media = op.linked_media
|
||||
if require_a_medium and not reusable_medium and not force:
|
||||
if not linked_media.exists():
|
||||
raise RequiredMediaExchangeError(
|
||||
_('Ticket needs to be exchanged to a suitable medium.'),
|
||||
'exchange',
|
||||
required_media_policy,
|
||||
required_media_type
|
||||
)
|
||||
elif op.organizer.settings.reusable_media_usage_enforced:
|
||||
raise CheckInError(
|
||||
_('This ticket has already been exchanged for a reusable medium that now needs to be used instead.'),
|
||||
'already_exchanged',
|
||||
)
|
||||
|
||||
device = None
|
||||
if isinstance(auth, Device):
|
||||
device = auth
|
||||
|
||||
@@ -51,7 +51,7 @@ from pretix.base.signals import (
|
||||
)
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF, repeatable_reads_transaction
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -455,7 +455,7 @@ def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> Non
|
||||
schedule,
|
||||
organizer,
|
||||
exporter,
|
||||
build_absolute_uri(
|
||||
mainreverse_absolute(
|
||||
'control:organizer.export',
|
||||
kwargs={
|
||||
'organizer': organizer.slug,
|
||||
@@ -481,7 +481,7 @@ def scheduled_event_export(self, event: Event, schedule: int) -> None:
|
||||
schedule,
|
||||
event,
|
||||
exporter,
|
||||
build_absolute_uri(
|
||||
mainreverse_absolute(
|
||||
'control:event.orders.export',
|
||||
kwargs={
|
||||
'event': event.slug,
|
||||
|
||||
@@ -85,7 +85,7 @@ from pretix.helpers.format import (
|
||||
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.ical import get_private_icals
|
||||
|
||||
logger = logging.getLogger('pretix.base.mail')
|
||||
@@ -997,7 +997,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += _(
|
||||
"You can view your order details at the following URL:\n{orderurl}."
|
||||
).replace("\n", "\r\n").format(
|
||||
orderurl=build_absolute_uri(
|
||||
orderurl=eventreverse_absolute(
|
||||
order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': position.web_secret,
|
||||
@@ -1013,7 +1013,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += _(
|
||||
"You can view your order details at the following URL:\n{orderurl}."
|
||||
).replace("\n", "\r\n").format(
|
||||
event=event.name, orderurl=build_absolute_uri(
|
||||
event=event.name, orderurl=eventreverse_absolute(
|
||||
order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
|
||||
@@ -23,10 +23,13 @@ import secrets
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import GiftCardAcceptance
|
||||
from pretix.base.models.media import MediumKeySet
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import Checkin, GiftCardAcceptance, Item
|
||||
from pretix.base.models.media import MediumKeySet, ReusableMedium
|
||||
from pretix.base.services.checkin import CheckInError
|
||||
|
||||
|
||||
def create_nfc_mf0aes_keyset(organizer):
|
||||
@@ -70,3 +73,174 @@ def get_keysets_for_organizer(organizer):
|
||||
if new_set:
|
||||
sets.append(new_set)
|
||||
return sets
|
||||
|
||||
|
||||
def perform_media_exchange(organizer, media_type, identifier, link_orderposition, user, auth):
|
||||
"""
|
||||
Create or retrieve a medium, then link the order position to it. Expected to be called in a transaction.
|
||||
|
||||
:param organizer: Organizer to operate in
|
||||
:param media_type: Type of medium to operate with
|
||||
:param identifier: Identifier of the medium
|
||||
:param link_orderposition: Position to link to the medium
|
||||
:return: ReusableMedium
|
||||
"""
|
||||
medium = None
|
||||
media_policy = link_orderposition.item.media_policy
|
||||
|
||||
if media_type not in MEDIA_TYPES: # should be caught by serializer already
|
||||
raise CheckInError(
|
||||
_('Invalid medium type.'),
|
||||
Checkin.REASON_ERROR,
|
||||
reason=_('Invalid medium type.'),
|
||||
)
|
||||
|
||||
if not MEDIA_TYPES[media_type].is_active(organizer):
|
||||
raise CheckInError(
|
||||
_('Medium type is not enabled for organizer.'),
|
||||
Checkin.REASON_ERROR,
|
||||
reason=_('Medium type is not enabled for organizer.'),
|
||||
)
|
||||
|
||||
if link_orderposition.item.media_type != media_type:
|
||||
raise CheckInError(
|
||||
_('Incorrect medium type for product.'),
|
||||
Checkin.REASON_PRODUCT,
|
||||
reason=_('Incorrect medium type for product.'),
|
||||
)
|
||||
|
||||
if link_orderposition.linked_media.exists():
|
||||
raise CheckInError(
|
||||
_('Ticket is already exchanged for reusable medium.'),
|
||||
Checkin.REASON_ALREADY_EXCHANGED,
|
||||
reason=_('Ticket is already exchanged for reusable medium.'),
|
||||
)
|
||||
|
||||
if media_policy in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_NEW):
|
||||
link_action = "append"
|
||||
else:
|
||||
link_action = "replace"
|
||||
|
||||
if media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||
try:
|
||||
medium = ReusableMedium.objects.get(
|
||||
type=media_type,
|
||||
identifier=identifier,
|
||||
organizer=organizer,
|
||||
)
|
||||
except ReusableMedium.DoesNotExist:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium not found.'),
|
||||
)
|
||||
else:
|
||||
if medium.is_expired or not medium.active:
|
||||
raise CheckInError(
|
||||
_('Reusable medium is inactive or expired.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium is inactive or expired.'),
|
||||
)
|
||||
|
||||
elif media_policy in (Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
try:
|
||||
medium = ReusableMedium.objects.get(
|
||||
type=media_type,
|
||||
identifier=identifier,
|
||||
organizer=organizer,
|
||||
)
|
||||
except ReusableMedium.DoesNotExist:
|
||||
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||
if not medium:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
if medium.is_expired or not medium.active:
|
||||
raise CheckInError(
|
||||
_('Reusable medium is inactive or expired.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
reason=_('Reusable medium is inactive or expired.'),
|
||||
)
|
||||
|
||||
elif media_policy == Item.MEDIA_POLICY_NEW:
|
||||
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||
raise CheckInError(
|
||||
_('Reusable medium not found and could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
try:
|
||||
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||
except IntegrityError:
|
||||
raise CheckInError(
|
||||
_('Reusable medium already exists.'),
|
||||
Checkin.REASON_MEDIUM_EXISTS,
|
||||
)
|
||||
else:
|
||||
if not medium:
|
||||
raise CheckInError(
|
||||
_('Reusable medium could not be created.'),
|
||||
Checkin.REASON_MEDIUM_INVALID,
|
||||
)
|
||||
|
||||
else:
|
||||
raise CheckInError(
|
||||
_('Product does not support medium exchange.'),
|
||||
Checkin.REASON_PRODUCT,
|
||||
reason=_('Product does not support medium exchange.'),
|
||||
)
|
||||
|
||||
if link_action == 'append':
|
||||
medium.linked_orderpositions.add(link_orderposition)
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'linked_orderposition': link_orderposition,
|
||||
}
|
||||
)
|
||||
elif link_action == 'replace':
|
||||
already_found = False
|
||||
for op_pk in medium.linked_orderpositions.values_list('pk', flat=True):
|
||||
if op_pk == link_orderposition.pk:
|
||||
already_found = True
|
||||
continue
|
||||
else:
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.removed',
|
||||
data={
|
||||
'linked_orderposition': op_pk,
|
||||
}
|
||||
)
|
||||
if not already_found:
|
||||
medium.linked_orderpositions.set([link_orderposition])
|
||||
medium.log_action(
|
||||
'pretix.reusable_medium.linked_orderposition.added',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'linked_orderposition': link_orderposition,
|
||||
}
|
||||
)
|
||||
|
||||
link_orderposition.order.log_action(
|
||||
'pretix.reusable_medium.exchanged',
|
||||
data={
|
||||
'position': link_orderposition.pk,
|
||||
'positionid': link_orderposition.positionid,
|
||||
'medium': medium.pk,
|
||||
'medium_identifier': medium.identifier,
|
||||
'medium_type': medium.media_type.identifier,
|
||||
}
|
||||
)
|
||||
medium.touch()
|
||||
|
||||
return medium
|
||||
|
||||
@@ -37,7 +37,7 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
|
||||
from pretix.base.signals import notification
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.celery import get_task_priority
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
@app.task(base=TransactionAwareTask, acks_late=True, max_retries=9, default_retry_delay=900)
|
||||
@@ -136,10 +136,10 @@ def send_notification_mail(notification: Notification, user: User):
|
||||
'site_url': settings.SITE_URL,
|
||||
'color': settings.PRETIX_PRIMARY_COLOR,
|
||||
'notification': notification,
|
||||
'settings_url': build_absolute_uri(
|
||||
'settings_url': mainreverse_absolute(
|
||||
'control:user.settings.notifications',
|
||||
),
|
||||
'disable_url': build_absolute_uri(
|
||||
'disable_url': mainreverse_absolute(
|
||||
'control:user.settings.notifications.off',
|
||||
kwargs={
|
||||
'token': user.notifications_token,
|
||||
|
||||
@@ -3506,7 +3506,7 @@ def signal_listener_issue_media(sender: Event, order: Order, **kwargs):
|
||||
from pretix.base.models import ReusableMedium
|
||||
|
||||
for p in order.positions.all():
|
||||
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||
mt = MEDIA_TYPES[p.item.media_type]
|
||||
if mt.medium_created_by_server and not p.linked_media.exists():
|
||||
rm = ReusableMedium.objects.create(
|
||||
@@ -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={
|
||||
|
||||
@@ -327,7 +327,7 @@ def get_best_name(position_or_address, parts=False):
|
||||
|
||||
@receiver(register_text_placeholders, dispatch_uid="pretixbase_register_text_placeholders")
|
||||
def base_placeholders(sender, **kwargs):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
def _event_sample(event):
|
||||
if event.has_subevents:
|
||||
@@ -388,14 +388,14 @@ def base_placeholders(sender, **kwargs):
|
||||
lambda event: LazyDate(now() + timedelta(days=15))
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_secret()
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -406,7 +406,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['order', 'event'],
|
||||
url_func=lambda order, event: build_absolute_uri(
|
||||
url_func=lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
@@ -415,7 +415,7 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
text_func=lambda order, event: _("View order details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
sample_url_func=lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -426,13 +426,13 @@ def base_placeholders(sender, **kwargs):
|
||||
sample_text_func=lambda event: _("View order details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -441,13 +441,13 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_products_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_products_change', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -456,13 +456,13 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_cancel', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
'url_cancel', ['order', 'event'], lambda order, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -471,7 +471,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
|
||||
'url', ['event', 'position'], lambda event, position: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position',
|
||||
kwargs={
|
||||
@@ -480,7 +480,7 @@ def base_placeholders(sender, **kwargs):
|
||||
'position': position.positionid
|
||||
}
|
||||
),
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -491,7 +491,7 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['event', 'position'],
|
||||
url_func=lambda event, position: build_absolute_uri(
|
||||
url_func=lambda event, position: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': position.order.code,
|
||||
@@ -500,7 +500,7 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
text_func=lambda event, position: _("View registration details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
sample_url_func=lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -511,14 +511,14 @@ def base_placeholders(sender, **kwargs):
|
||||
sample_text_func=lambda event: _("View registration details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.modify', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.modify', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -528,14 +528,14 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_products_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
'url_products_change', ['position', 'event'], lambda position, event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.change', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
), lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.order.position.change', kwargs={
|
||||
'order': 'F8VVL',
|
||||
@@ -581,20 +581,20 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_remove', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
lambda waiting_list_voucher, event: eventreverse_absolute(
|
||||
event, 'presale:event.waitinglist.remove'
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.waitinglist.remove',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
lambda waiting_list_voucher, event: eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
lambda event: eventreverse_absolute(
|
||||
event,
|
||||
'presale:event.redeem',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
@@ -611,7 +611,7 @@ def base_placeholders(sender, **kwargs):
|
||||
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
order.full_code,
|
||||
build_absolute_uri(event, 'presale:event.order.open', kwargs={
|
||||
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order.code,
|
||||
@@ -623,7 +623,7 @@ def base_placeholders(sender, **kwargs):
|
||||
), lambda event: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
'{}-{}'.format(event.slug.upper(), order['code']),
|
||||
build_absolute_uri(event, 'presale:event.order.open', kwargs={
|
||||
eventreverse_absolute(event, 'presale:event.order.open', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order['code'],
|
||||
@@ -662,13 +662,13 @@ def base_placeholders(sender, **kwargs):
|
||||
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
|
||||
'voucher_url_list', ['event', 'voucher_list'],
|
||||
lambda event, voucher_list: ' \n'.join([
|
||||
build_absolute_uri(
|
||||
eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + c
|
||||
for c in voucher_list
|
||||
]),
|
||||
lambda event: ' \n'.join([
|
||||
build_absolute_uri(
|
||||
eventreverse_absolute(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + c
|
||||
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
|
||||
@@ -676,10 +676,10 @@ def base_placeholders(sender, **kwargs):
|
||||
inline=False,
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
|
||||
'url', ['event', 'voucher_list'], lambda event, voucher_list: eventreverse_absolute(event, 'presale:event.index', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
}), lambda event: build_absolute_uri(event, 'presale:event.index', kwargs={
|
||||
}), lambda event: eventreverse_absolute(event, 'presale:event.index', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ from pretix.base.services.mail import mail
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@@ -121,7 +121,7 @@ def send_update_notification_email():
|
||||
)
|
||||
),
|
||||
{
|
||||
'url': build_absolute_uri('control:global.update')
|
||||
'url': mainreverse_absolute('control:global.update')
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -211,12 +211,25 @@ DEFAULTS = {
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Activate re-usable media"),
|
||||
help_text=_("The re-usable media feature allows you to connect tickets and gift cards with physical media "
|
||||
"such as wristbands or chip cards that may be re-used for different tickets or gift cards "
|
||||
label=_("Activate reusable media"),
|
||||
help_text=_("The reusable media feature allows you to connect tickets and gift cards with physical media "
|
||||
"such as wristbands or chip cards that may be reused for different tickets or gift cards "
|
||||
"later.")
|
||||
)
|
||||
},
|
||||
'reusable_media_usage_enforced': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Enforce the usage of issued reusable media for check-in"),
|
||||
help_text=_("If enabled, a ticket barcode will not be accepted anymore, if a reusable medium has been "
|
||||
"created and linked to a ticket. Keeping this option turned off will treat the reusable "
|
||||
"medium and ticket as equals."),
|
||||
widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_settings-reusable_media_active'}),
|
||||
)
|
||||
},
|
||||
'reusable_media_type_barcode': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
{% block custom_header %}{% endblock %}
|
||||
{% if css_theme %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ css_theme }}" />
|
||||
@@ -21,5 +22,4 @@
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
<script src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
</html>
|
||||
|
||||
@@ -55,10 +55,12 @@
|
||||
{% trans "You receive these emails based on your notification settings." %}<br>
|
||||
<a href="{{ settings_url }}">
|
||||
{% trans "Click here to view and change your notification settings" %}
|
||||
</a><br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
</a>
|
||||
{% if disable_url %}<br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
{% trans "You receive these emails based on your notification settings." %}
|
||||
{% trans "Click here to view and change your notification settings:" %}
|
||||
{{ settings_url }}
|
||||
{% trans "Click here disable all notifications immediately:" %}
|
||||
{% if disable_url %}{% trans "Click here disable all notifications immediately:" %}
|
||||
{{ disable_url }}
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def human_readable_locale(value):
|
||||
if not value:
|
||||
return ''
|
||||
return dict(settings.LANGUAGES).get(value, '')
|
||||
@@ -24,10 +24,12 @@ import calendar
|
||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rrulestr
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.core.validators import RegexValidator, validate_email
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.templatetags.rich_text import URL_RE
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
@@ -113,6 +115,33 @@ def multimail_validate(val):
|
||||
return s
|
||||
|
||||
|
||||
class RegexValidatorInverseMatchAndParam(RegexValidator):
|
||||
inverse_match = True
|
||||
|
||||
def __call__(self, value):
|
||||
regex_matches = self.regex.search(str(value))
|
||||
if regex_matches:
|
||||
raise ValidationError(
|
||||
self.message,
|
||||
code=self.code,
|
||||
params={
|
||||
"value": value,
|
||||
"match": regex_matches.group(0) if regex_matches else "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class NoUrlValidator(RegexValidatorInverseMatchAndParam):
|
||||
regex = URL_RE
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if not kwargs.get("message"):
|
||||
kwargs["message"] = _('You entered an URL, which is not allowed. Please remove %(match)s from your input.')
|
||||
if not kwargs.get("code"):
|
||||
kwargs["code"] = "contains_url"
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class RRuleValidator:
|
||||
def __init__(self, enforce_simple=False):
|
||||
self.enforce_simple = enforce_simple
|
||||
|
||||
@@ -461,3 +461,31 @@ 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)
|
||||
|
||||
@@ -80,7 +80,7 @@ from pretix.control.forms.widgets import Select2
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.models import AlternativeDomainAssignment, KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_organizer_domain,
|
||||
eventreverse_absolute, get_organizer_domain,
|
||||
)
|
||||
from pretix.plugins.banktransfer.payment import BankTransfer
|
||||
from pretix.presale.style import get_fonts
|
||||
@@ -219,7 +219,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
self.fields['slug'].widget.prefix = build_absolute_uri(self.organizer, 'presale:organizer.index')
|
||||
self.fields['slug'].widget.prefix = eventreverse_absolute(self.organizer, 'presale:organizer.index')
|
||||
self.fields['tax_rate']._required = True # Do not render as optional because it is conditionally required
|
||||
if self.has_subevents:
|
||||
del self.fields['presale_start']
|
||||
|
||||
@@ -1342,7 +1342,13 @@ class QuestionAnswerFilterForm(forms.Form):
|
||||
opqs = opqs.filter(canceled=False)
|
||||
if fdata.get("item", "") != "":
|
||||
i = fdata.get("item", "")
|
||||
opqs = opqs.filter(item_id__in=(i,))
|
||||
if '-' in i:
|
||||
opqs = opqs.filter(
|
||||
item_id=i.split('-')[0],
|
||||
variation_id=i.split('-')[1],
|
||||
)
|
||||
else:
|
||||
opqs = opqs.filter(item_id=i)
|
||||
|
||||
return opqs
|
||||
|
||||
@@ -1871,7 +1877,7 @@ class ReusableMediaFilterForm(FilterForm):
|
||||
Q(identifier__icontains=query)
|
||||
| Q(customer__identifier__icontains=query)
|
||||
| Q(customer__external_identifier__istartswith=query)
|
||||
| Q(linked_orderposition__order__code__icontains=query)
|
||||
| Q(linked_orderpositions__order__code__icontains=query)
|
||||
| Q(linked_giftcard__secret__icontains=query)
|
||||
)
|
||||
|
||||
|
||||
@@ -108,8 +108,9 @@ class GlobalSettingsForm(SettingsForm):
|
||||
('widget_vite_origins', forms.CharField(
|
||||
widget=forms.Textarea(attrs={'rows': '3'}),
|
||||
required=False,
|
||||
label=_("Vite widget origins"),
|
||||
help_text=_("One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget."),
|
||||
# Not translated on purpose, this is a temporary feature and contains too many special case words
|
||||
label="Vite widget origins",
|
||||
help_text="One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget.",
|
||||
))
|
||||
])
|
||||
responses = register_global_settings.send(self)
|
||||
|
||||
@@ -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
|
||||
from pretix.control.forms.widgets import Select2, Select2Multiple
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
class OrganizerForm(I18nModelForm):
|
||||
@@ -249,6 +249,15 @@ 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
|
||||
@@ -627,6 +636,7 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
'cookie_consent_dialog_button_yes',
|
||||
'cookie_consent_dialog_button_no',
|
||||
'reusable_media_active',
|
||||
'reusable_media_usage_enforced',
|
||||
'reusable_media_type_barcode',
|
||||
'reusable_media_type_barcode_identifier_length',
|
||||
'reusable_media_type_nfc_uid',
|
||||
@@ -781,7 +791,7 @@ class MailSettingsForm(SettingsForm):
|
||||
}
|
||||
|
||||
if 'url' in base_parameters:
|
||||
placeholders['url'] = build_absolute_uri(
|
||||
placeholders['url'] = eventreverse_absolute(
|
||||
self.organizer,
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?token=' + get_random_string(30)
|
||||
@@ -963,12 +973,12 @@ class ReusableMediumUpdateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ReusableMedium
|
||||
fields = ['active', 'expires', 'customer', 'linked_giftcard', 'linked_orderposition', 'notes']
|
||||
fields = ['active', 'expires', 'customer', 'linked_giftcard', 'linked_orderpositions', 'notes']
|
||||
field_classes = {
|
||||
'expires': SplitDateTimeField,
|
||||
'customer': SafeModelChoiceField,
|
||||
'linked_giftcard': SafeModelChoiceField,
|
||||
'linked_orderposition': SafeOrderPositionChoiceField,
|
||||
'linked_orderpositions': SafeOrderPositionMultipleChoiceField,
|
||||
}
|
||||
widgets = {
|
||||
'expires': SplitDateTimePickerWidget,
|
||||
@@ -978,8 +988,8 @@ class ReusableMediumUpdateForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
organizer = self.instance.organizer
|
||||
|
||||
self.fields['linked_orderposition'].queryset = OrderPosition.all.filter(order__event__organizer=organizer).all()
|
||||
self.fields['linked_orderposition'].widget = Select2(
|
||||
self.fields['linked_orderpositions'].queryset = OrderPosition.all.filter(order__event__organizer=organizer).all()
|
||||
self.fields['linked_orderpositions'].widget = Select2Multiple(
|
||||
attrs={
|
||||
'data-model-select2': 'generic',
|
||||
'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={
|
||||
@@ -987,8 +997,8 @@ class ReusableMediumUpdateForm(forms.ModelForm):
|
||||
}),
|
||||
}
|
||||
)
|
||||
self.fields['linked_orderposition'].widget.choices = self.fields['linked_orderposition'].choices
|
||||
self.fields['linked_orderposition'].required = False
|
||||
self.fields['linked_orderpositions'].widget.choices = self.fields['linked_orderpositions'].choices
|
||||
self.fields['linked_orderpositions'].required = False
|
||||
|
||||
self.fields['linked_giftcard'].queryset = organizer.issued_gift_cards.all()
|
||||
self.fields['linked_giftcard'].widget = Select2(
|
||||
@@ -1042,12 +1052,12 @@ class ReusableMediumCreateForm(ReusableMediumUpdateForm):
|
||||
|
||||
class Meta:
|
||||
model = ReusableMedium
|
||||
fields = ['active', 'type', 'identifier', 'expires', 'linked_orderposition', 'linked_giftcard', 'customer', 'notes']
|
||||
fields = ['active', 'type', 'identifier', 'expires', 'linked_orderpositions', 'linked_giftcard', 'customer', 'notes']
|
||||
field_classes = {
|
||||
'expires': SplitDateTimeField,
|
||||
'customer': SafeModelChoiceField,
|
||||
'linked_giftcard': SafeModelChoiceField,
|
||||
'linked_orderposition': SafeOrderPositionChoiceField,
|
||||
'linked_orderpositions': SafeOrderPositionMultipleChoiceField,
|
||||
}
|
||||
widgets = {
|
||||
'expires': SplitDateTimePickerWidget,
|
||||
|
||||
@@ -29,17 +29,30 @@ class Select2Mixin:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def options(self, name, value, attrs=None):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
return
|
||||
|
||||
def optgroups(self, name, value, attrs=None):
|
||||
|
||||
@@ -743,7 +743,10 @@ 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.'),
|
||||
|
||||
@@ -213,6 +213,16 @@ 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'
|
||||
@@ -261,6 +271,16 @@ 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,6 +66,7 @@
|
||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/errors.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{{ html_head|safe }}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
<span class="fa fa-check-circle"></span>
|
||||
{% elif result.status == "incomplete" %}
|
||||
<span class="fa fa-question-circle"></span>
|
||||
{% elif result.status == "exchange" %}
|
||||
<span class="fa fa-recycle"></span>
|
||||
{% elif result.status == "error" %}
|
||||
{% if result.reason == "already_redeemed" %}
|
||||
<span class="fa fa-warning"></span>
|
||||
@@ -79,6 +81,14 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% elif result.status == "exchange" %}
|
||||
<h3 class="nomargin-top">{% trans "Media exchange required" %}</h3>
|
||||
<p>
|
||||
{% blocktrans trimmed with media_policy=media_policies|getitem:result.media_policy media_type=media_types|getitem:result.media_type %}
|
||||
This ticket needs to be exchanged into a <strong>{{ media_type }}</strong> reusable medium.
|
||||
<strong>{{ media_policy }}</strong>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% elif result.status == "error" %}
|
||||
<h3 class="nomargin-top">{{ reason_labels|getitem:result.reason }}</h3>
|
||||
{% if result.reason_explanation %}
|
||||
|
||||
@@ -9,23 +9,24 @@
|
||||
<h3 class="panel-title">{% trans "Go offline" %}</h3>
|
||||
</div>
|
||||
<div class="row panel-body">
|
||||
<div class="col-sm-12 col-md-9 nomargin-bottom">
|
||||
<div class="col-sm-12 col-lg-6">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can take your event offline. Nobody except your team will be able to see or access it any more.
|
||||
{% endblocktrans %}
|
||||
</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">
|
||||
</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">
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-lg btn-block">
|
||||
<span class="fa fa-power-off"></span>
|
||||
{% trans "Go offline" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<span class="fa fa-power-off"></span>
|
||||
{% trans "Go offline" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,22 +35,24 @@
|
||||
<h3 class="panel-title">{% trans "Cancel event" %}</h3>
|
||||
</div>
|
||||
<div class="row panel-body">
|
||||
<div class="col-sm-12 col-md-9 nomargin-bottom">
|
||||
<div class="col-sm-12 col-lg-6">
|
||||
<p>
|
||||
{% 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-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>
|
||||
<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 %}
|
||||
{% trans "Cancel event" %}
|
||||
</a>
|
||||
{% else %}
|
||||
{% trans "No permission" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans "No permission" %}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,15 +62,16 @@
|
||||
<h3 class="panel-title">{% trans "Delete personal data" %}</h3>
|
||||
</div>
|
||||
<div class="row panel-body">
|
||||
<div class="col-sm-12 col-md-9 nomargin-bottom">
|
||||
<div class="col-sm-12 col-lg-6">
|
||||
<p>
|
||||
{% 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-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">
|
||||
<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">
|
||||
<span class="fa fa-eraser"></span>
|
||||
{% trans "Delete personal data" %}
|
||||
</a>
|
||||
@@ -80,15 +84,17 @@
|
||||
<h3 class="panel-title">{% trans "Delete event" %}</h3>
|
||||
</div>
|
||||
<div class="row panel-body">
|
||||
<div class="col-sm-12 col-md-9 nomargin-bottom">
|
||||
<div class="col-sm-12 col-lg-6">
|
||||
<p>
|
||||
{% 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-md-3">
|
||||
<div class="col-sm-12 col-lg-6 text-right">
|
||||
<a href="{% url "control:event.delete" organizer=request.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-danger btn-block btn-lg {% if not request.event.allow_delete %}disabled{% endif %}">
|
||||
class="btn btn-danger btn-lg {% if not request.event.allow_delete %}disabled{% endif %}">
|
||||
<span class="fa fa-trash"></span>
|
||||
{% trans "Delete event" %}
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load eventsignal %}
|
||||
{% load i18n %}
|
||||
{% block title %}
|
||||
{% trans "Approve order" %}
|
||||
@@ -7,6 +8,9 @@
|
||||
<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>
|
||||
|
||||
@@ -134,6 +134,39 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if invoice_qualified and order.invoice_dirty %}
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
This order was changed after the last invoice was generated. A new invoice was not generated yet, because invoices are configured to be generated on payment or if required by the payment method.
|
||||
A new invoice will be generated once the customer pays the invoice or selects a payment method that requires an invoice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% if "event.orders:write" in request.eventpermset %}
|
||||
<p>
|
||||
{% if uncancelled_invoice %}
|
||||
<form action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=uncancelled_invoice.pk %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default" type="submit">
|
||||
{% blocktrans trimmed %}
|
||||
Reissue invoice
|
||||
{% endblocktrans %}
|
||||
</button>
|
||||
</form>
|
||||
{% elif can_generate_invoice %}
|
||||
<form method="post" action="{% url "control:event.order.geninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default">
|
||||
{% trans "Generate invoice" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-10">
|
||||
{% for cr in order.cancellation_requests.all %}
|
||||
@@ -553,7 +586,7 @@
|
||||
<span class="fa fa-print"></span>
|
||||
{{ pl.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{{ pl.get_type_display }}
|
||||
({{ pl.source }}{% if pl.device %}, #{{ pl.device.device_id }}{% endif %})
|
||||
({{ pl.source }}{% if pl.device %}, {{ pl.device.name }} - #{{ pl.device.device_id }}{% endif %})
|
||||
{% if not pl.successful %}<span class="fa fa-warning fa-fw"></span>{% endif %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
@@ -1045,7 +1078,7 @@
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>
|
||||
{{ order.invoice_address.vat_id }}
|
||||
{% if order.invoice_address.vat_id_validated %}
|
||||
{% if order.invoice_address.vat_id and order.invoice_address.vat_id_validated %}
|
||||
<span class="fa fa-check" data-toggle="tooltip" title="{% blocktrans trimmed %}Valid EU VAT ID{% endblocktrans %}"></span>
|
||||
{% elif order.invoice_address.vat_id %}
|
||||
<form class="form-inline helper-display-inline" method="post"
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Reusable media" %}</legend>
|
||||
{% bootstrap_field sform.reusable_media_active layout="control" %}
|
||||
{% bootstrap_field sform.reusable_media_usage_enforced layout="control" %}
|
||||
<div data-display-dependency="#{{ sform.reusable_media_active.id_for_label }}">
|
||||
|
||||
<div class="panel panel-default">
|
||||
|
||||
@@ -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' '-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>
|
||||
<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>
|
||||
<th>{% trans "Connections" context "reusable_media" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -90,13 +90,13 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if m.linked_orderposition %}
|
||||
{% for op in m.linked_orderpositions.all %}
|
||||
<span class="helper-display-block">
|
||||
<span class="fa fa-ticket fa-fw"></span>
|
||||
<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 }}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if m.linked_giftcard %}
|
||||
<span class="helper-display-block">
|
||||
<span class="fa fa-credit-card fa-fw"></span>
|
||||
|
||||
@@ -26,7 +26,19 @@
|
||||
<dt>{% trans "Media type" context "reusable_media" %}</dt>
|
||||
<dd>{{ medium.get_type_display }}</dd>
|
||||
<dt>{% trans "Identifier" context "reusable_media" %}</dt>
|
||||
<dd><code>{{ medium.identifier }}</code></dd>
|
||||
<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>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>
|
||||
{% if not medium.active %}
|
||||
@@ -41,34 +53,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 %}">
|
||||
<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 %}
|
||||
{{ medium.customer }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ medium.customer }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if medium.linked_orderposition %}
|
||||
{% for op in medium.linked_orderpositions.all %}
|
||||
<span class="helper-display-block">
|
||||
<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 %}
|
||||
<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 %}
|
||||
{% 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,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load getitem %}
|
||||
{% load icon %}
|
||||
{% block inner %}
|
||||
{% if team %}
|
||||
<h1>{% trans "Team:" %} {{ team.name }}</h1>
|
||||
@@ -25,6 +26,18 @@
|
||||
<legend>{% trans "Organizer permissions" %}</legend>
|
||||
{% bootstrap_field form.all_organizer_permissions layout="control" %}
|
||||
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_organizer_permissions" data-inverse>
|
||||
<p class="text-muted">
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed %}
|
||||
Even if a team has no access to a certain category of data, they might still be able to see
|
||||
parts of this data when it is linked to data they can see.
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
For example, someone with access to customer accounts will be able to see some information
|
||||
about gift cards linked to a customer account, even if they generally can't see gift cards
|
||||
directly.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% for f in form.organizer_field_names %}
|
||||
{% bootstrap_field form|getitem:f layout="control" %}
|
||||
{% endfor %}
|
||||
@@ -37,6 +50,17 @@
|
||||
{% bootstrap_field form.limit_events layout="control" %}
|
||||
{% bootstrap_field form.all_event_permissions layout="control" %}
|
||||
<div class="team-permission-groups col-md-9 col-md-offset-3" data-display-dependency="#id_all_event_permissions" data-inverse>
|
||||
<p class="text-muted">
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed %}
|
||||
Even if a team has no access to a certain category of data, they might still be able to see
|
||||
parts of this data when it is linked to data they can see.
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
For example, someone with access to orders will be able to see some information about
|
||||
vouchers used to create an order, even if they generally can't see vouchers directly.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% for f in form.event_field_names %}
|
||||
{% bootstrap_field form|getitem:f layout="control" %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -4,290 +4,306 @@
|
||||
{% load formset_tags %}
|
||||
{% load eventsignal %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
|
||||
{% load money %}
|
||||
{% load icon %}
|
||||
{% block title %}{% blocktrans trimmed with name=subevent.name context "subevent" %}Date: {{ name }}
|
||||
{% endblocktrans %}{% 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>
|
||||
<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 %}
|
||||
</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>
|
||||
{% 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 %}
|
||||
</fieldset>
|
||||
{% 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 %}
|
||||
<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 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>
|
||||
</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 class="numbers">
|
||||
{{ cl.checkin_count|default_if_none:"0" }} /
|
||||
{{ cl.position_count|default_if_none:"0" }}
|
||||
</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" %}
|
||||
</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 %}
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
{% 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>
|
||||
</tbody>
|
||||
</table>
|
||||
</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 %}
|
||||
{% 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 "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 %}
|
||||
<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>
|
||||
</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 %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
No orders found.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</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>
|
||||
{% 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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "pretixcontrol/includes/logs.html" with obj=subevent %}
|
||||
</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>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
{% 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 %}?returnto={{ request.GET.urlencode|urlencode }}">
|
||||
<strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}">
|
||||
{{ 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" 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>
|
||||
<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>
|
||||
<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 %}?returnto={{ request.GET.urlencode|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 %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -308,7 +308,8 @@ 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.SubEventUpdate.as_view(), name='event.subevent'),
|
||||
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+)/delete$', subevents.SubEventDelete.as_view(),
|
||||
name='event.subevent.delete'),
|
||||
re_path(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
||||
|
||||
@@ -50,7 +50,7 @@ from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.api.views.checkin import _redeem_process
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import Checkin, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models import Checkin, Item, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.permissions import AnyPermissionOf
|
||||
@@ -401,13 +401,14 @@ class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
{
|
||||
'id': i.pk,
|
||||
'name': str(i),
|
||||
'active': i.active,
|
||||
'variations': [
|
||||
{
|
||||
'id': v.pk,
|
||||
'name': str(v.value)
|
||||
} for v in i.variations.all()
|
||||
]
|
||||
} for i in self.request.event.items.filter(active=True).prefetch_related('variations')
|
||||
} for i in self.request.event.items.prefetch_related('variations')
|
||||
],
|
||||
**super().get_context_data(),
|
||||
}
|
||||
@@ -532,6 +533,8 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
|
||||
checkinlist=self.list,
|
||||
result=self.result,
|
||||
reason_labels=dict(Checkin.REASONS),
|
||||
media_policies=dict(Item.MEDIA_POLICIES),
|
||||
media_types=dict(MEDIA_TYPES),
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
@@ -44,7 +44,7 @@ from pretix.control.permissions import (
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
from . import CreateView, UpdateView
|
||||
|
||||
|
||||
class DiscountDelete(EventPermissionRequiredMixin, CompatDeleteView):
|
||||
@@ -183,7 +183,7 @@ class DiscountCreate(EventPermissionRequiredMixin, CreateView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class DiscountList(PaginationMixin, ListView):
|
||||
class DiscountList(ListView):
|
||||
model = Discount
|
||||
context_object_name = 'discounts'
|
||||
template_name = 'pretixcontrol/items/discounts.html'
|
||||
|
||||
@@ -97,7 +97,9 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views.mailsetup import MailSettingsSetupView
|
||||
from pretix.control.views.user import RecentAuthenticationRequiredMixin
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
eventreverse_absolute, get_event_domain,
|
||||
)
|
||||
from pretix.presale.views.widget import (
|
||||
version_default as widget_version_default,
|
||||
)
|
||||
@@ -1148,8 +1150,11 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
|
||||
if request.POST.get("delete") == "yes":
|
||||
try:
|
||||
with transaction.atomic():
|
||||
for order in request.event.orders.filter(testmode=True):
|
||||
order.gracefully_delete(user=self.request.user)
|
||||
Order.gracefully_delete_bulk(
|
||||
request.event,
|
||||
request.event.orders.filter(testmode=True),
|
||||
user=self.request.user
|
||||
)
|
||||
except ProtectedError:
|
||||
messages.error(self.request, _('An order could not be deleted as some constraints (e.g. data '
|
||||
'created by plug-ins) do not allow it.'))
|
||||
@@ -1734,7 +1739,7 @@ class EventQRCode(EventPermissionRequiredMixin, View):
|
||||
permission = None
|
||||
|
||||
def get(self, request, *args, filetype, **kwargs):
|
||||
url = build_absolute_uri(request.event, 'presale:event.index')
|
||||
url = eventreverse_absolute(request.event, 'presale:event.index')
|
||||
|
||||
if "url" in request.GET:
|
||||
if url_has_allowed_host_and_scheme(request.GET["url"], allowed_hosts=[urlparse(url).netloc]):
|
||||
|
||||
@@ -335,7 +335,7 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class CategoryList(PaginationMixin, ListView):
|
||||
class CategoryList(ListView):
|
||||
model = ItemCategory
|
||||
context_object_name = 'categories'
|
||||
template_name = 'pretixcontrol/items/categories.html'
|
||||
|
||||
@@ -139,6 +139,7 @@ from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.helpers.iter import chunked_iterable
|
||||
from pretix.helpers.json import CustomJSONEncoder
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
@@ -240,7 +241,7 @@ class BaseOrderBulkActionView(OrderSearchMixin, EventPermissionRequiredMixin, As
|
||||
raise NotImplementedError()
|
||||
|
||||
def execute_bulk(self, queryset: QuerySet, form: forms.Form):
|
||||
qs = self.allowed_for(self.allowed_for(self.get_queryset()))
|
||||
qs = self.allowed_for(self.get_queryset())
|
||||
total = qs.count()
|
||||
orders_with_successful_action = 0
|
||||
for i, o in enumerate(qs):
|
||||
@@ -394,8 +395,21 @@ class OrderDeleteBulkActionView(BaseOrderBulkActionView):
|
||||
testmode=True,
|
||||
)
|
||||
|
||||
def execute_single(self, instance, form: forms.Form):
|
||||
instance.gracefully_delete(user=self.request.user)
|
||||
def execute_bulk(self, queryset: QuerySet, form: forms.Form):
|
||||
qs = self.allowed_for(self.get_queryset())
|
||||
total = qs.count()
|
||||
all_ids = list(qs.values_list("id", flat=True))
|
||||
|
||||
orders_with_successful_action = 0
|
||||
for chunk in chunked_iterable(all_ids, 1000):
|
||||
Order.gracefully_delete_bulk(
|
||||
self.request.event,
|
||||
qs.filter(id__in=chunk),
|
||||
user=self.request.user,
|
||||
)
|
||||
orders_with_successful_action += len(chunk)
|
||||
self.async_set_progress(orders_with_successful_action / total * 100)
|
||||
return orders_with_successful_action, total
|
||||
|
||||
|
||||
class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
@@ -554,6 +568,9 @@ class OrderDetail(OrderView):
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
ctx['payment_refund_sum'] = self.order.payment_refund_sum
|
||||
ctx['pending_sum'] = self.order.pending_sum
|
||||
ctx['uncancelled_invoice'] = self.order.invoices.exclude(
|
||||
Exists(self.order.invoices.filter(refers=OuterRef('pk'), is_cancellation=True))
|
||||
).exclude(is_cancellation=True).first()
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
@@ -139,8 +139,8 @@ from pretix.helpers import OF_SELF, GroupConcat
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1039,7 +1039,7 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
||||
'user': self,
|
||||
'organizer': self.request.organizer.name,
|
||||
'team': instance.team.name,
|
||||
'url': build_global_uri('control:auth.invite', kwargs={
|
||||
'url': mainreverse_absolute('control:auth.invite', kwargs={
|
||||
'token': instance.token
|
||||
})
|
||||
},
|
||||
@@ -2851,10 +2851,12 @@ class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['redirect_uri'] = build_absolute_uri(self.request.organizer, 'presale:organizer.customer.login.return',
|
||||
kwargs={
|
||||
'provider': self.object.pk
|
||||
})
|
||||
ctx['redirect_uri'] = eventreverse_absolute(
|
||||
self.request.organizer, 'presale:organizer.customer.login.return',
|
||||
kwargs={
|
||||
'provider': self.object.pk
|
||||
}
|
||||
)
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self):
|
||||
@@ -3085,7 +3087,7 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
self.customer.log_action('pretix.customer.password.resetrequested', {}, user=self.request.user)
|
||||
ctx = self.customer.get_email_context()
|
||||
token = TokenGenerator().make_token(self.customer)
|
||||
ctx['url'] = build_absolute_uri(
|
||||
ctx['url'] = eventreverse_absolute(
|
||||
self.request.organizer,
|
||||
'presale:organizer.customer.recoverpw'
|
||||
) + '?id=' + self.customer.identifier + '&token=' + token
|
||||
@@ -3384,8 +3386,10 @@ class ReusableMediaListView(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.reusable_media.select_related(
|
||||
'customer', 'linked_orderposition', 'linked_orderposition__order', 'linked_orderposition__order__event',
|
||||
'linked_giftcard'
|
||||
'customer',
|
||||
'linked_giftcard',
|
||||
).prefetch_related(
|
||||
Prefetch('linked_orderpositions', queryset=OrderPosition.objects.select_related("order", "order__event"))
|
||||
)
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
@@ -3433,10 +3437,14 @@ class ReusableMediumCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
r = super().form_valid(form)
|
||||
form.instance.log_action('pretix.reusable_medium.created', user=self.request.user, data={
|
||||
|
||||
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
|
||||
|
||||
@@ -3461,13 +3469,40 @@ 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():
|
||||
self.object.log_action('pretix.reusable_medium.changed', user=self.request.user, data={
|
||||
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 super().form_valid(form)
|
||||
return result
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.reusable_medium', kwargs={
|
||||
|
||||
@@ -41,7 +41,9 @@ 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, F, Prefetch, ProtectedError
|
||||
from django.db.models import (
|
||||
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Subquery,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, TruncDate, TruncTime
|
||||
from django.forms import inlineformset_factory
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
@@ -49,17 +51,21 @@ 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, FormView, ListView, UpdateView
|
||||
from django.views.generic import (
|
||||
CreateView, DetailView, FormView, ListView, UpdateView,
|
||||
)
|
||||
|
||||
from pretix.base.models import CartPosition, LogEntry
|
||||
from pretix.base.models import CartPosition, LogEntry, OrderPosition
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import (
|
||||
ItemVariation, Quota, SubEventItem, SubEventItemVariation,
|
||||
Item, 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
|
||||
@@ -505,9 +511,68 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
) and self.cl_formset.is_valid() and all(f.is_valid() for f in self.plugin_forms)
|
||||
|
||||
|
||||
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
|
||||
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'
|
||||
permission = 'event.subevents:write'
|
||||
context_object_name = 'subevent'
|
||||
form_class = SubEventForm
|
||||
@@ -531,6 +596,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
self.save_formset(self.object)
|
||||
self.save_cl_formset(self.object)
|
||||
self.save_meta()
|
||||
@@ -569,23 +635,31 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
f.subevent = self.object
|
||||
f.save()
|
||||
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk})
|
||||
return super().form_valid(form)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
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={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
}) + ('?' + self.request.GET.get('returnto') if 'returnto' in self.request.GET else '')
|
||||
'subevent': self.object.pk,
|
||||
})
|
||||
|
||||
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/detail.html'
|
||||
template_name = 'pretixcontrol/subevents/edit.html'
|
||||
permission = 'event.subevents:write'
|
||||
context_object_name = 'subevent'
|
||||
form_class = SubEventForm
|
||||
|
||||
@@ -145,11 +145,21 @@ def event_list(request):
|
||||
if 'can_copy' in request.GET:
|
||||
qs = EventWizardCopyForm.copy_from_queryset(request.user, request.session)
|
||||
else:
|
||||
qs = request.user.get_events_with_any_permission(request)
|
||||
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 = qs.filter(
|
||||
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query) |
|
||||
Q(organizer__name__icontains=i18ncomp(query)) | Q(organizer__slug__icontains=query)
|
||||
name_slug_q
|
||||
).annotate(
|
||||
min_from=Min('subevents__date_from'),
|
||||
max_from=Max('subevents__date_from'),
|
||||
@@ -162,10 +172,19 @@ 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': [
|
||||
serialize_event(e) for e in qs.select_related('organizer')[offset:offset + pagesize]
|
||||
],
|
||||
'results': results,
|
||||
'pagination': {
|
||||
"more": total >= (offset + pagesize)
|
||||
}
|
||||
@@ -181,7 +200,7 @@ def giftcard_select2(request, **kwargs):
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:write', request):
|
||||
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:read', request):
|
||||
qs = request.organizer.issued_gift_cards.filter(
|
||||
Q(secret__icontains=query)
|
||||
).order_by('secret')
|
||||
|
||||
@@ -77,7 +77,7 @@ from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import eventreverse_absolute
|
||||
|
||||
|
||||
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
@@ -338,7 +338,7 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
}
|
||||
if self.object.subevent_id:
|
||||
url_params['subevent'] = self.object.subevent_id
|
||||
ctx['url'] = build_absolute_uri(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
|
||||
ctx['url'] = eventreverse_absolute(self.request.event, "presale:event.redeem") + "?" + urlencode(url_params)
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ from urllib3.util.connection import (
|
||||
)
|
||||
from urllib3.util.timeout import _DEFAULT_TIMEOUT
|
||||
|
||||
_cgnat_net = ipaddress.ip_network('100.64.0.0/10')
|
||||
|
||||
|
||||
def monkeypatch_vobject_performance():
|
||||
"""
|
||||
@@ -152,6 +154,8 @@ def monkeypatch_urllib3_ssrf_protection():
|
||||
raise HTTPError(f"Request to local address {sa[0]} blocked")
|
||||
if ip_addr.is_private:
|
||||
raise HTTPError(f"Request to private address {sa[0]} blocked")
|
||||
if ip_addr in _cgnat_net:
|
||||
raise HTTPError(f"Request to RFC 6598 address {sa[0]} blocked")
|
||||
|
||||
sock = None
|
||||
try:
|
||||
|
||||
@@ -35,7 +35,7 @@ from geoip2.errors import AddressNotFoundError
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.helpers.http import get_client_ip
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import mainreverse_absolute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -175,7 +175,7 @@ def handle_login_source(user, request):
|
||||
'source': src,
|
||||
'country': Country(str(country)).name if country else _('Unknown country'),
|
||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||
'url': build_absolute_uri('control:user.settings')
|
||||
'url': mainreverse_absolute('control:user.settings')
|
||||
},
|
||||
event=None,
|
||||
user=user,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import warnings
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
@@ -26,6 +27,15 @@ from django.urls import reverse
|
||||
|
||||
|
||||
def build_absolute_uri(urlname, args=None, kwargs=None):
|
||||
warnings.warn(
|
||||
'Usage of build_absolute_uri is confusing since there are many functions with that name. '
|
||||
'Replace this usage with ',
|
||||
DeprecationWarning
|
||||
)
|
||||
return mainreverse_absolute(urlname, args, kwargs)
|
||||
|
||||
|
||||
def mainreverse_absolute(urlname, args=None, kwargs=None):
|
||||
from pretix.multidomain import maindomain_urlconf
|
||||
|
||||
return urljoin(settings.SITE_URL, reverse(urlname, args=args, kwargs=kwargs, urlconf=maindomain_urlconf))
|
||||
|
||||
+1507
-1421
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"POT-Creation-Date: 2026-05-27 14:47+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -130,7 +130,6 @@ msgid "Mercado Pago"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:167
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:89
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
@@ -184,172 +183,6 @@ msgstr ""
|
||||
msgid "Contacting your bank …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Confirmed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket not paid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Additional information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Valid ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Exit recorded"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Ticket already used"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
msgid "Unknown ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Entry not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
msgid "Ticket blocked"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Order canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
|
||||
msgid "Order not approved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
||||
msgid "Valid Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:72
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
@@ -376,46 +209,46 @@ msgid ""
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixbase/js/asynctask.js:182
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixbase/js/asynctask.js:135
|
||||
#: pretix/static/pretixbase/js/asynctask.js:192
|
||||
#: pretix/static/pretixbase/js/asynctask.js:196
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:128
|
||||
#: pretix/static/pretixbase/js/asynctask.js:138
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:162
|
||||
#: pretix/static/pretixbase/js/asynctask.js:172
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:188
|
||||
#: pretix/static/pretixbase/js/asynctask.js:198
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
#: pretix/static/pretixbase/js/asynctask.js:226
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:219
|
||||
#: pretix/static/pretixbase/js/asynctask.js:229
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
#: pretix/static/pretixbase/js/asynctask.js:286
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
#: pretix/static/pretixbase/js/asynctask.js:341
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
|
||||
@@ -427,146 +260,6 @@ msgstr ""
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:12
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
|
||||
msgid "is one of"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
|
||||
msgid "is before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:34
|
||||
msgid "is after"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
|
||||
msgid "="
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
|
||||
msgid "Product variation"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
|
||||
msgid "Gate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
|
||||
msgid "Current date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
|
||||
msgid "Current entry status"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
|
||||
msgid "Number of previous entries"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:127
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
|
||||
msgid "Number of previous entries since"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
|
||||
msgid "Number of previous entries before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
|
||||
msgid "Number of days with a previous entry since"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
|
||||
msgid "Number of days with a previous entry before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
|
||||
msgid "Event start"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:185
|
||||
msgid "Event end"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:186
|
||||
msgid "Event admission"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
|
||||
msgid "custom date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
|
||||
msgid "custom time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:189
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:190
|
||||
msgid "Add condition"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:171
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
@@ -701,6 +394,16 @@ msgstr ""
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
|
||||
+1611
-1522
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"POT-Creation-Date: 2026-05-27 14:47+0000\n"
|
||||
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
||||
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -134,7 +134,6 @@ msgid "Mercado Pago"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:167
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:89
|
||||
msgid "Continue"
|
||||
msgstr "المتابعة"
|
||||
@@ -188,180 +187,6 @@ msgstr "المجموع"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "جاري الاتصال بالبنك الذي تتعامل معه …"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr "اختر قائمة الدخول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr "لم يتم العثور على قوائم دخول نشطة."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr "تبديل قائمة الدخول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr "البحث في النتائج"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr "لم يتم العثور على تذاكر"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr "النتيجة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr "تحتاج هذه التذكرة إلى إهتمام خاص"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr "تغيير المسار"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr "دخول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr "خروج"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr "قم بمسح التذكرة أو إبحث واضغط زر العودة…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr "تحميل المزيد"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr "ساري المفعول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr "غير مدفوع"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr "ملغاة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Confirmed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
msgstr "مستخدم"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
msgid "Cancel"
|
||||
msgstr "قم بالإلغاء"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket not paid"
|
||||
msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr "لم يتم دفع قيمة التذكرة بعد، هل تريد المتابعة على أي حال؟"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Additional information required"
|
||||
msgstr "مطلوب معلومات إضافية"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Valid ticket"
|
||||
msgstr "تذكرة سارية المفعول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Exit recorded"
|
||||
msgstr "تم تسجيل الخروج"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Ticket already used"
|
||||
msgstr "تم استخدام التذكرة مسبقا"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Information required"
|
||||
msgstr "معلومات مطلوبة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
#, fuzzy
|
||||
#| msgid "Unknown error."
|
||||
msgid "Unknown ticket"
|
||||
msgstr "خطأ غير معروف."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
#, fuzzy
|
||||
#| msgid "Entry not allowed"
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr "إدخال غير مسموح"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Entry not allowed"
|
||||
msgstr "إدخال غير مسموح"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr "تم إلغاء رمز التذكرة أو تبديله"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
#, fuzzy
|
||||
#| msgid "Ticket not paid"
|
||||
msgid "Ticket blocked"
|
||||
msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
#, fuzzy
|
||||
#| msgid "Ticket not paid"
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Order canceled"
|
||||
msgstr "تم إلغاء الطلب"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
|
||||
msgid "Order not approved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr "تذاكر الدخول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
||||
msgid "Valid Tickets"
|
||||
msgstr "تذاكر سارية المفعول"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
|
||||
msgid "Currently inside"
|
||||
msgstr "حاليا بالداخل"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr "نعم"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:72
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr "لا"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
@@ -395,36 +220,36 @@ msgstr ""
|
||||
"وصل طلبك للخادم وننتظر تنفيذه. إذا استغرق الأمر أكثر من دقيقتين تواصل معنا "
|
||||
"أو عاود المحاولة مجددا."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixbase/js/asynctask.js:182
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixbase/js/asynctask.js:135
|
||||
#: pretix/static/pretixbase/js/asynctask.js:192
|
||||
#: pretix/static/pretixbase/js/asynctask.js:196
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "حدث خطأ من نوع {code}."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:128
|
||||
#: pretix/static/pretixbase/js/asynctask.js:138
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr "لم نتمكن من الاتصال بالخادم، لكن سنواصل المحاولة، رمز آخر خطأ: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:162
|
||||
#: pretix/static/pretixbase/js/asynctask.js:172
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "استغرقت الطلب فترة طويلة، الرجاء المحاولة مرة أخرى."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:188
|
||||
#: pretix/static/pretixbase/js/asynctask.js:198
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"لا يمكننا الوصول إلى الخادم حاليا، حاول مرة أخرى من فضلك. رمز الخطأ : {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
#: pretix/static/pretixbase/js/asynctask.js:226
|
||||
msgid "We are processing your request …"
|
||||
msgstr "جاري معالجة طلبك …"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:219
|
||||
#: pretix/static/pretixbase/js/asynctask.js:229
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
@@ -433,11 +258,11 @@ msgstr ""
|
||||
"نعمل الآن على ارسال طلبك إلى الخادم، إذا أستغرقت العملية أكثر من دقيقة، يرجى "
|
||||
"التحقق من اتصالك بالإنترنت ثم أعد تحميل الصفحة مرة أخرى."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
#: pretix/static/pretixbase/js/asynctask.js:286
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
#: pretix/static/pretixbase/js/asynctask.js:341
|
||||
msgid "Close message"
|
||||
msgstr "أغلق الرسالة"
|
||||
|
||||
@@ -449,154 +274,6 @@ msgstr "تم النسخ!"
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr "للنسخ اضغط Ctrl + C!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:12
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
|
||||
msgid "is one of"
|
||||
msgstr "واحد من"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
|
||||
msgid "is before"
|
||||
msgstr "قبل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:34
|
||||
msgid "is after"
|
||||
msgstr "بعد"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
|
||||
msgid "="
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
|
||||
msgid "Product"
|
||||
msgstr "منتج"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
|
||||
msgid "Product variation"
|
||||
msgstr "نوع المنتج"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
|
||||
msgid "Gate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
|
||||
msgid "Current date and time"
|
||||
msgstr "التاريخ والوقت الحالي"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
|
||||
msgid "Current entry status"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
|
||||
msgid "Number of previous entries"
|
||||
msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:127
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr "عدد المدخلات السابقة قبل منتصف الليل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
|
||||
#, fuzzy
|
||||
#| msgid "Number of previous entries"
|
||||
msgid "Number of previous entries since"
|
||||
msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
|
||||
#, fuzzy
|
||||
#| msgid "Number of previous entries"
|
||||
msgid "Number of previous entries before"
|
||||
msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
|
||||
#, fuzzy
|
||||
#| msgid "Number of days with a previous entry"
|
||||
msgid "Number of days with a previous entry since"
|
||||
msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
|
||||
#, fuzzy
|
||||
#| msgid "Number of days with a previous entry"
|
||||
msgid "Number of days with a previous entry before"
|
||||
msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr "جميع الشروط في الأسفل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr "خيار واحد على الأقل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
|
||||
msgid "Event start"
|
||||
msgstr "بداية الفعالية"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:185
|
||||
msgid "Event end"
|
||||
msgstr "نهاية الفعالية"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:186
|
||||
msgid "Event admission"
|
||||
msgstr "تسجيل الفعالية"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
|
||||
msgid "custom date and time"
|
||||
msgstr "تحديد التاريخ والوقت"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
|
||||
msgid "custom time"
|
||||
msgstr "الوقت المحدد"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:189
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr "القدرة على التحمل (الدقائق)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:190
|
||||
msgid "Add condition"
|
||||
msgstr "أضف شرطا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
|
||||
msgid "minutes"
|
||||
msgstr "الدقائق"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:171
|
||||
msgid "Check-in QR"
|
||||
msgstr "QR الدخول"
|
||||
@@ -741,6 +418,16 @@ msgstr "غير ذلك"
|
||||
msgid "Count"
|
||||
msgstr "احسب"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr "نعم"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr "لا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
@@ -1260,6 +947,193 @@ msgstr "نوفمبر"
|
||||
msgid "December"
|
||||
msgstr "ديسمبر"
|
||||
|
||||
#~ msgid "Select a check-in list"
|
||||
#~ msgstr "اختر قائمة الدخول"
|
||||
|
||||
#~ msgid "No active check-in lists found."
|
||||
#~ msgstr "لم يتم العثور على قوائم دخول نشطة."
|
||||
|
||||
#~ msgid "Switch check-in list"
|
||||
#~ msgstr "تبديل قائمة الدخول"
|
||||
|
||||
#~ msgid "Search results"
|
||||
#~ msgstr "البحث في النتائج"
|
||||
|
||||
#~ msgid "No tickets found"
|
||||
#~ msgstr "لم يتم العثور على تذاكر"
|
||||
|
||||
#~ msgid "Result"
|
||||
#~ msgstr "النتيجة"
|
||||
|
||||
#~ msgid "This ticket requires special attention"
|
||||
#~ msgstr "تحتاج هذه التذكرة إلى إهتمام خاص"
|
||||
|
||||
#~ msgid "Switch direction"
|
||||
#~ msgstr "تغيير المسار"
|
||||
|
||||
#~ msgid "Entry"
|
||||
#~ msgstr "دخول"
|
||||
|
||||
#~ msgid "Exit"
|
||||
#~ msgstr "خروج"
|
||||
|
||||
#~ msgid "Scan a ticket or search and press return…"
|
||||
#~ msgstr "قم بمسح التذكرة أو إبحث واضغط زر العودة…"
|
||||
|
||||
#~ msgid "Load more"
|
||||
#~ msgstr "تحميل المزيد"
|
||||
|
||||
#~ msgid "Valid"
|
||||
#~ msgstr "ساري المفعول"
|
||||
|
||||
#~ msgid "Unpaid"
|
||||
#~ msgstr "غير مدفوع"
|
||||
|
||||
#~ msgid "Canceled"
|
||||
#~ msgstr "ملغاة"
|
||||
|
||||
#~ msgid "Redeemed"
|
||||
#~ msgstr "مستخدم"
|
||||
|
||||
#~ msgid "Cancel"
|
||||
#~ msgstr "قم بالإلغاء"
|
||||
|
||||
#~ msgid "Ticket not paid"
|
||||
#~ msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#~ msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
#~ msgstr "لم يتم دفع قيمة التذكرة بعد، هل تريد المتابعة على أي حال؟"
|
||||
|
||||
#~ msgid "Additional information required"
|
||||
#~ msgstr "مطلوب معلومات إضافية"
|
||||
|
||||
#~ msgid "Valid ticket"
|
||||
#~ msgstr "تذكرة سارية المفعول"
|
||||
|
||||
#~ msgid "Exit recorded"
|
||||
#~ msgstr "تم تسجيل الخروج"
|
||||
|
||||
#~ msgid "Ticket already used"
|
||||
#~ msgstr "تم استخدام التذكرة مسبقا"
|
||||
|
||||
#~ msgid "Information required"
|
||||
#~ msgstr "معلومات مطلوبة"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Unknown error."
|
||||
#~ msgid "Unknown ticket"
|
||||
#~ msgstr "خطأ غير معروف."
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Entry not allowed"
|
||||
#~ msgid "Ticket type not allowed here"
|
||||
#~ msgstr "إدخال غير مسموح"
|
||||
|
||||
#~ msgid "Entry not allowed"
|
||||
#~ msgstr "إدخال غير مسموح"
|
||||
|
||||
#~ msgid "Ticket code revoked/changed"
|
||||
#~ msgstr "تم إلغاء رمز التذكرة أو تبديله"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Ticket not paid"
|
||||
#~ msgid "Ticket blocked"
|
||||
#~ msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Ticket not paid"
|
||||
#~ msgid "Ticket not valid at this time"
|
||||
#~ msgstr "لم يتم دفع قيمة التذكرة"
|
||||
|
||||
#~ msgid "Order canceled"
|
||||
#~ msgstr "تم إلغاء الطلب"
|
||||
|
||||
#~ msgid "Checked-in Tickets"
|
||||
#~ msgstr "تذاكر الدخول"
|
||||
|
||||
#~ msgid "Valid Tickets"
|
||||
#~ msgstr "تذاكر سارية المفعول"
|
||||
|
||||
#~ msgid "Currently inside"
|
||||
#~ msgstr "حاليا بالداخل"
|
||||
|
||||
#~ msgid "is one of"
|
||||
#~ msgstr "واحد من"
|
||||
|
||||
#~ msgid "is before"
|
||||
#~ msgstr "قبل"
|
||||
|
||||
#~ msgid "is after"
|
||||
#~ msgstr "بعد"
|
||||
|
||||
#~ msgid "Product"
|
||||
#~ msgstr "منتج"
|
||||
|
||||
#~ msgid "Product variation"
|
||||
#~ msgstr "نوع المنتج"
|
||||
|
||||
#~ msgid "Current date and time"
|
||||
#~ msgstr "التاريخ والوقت الحالي"
|
||||
|
||||
#~ msgid "Number of previous entries"
|
||||
#~ msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#~ msgid "Number of previous entries since midnight"
|
||||
#~ msgstr "عدد المدخلات السابقة قبل منتصف الليل"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Number of previous entries"
|
||||
#~ msgid "Number of previous entries since"
|
||||
#~ msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Number of previous entries"
|
||||
#~ msgid "Number of previous entries before"
|
||||
#~ msgstr "عدد المدخلات السابقة"
|
||||
|
||||
#~ msgid "Number of days with a previous entry"
|
||||
#~ msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Number of days with a previous entry"
|
||||
#~ msgid "Number of days with a previous entry since"
|
||||
#~ msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Number of days with a previous entry"
|
||||
#~ msgid "Number of days with a previous entry before"
|
||||
#~ msgstr "عدد الأيام التي تحتوي على مدخل سابق"
|
||||
|
||||
#~ msgid "All of the conditions below (AND)"
|
||||
#~ msgstr "جميع الشروط في الأسفل"
|
||||
|
||||
#~ msgid "At least one of the conditions below (OR)"
|
||||
#~ msgstr "خيار واحد على الأقل"
|
||||
|
||||
#~ msgid "Event start"
|
||||
#~ msgstr "بداية الفعالية"
|
||||
|
||||
#~ msgid "Event end"
|
||||
#~ msgstr "نهاية الفعالية"
|
||||
|
||||
#~ msgid "Event admission"
|
||||
#~ msgstr "تسجيل الفعالية"
|
||||
|
||||
#~ msgid "custom date and time"
|
||||
#~ msgstr "تحديد التاريخ والوقت"
|
||||
|
||||
#~ msgid "custom time"
|
||||
#~ msgstr "الوقت المحدد"
|
||||
|
||||
#~ msgid "Tolerance (minutes)"
|
||||
#~ msgstr "القدرة على التحمل (الدقائق)"
|
||||
|
||||
#~ msgid "Add condition"
|
||||
#~ msgstr "أضف شرطا"
|
||||
|
||||
#~ msgid "minutes"
|
||||
#~ msgstr "الدقائق"
|
||||
|
||||
#~ msgid "Time zone:"
|
||||
#~ msgstr "المنطقة الزمنية:"
|
||||
|
||||
|
||||
+1506
-1420
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"POT-Creation-Date: 2026-05-27 14:47+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -130,7 +130,6 @@ msgid "Mercado Pago"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:167
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:89
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
@@ -184,172 +183,6 @@ msgstr ""
|
||||
msgid "Contacting your bank …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Confirmed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket not paid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Additional information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Valid ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Exit recorded"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Ticket already used"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
msgid "Unknown ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Entry not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
msgid "Ticket blocked"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Order canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
|
||||
msgid "Order not approved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
||||
msgid "Valid Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:72
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
@@ -376,46 +209,46 @@ msgid ""
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixbase/js/asynctask.js:182
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixbase/js/asynctask.js:135
|
||||
#: pretix/static/pretixbase/js/asynctask.js:192
|
||||
#: pretix/static/pretixbase/js/asynctask.js:196
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:128
|
||||
#: pretix/static/pretixbase/js/asynctask.js:138
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:162
|
||||
#: pretix/static/pretixbase/js/asynctask.js:172
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:188
|
||||
#: pretix/static/pretixbase/js/asynctask.js:198
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
#: pretix/static/pretixbase/js/asynctask.js:226
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:219
|
||||
#: pretix/static/pretixbase/js/asynctask.js:229
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
#: pretix/static/pretixbase/js/asynctask.js:286
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
#: pretix/static/pretixbase/js/asynctask.js:341
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
|
||||
@@ -427,146 +260,6 @@ msgstr ""
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:12
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
|
||||
msgid "is one of"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
|
||||
msgid "is before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:34
|
||||
msgid "is after"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
|
||||
msgid "="
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
|
||||
msgid "Product variation"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
|
||||
msgid "Gate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
|
||||
msgid "Current date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
|
||||
msgid "Current entry status"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
|
||||
msgid "Number of previous entries"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:127
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
|
||||
msgid "Number of previous entries since"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
|
||||
msgid "Number of previous entries before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
|
||||
msgid "Number of days with a previous entry since"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
|
||||
msgid "Number of days with a previous entry before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
|
||||
msgid "Event start"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:185
|
||||
msgid "Event end"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:186
|
||||
msgid "Event admission"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
|
||||
msgid "custom date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
|
||||
msgid "custom time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:189
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:190
|
||||
msgid "Add condition"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:171
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
@@ -701,6 +394,16 @@ msgstr ""
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
|
||||
+1551
-1431
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"POT-Creation-Date: 2026-05-27 14:47+0000\n"
|
||||
"PO-Revision-Date: 2025-10-31 17:00+0000\n"
|
||||
"Last-Translator: Núria Masclans <nuriamasclansserrat@gmail.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -131,7 +131,6 @@ msgid "Mercado Pago"
|
||||
msgstr "Mercado Pago"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:167
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:89
|
||||
msgid "Continue"
|
||||
msgstr "Continua"
|
||||
@@ -185,172 +184,6 @@ msgstr "Total"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Contactant amb el teu banc…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr "Selecciona una llista d'acreditació"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr "No s'ha trobat cap lllista d'acreditació activa."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr "Canvia la llista d'acreditació"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr "Resultats de la cerca"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr "No s'han trobat entrades"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr "Resultat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr "Aquesta entrada requereix atenció especial"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr "Canvia la direcció"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr "Entrada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr "Sortida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr "Escaneja l'entrada o cerca i prem Retorn…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr "Mostra'n més"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr "Vàlid"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr "No pagat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr "Cancel·lat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Confirmed"
|
||||
msgstr "Confirmat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr "Pendent d'aprovació"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
msgstr "Reemborsat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel·lar"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket not paid"
|
||||
msgstr "Entrada no pagada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr "Aquesta entrada no està pagada. Vols continuar igualment?"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Additional information required"
|
||||
msgstr "Cal informació addicional"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Valid ticket"
|
||||
msgstr "Entrada vàlida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Exit recorded"
|
||||
msgstr "Sortida registrada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Ticket already used"
|
||||
msgstr "Entrada ja utilitzada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Information required"
|
||||
msgstr "Informació requerida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
msgid "Unknown ticket"
|
||||
msgstr "Entrada desconeguda"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr "Tipus d'entrada no permès aquí"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Entry not allowed"
|
||||
msgstr "Entrada no permesa"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr "Codi d’entrada anul·lat o modificat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
msgid "Ticket blocked"
|
||||
msgstr "Entrada bloquejada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr "Entrada no vàlida en aquest moment"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Order canceled"
|
||||
msgstr "S'ha cancel·lat la comanda"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr "Codi de l’entrada ambigu a la llista"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
|
||||
msgid "Order not approved"
|
||||
msgstr "Comanda no aprovada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr "Entrades registrades"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:69
|
||||
msgid "Valid Tickets"
|
||||
msgstr "Entrades vàlides"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
|
||||
msgid "Currently inside"
|
||||
msgstr "Actualment dins"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:72
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
msgid "close"
|
||||
msgstr "Tanca"
|
||||
@@ -383,14 +216,14 @@ msgstr ""
|
||||
"Si aquesta acció triga més de 2 minuts, contacta'ns o torna enrere al "
|
||||
"navegador i prova-ho de nou."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixbase/js/asynctask.js:182
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixbase/js/asynctask.js:135
|
||||
#: pretix/static/pretixbase/js/asynctask.js:192
|
||||
#: pretix/static/pretixbase/js/asynctask.js:196
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "S'ha produït un error de tipus {code}."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:128
|
||||
#: pretix/static/pretixbase/js/asynctask.js:138
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
@@ -398,23 +231,23 @@ msgstr ""
|
||||
"Actualment no podem connectar amb el servidor, però seguim intentant-ho. "
|
||||
"Últim codi d’error: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:162
|
||||
#: pretix/static/pretixbase/js/asynctask.js:172
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Temps d’espera esgotat. Torna-ho a provar."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:188
|
||||
#: pretix/static/pretixbase/js/asynctask.js:198
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"No es pot connectar amb el servidor. Torna-ho a provar. Codi d’error: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
#: pretix/static/pretixbase/js/asynctask.js:226
|
||||
msgid "We are processing your request …"
|
||||
msgstr "Estem processant la vostra sol·licitud …"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:219
|
||||
#: pretix/static/pretixbase/js/asynctask.js:229
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
@@ -423,11 +256,11 @@ msgstr ""
|
||||
"Hem enviat la teva petició al servidor. Si triga més d'1 minut, comprova la "
|
||||
"connexió, recarrega la pàgina i torna-ho a provar."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
#: pretix/static/pretixbase/js/asynctask.js:286
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr "Si triga més de pocs minuts, contacta'ns."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
#: pretix/static/pretixbase/js/asynctask.js:341
|
||||
msgid "Close message"
|
||||
msgstr "Tanca el missatge"
|
||||
|
||||
@@ -439,146 +272,6 @@ msgstr "Copiat!"
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr "Prem Ctrl-C per copiar!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:12
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
|
||||
msgid "is one of"
|
||||
msgstr "forma part de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
|
||||
msgid "is before"
|
||||
msgstr "abans de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:34
|
||||
msgid "is after"
|
||||
msgstr "després de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
|
||||
msgid "="
|
||||
msgstr "="
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
|
||||
msgid "Product"
|
||||
msgstr "Producte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
|
||||
msgid "Product variation"
|
||||
msgstr "Variació del producte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
|
||||
msgid "Gate"
|
||||
msgstr "Porta"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
|
||||
msgid "Current date and time"
|
||||
msgstr "Data i hora actuals"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr "Dia actual de la setmana (1 = Dilluns, 7 = Diumenge)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
|
||||
msgid "Current entry status"
|
||||
msgstr "Estat actual de l'entrada"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
|
||||
msgid "Number of previous entries"
|
||||
msgstr "Nombre d'entrades anteriors"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:127
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr "Nombre d’entrades anteriors des de la mitjanit"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
|
||||
msgid "Number of previous entries since"
|
||||
msgstr "Nombre d’entrades anteriors des de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
|
||||
msgid "Number of previous entries before"
|
||||
msgstr "Nombre d’entrades anteriors abans de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr "Nombre de dies amb una entrada anterior"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
|
||||
msgid "Number of days with a previous entry since"
|
||||
msgstr "Nombre de dies amb una entrada anterior des de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
|
||||
msgid "Number of days with a previous entry before"
|
||||
msgstr "Nombre de dies amb una entrada anterior abans de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr "Minuts des de l'última entrada (-1 per la primera entrada)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr "Minuts des de la primera entrada (-1 a la primera entrada)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr "Complir totes les condicions següents"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr "Complir almenys una de les condicions següents"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
|
||||
msgid "Event start"
|
||||
msgstr "Inici de l'esdeveniment"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:185
|
||||
msgid "Event end"
|
||||
msgstr "Finalització de l'esdeveniment"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:186
|
||||
msgid "Event admission"
|
||||
msgstr "Admissió de l'esdeveniment"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
|
||||
msgid "custom date and time"
|
||||
msgstr "Data i hora personalitzades"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
|
||||
msgid "custom time"
|
||||
msgstr "Hora personalitzada"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:189
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr "Tolerància (minuts)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:190
|
||||
msgid "Add condition"
|
||||
msgstr "Afegeix condició"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
|
||||
msgid "minutes"
|
||||
msgstr "en minuts"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
|
||||
msgid "Duplicate"
|
||||
msgstr "Duplicat"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr "present"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr "absent"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr "Error: Producte no trobat!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
msgstr "Error: Variació no trobada!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:171
|
||||
msgid "Check-in QR"
|
||||
msgstr "Registre amb codi QR"
|
||||
@@ -717,6 +410,16 @@ msgstr "Altres"
|
||||
msgid "Count"
|
||||
msgstr "Quantitat"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:271
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
@@ -1193,5 +896,223 @@ msgstr ""
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Select a check-in list"
|
||||
#~ msgstr "Selecciona una llista d'acreditació"
|
||||
|
||||
#~ msgid "No active check-in lists found."
|
||||
#~ msgstr "No s'ha trobat cap lllista d'acreditació activa."
|
||||
|
||||
#~ msgid "Switch check-in list"
|
||||
#~ msgstr "Canvia la llista d'acreditació"
|
||||
|
||||
#~ msgid "Search results"
|
||||
#~ msgstr "Resultats de la cerca"
|
||||
|
||||
#~ msgid "No tickets found"
|
||||
#~ msgstr "No s'han trobat entrades"
|
||||
|
||||
#~ msgid "Result"
|
||||
#~ msgstr "Resultat"
|
||||
|
||||
#~ msgid "This ticket requires special attention"
|
||||
#~ msgstr "Aquesta entrada requereix atenció especial"
|
||||
|
||||
#~ msgid "Switch direction"
|
||||
#~ msgstr "Canvia la direcció"
|
||||
|
||||
#~ msgid "Entry"
|
||||
#~ msgstr "Entrada"
|
||||
|
||||
#~ msgid "Exit"
|
||||
#~ msgstr "Sortida"
|
||||
|
||||
#~ msgid "Scan a ticket or search and press return…"
|
||||
#~ msgstr "Escaneja l'entrada o cerca i prem Retorn…"
|
||||
|
||||
#~ msgid "Load more"
|
||||
#~ msgstr "Mostra'n més"
|
||||
|
||||
#~ msgid "Valid"
|
||||
#~ msgstr "Vàlid"
|
||||
|
||||
#~ msgid "Unpaid"
|
||||
#~ msgstr "No pagat"
|
||||
|
||||
#~ msgid "Canceled"
|
||||
#~ msgstr "Cancel·lat"
|
||||
|
||||
#~ msgid "Confirmed"
|
||||
#~ msgstr "Confirmat"
|
||||
|
||||
#~ msgid "Approval pending"
|
||||
#~ msgstr "Pendent d'aprovació"
|
||||
|
||||
#~ msgid "Redeemed"
|
||||
#~ msgstr "Reemborsat"
|
||||
|
||||
#~ msgid "Cancel"
|
||||
#~ msgstr "Cancel·lar"
|
||||
|
||||
#~ msgid "Ticket not paid"
|
||||
#~ msgstr "Entrada no pagada"
|
||||
|
||||
#~ msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
#~ msgstr "Aquesta entrada no està pagada. Vols continuar igualment?"
|
||||
|
||||
#~ msgid "Additional information required"
|
||||
#~ msgstr "Cal informació addicional"
|
||||
|
||||
#~ msgid "Valid ticket"
|
||||
#~ msgstr "Entrada vàlida"
|
||||
|
||||
#~ msgid "Exit recorded"
|
||||
#~ msgstr "Sortida registrada"
|
||||
|
||||
#~ msgid "Ticket already used"
|
||||
#~ msgstr "Entrada ja utilitzada"
|
||||
|
||||
#~ msgid "Information required"
|
||||
#~ msgstr "Informació requerida"
|
||||
|
||||
#~ msgid "Unknown ticket"
|
||||
#~ msgstr "Entrada desconeguda"
|
||||
|
||||
#~ msgid "Ticket type not allowed here"
|
||||
#~ msgstr "Tipus d'entrada no permès aquí"
|
||||
|
||||
#~ msgid "Entry not allowed"
|
||||
#~ msgstr "Entrada no permesa"
|
||||
|
||||
#~ msgid "Ticket code revoked/changed"
|
||||
#~ msgstr "Codi d’entrada anul·lat o modificat"
|
||||
|
||||
#~ msgid "Ticket blocked"
|
||||
#~ msgstr "Entrada bloquejada"
|
||||
|
||||
#~ msgid "Ticket not valid at this time"
|
||||
#~ msgstr "Entrada no vàlida en aquest moment"
|
||||
|
||||
#~ msgid "Order canceled"
|
||||
#~ msgstr "S'ha cancel·lat la comanda"
|
||||
|
||||
#~ msgid "Ticket code is ambiguous on list"
|
||||
#~ msgstr "Codi de l’entrada ambigu a la llista"
|
||||
|
||||
#~ msgid "Order not approved"
|
||||
#~ msgstr "Comanda no aprovada"
|
||||
|
||||
#~ msgid "Checked-in Tickets"
|
||||
#~ msgstr "Entrades registrades"
|
||||
|
||||
#~ msgid "Valid Tickets"
|
||||
#~ msgstr "Entrades vàlides"
|
||||
|
||||
#~ msgid "Currently inside"
|
||||
#~ msgstr "Actualment dins"
|
||||
|
||||
#~ msgid "is one of"
|
||||
#~ msgstr "forma part de"
|
||||
|
||||
#~ msgid "is before"
|
||||
#~ msgstr "abans de"
|
||||
|
||||
#~ msgid "is after"
|
||||
#~ msgstr "després de"
|
||||
|
||||
#~ msgid "="
|
||||
#~ msgstr "="
|
||||
|
||||
#~ msgid "Product"
|
||||
#~ msgstr "Producte"
|
||||
|
||||
#~ msgid "Product variation"
|
||||
#~ msgstr "Variació del producte"
|
||||
|
||||
#~ msgid "Gate"
|
||||
#~ msgstr "Porta"
|
||||
|
||||
#~ msgid "Current date and time"
|
||||
#~ msgstr "Data i hora actuals"
|
||||
|
||||
#~ msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
#~ msgstr "Dia actual de la setmana (1 = Dilluns, 7 = Diumenge)"
|
||||
|
||||
#~ msgid "Current entry status"
|
||||
#~ msgstr "Estat actual de l'entrada"
|
||||
|
||||
#~ msgid "Number of previous entries"
|
||||
#~ msgstr "Nombre d'entrades anteriors"
|
||||
|
||||
#~ msgid "Number of previous entries since midnight"
|
||||
#~ msgstr "Nombre d’entrades anteriors des de la mitjanit"
|
||||
|
||||
#~ msgid "Number of previous entries since"
|
||||
#~ msgstr "Nombre d’entrades anteriors des de"
|
||||
|
||||
#~ msgid "Number of previous entries before"
|
||||
#~ msgstr "Nombre d’entrades anteriors abans de"
|
||||
|
||||
#~ msgid "Number of days with a previous entry"
|
||||
#~ msgstr "Nombre de dies amb una entrada anterior"
|
||||
|
||||
#~ msgid "Number of days with a previous entry since"
|
||||
#~ msgstr "Nombre de dies amb una entrada anterior des de"
|
||||
|
||||
#~ msgid "Number of days with a previous entry before"
|
||||
#~ msgstr "Nombre de dies amb una entrada anterior abans de"
|
||||
|
||||
#~ msgid "Minutes since last entry (-1 on first entry)"
|
||||
#~ msgstr "Minuts des de l'última entrada (-1 per la primera entrada)"
|
||||
|
||||
#~ msgid "Minutes since first entry (-1 on first entry)"
|
||||
#~ msgstr "Minuts des de la primera entrada (-1 a la primera entrada)"
|
||||
|
||||
#~ msgid "All of the conditions below (AND)"
|
||||
#~ msgstr "Complir totes les condicions següents"
|
||||
|
||||
#~ msgid "At least one of the conditions below (OR)"
|
||||
#~ msgstr "Complir almenys una de les condicions següents"
|
||||
|
||||
#~ msgid "Event start"
|
||||
#~ msgstr "Inici de l'esdeveniment"
|
||||
|
||||
#~ msgid "Event end"
|
||||
#~ msgstr "Finalització de l'esdeveniment"
|
||||
|
||||
#~ msgid "Event admission"
|
||||
#~ msgstr "Admissió de l'esdeveniment"
|
||||
|
||||
#~ msgid "custom date and time"
|
||||
#~ msgstr "Data i hora personalitzades"
|
||||
|
||||
#~ msgid "custom time"
|
||||
#~ msgstr "Hora personalitzada"
|
||||
|
||||
#~ msgid "Tolerance (minutes)"
|
||||
#~ msgstr "Tolerància (minuts)"
|
||||
|
||||
#~ msgid "Add condition"
|
||||
#~ msgstr "Afegeix condició"
|
||||
|
||||
#~ msgid "minutes"
|
||||
#~ msgstr "en minuts"
|
||||
|
||||
#~ msgid "Duplicate"
|
||||
#~ msgstr "Duplicat"
|
||||
|
||||
#~ msgctxt "entry_status"
|
||||
#~ msgid "present"
|
||||
#~ msgstr "present"
|
||||
|
||||
#~ msgctxt "entry_status"
|
||||
#~ msgid "absent"
|
||||
#~ msgstr "absent"
|
||||
|
||||
#~ msgid "Error: Product not found!"
|
||||
#~ msgstr "Error: Producte no trobat!"
|
||||
|
||||
#~ msgid "Error: Variation not found!"
|
||||
#~ msgstr "Error: Variació no trobada!"
|
||||
|
||||
#~ msgid "iDEAL"
|
||||
#~ msgstr "iDEAL"
|
||||
|
||||
+1577
-1430
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user