forked from CGM_Public/pretix_original
Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 118c206bb1 | |||
| a56c6ae1e0 | |||
| 1efd952a19 | |||
| ddd0db3d98 | |||
| dde724d0be | |||
| 9f55187690 | |||
| e572bfb752 | |||
| beccdf8dad | |||
| b141ea4ed5 | |||
| 32dd125b65 | |||
| b5794780da | |||
| 835c08f1ca | |||
| a79d5fddda | |||
| beb03e07e0 | |||
| dd2e5d09f9 | |||
| 5c2456e92e | |||
| 3579a7f298 | |||
| 5dd20de745 | |||
| 5708099fc9 | |||
| 2c2e8e7d21 | |||
| 5993482f6c | |||
| c905659dfb | |||
| 196c131ac9 | |||
| a554433fad | |||
| c552dd876c | |||
| 105ae8592d | |||
| ca4540eeb7 | |||
| 381366a248 | |||
| 816a3ec994 | |||
| 88d3d12dbc | |||
| 71caa17879 | |||
| 2ecdfde756 | |||
| 1f753a57c5 | |||
| 595c042624 | |||
| 5a5a551c21 | |||
| e74793994a | |||
| f1bdd3b7af | |||
| 13c40f9bb7 | |||
| 3e15e2a887 | |||
| 7525ee853b | |||
| f02b1be659 | |||
| 20f171b790 | |||
| 22906dfa77 | |||
| 5f74e661b3 | |||
| 5b99788354 | |||
| b45d58b60e | |||
| 2a58e958b0 | |||
| e44695dfcf | |||
| 1d289088f4 | |||
| a9be6337bc | |||
| df29d4e8c4 | |||
| bf2cabf7b6 | |||
| db614d36e6 | |||
| 13452b5d8c | |||
| 6422cd7858 | |||
| 2e87cb5691 | |||
| b53ee938bf | |||
| ec3bdd4a57 | |||
| 7b607594d8 | |||
| 53f129d5d3 | |||
| a4385c8b6e | |||
| 3acae96021 | |||
| b9add5ff6f | |||
| 4ca9813a1d | |||
| 347748896d | |||
| 0f590caa18 | |||
| 18801f2d1c | |||
| e5f29bd592 | |||
| 1f904d482b | |||
| b8ad276f53 | |||
| e109c37738 | |||
| 4d597d5be3 | |||
| ae8ec42905 | |||
| e5b89e9b08 | |||
| da91f5f117 | |||
| ae29240e58 | |||
| 74edf10b04 | |||
| e2e0eca872 | |||
| 6132e4a2c4 | |||
| 7df7d28518 | |||
| 11ab5c5eeb | |||
| 20211d2097 | |||
| d760ad38bf | |||
| 69af2cee93 | |||
| 6b199a2b9c | |||
| 94a64ba53a | |||
| 70f06a8f40 | |||
| a747ab154a | |||
| 6317233150 | |||
| 4d94158ff0 | |||
| 8f92eb2d2d | |||
| f29896b267 | |||
| 2dc625cf31 | |||
| 855226d37c | |||
| 648c0da9fe | |||
| 59e3494fa2 | |||
| c4ff57c07a | |||
| cc4fbfe4c7 | |||
| e99ee91573 | |||
| e2753686ee | |||
| 33f8b9851e | |||
| e3d8cf07af | |||
| 0279ca7d94 | |||
| d1989c3cd3 | |||
| 61cb2e15cf | |||
| f2ee1d00b3 | |||
| e8e9698a31 | |||
| a1bf7be244 | |||
| f4ca9a5681 | |||
| e6d984538f | |||
| 9f1ee9157f | |||
| 242e5af4b5 |
@@ -60,6 +60,14 @@ http {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location /static/staticfiles.json {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location /static/CACHE/manifest.json {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location /static/ {
|
||||
alias /pretix/src/pretix/static.dist/;
|
||||
access_log off;
|
||||
|
||||
@@ -248,6 +248,14 @@ The following snippet is an example on how to configure a nginx proxy for pretix
|
||||
return 404;
|
||||
}
|
||||
|
||||
location /static/staticfiles.json {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location /static/CACHE/manifest.json {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location /static/ {
|
||||
alias /var/pretix/venv/lib/python3.11/site-packages/pretix/static.dist/;
|
||||
access_log off;
|
||||
|
||||
@@ -97,6 +97,7 @@ lines list of objects The actual invo
|
||||
├ gross_value money (string) Price including taxes
|
||||
├ tax_value money (string) Tax amount included
|
||||
├ tax_name string Name of used tax rate (e.g. "VAT")
|
||||
├ tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
|
||||
└ tax_rate decimal (string) Used tax rate
|
||||
foreign_currency_display string If the invoice should also show the total and tax
|
||||
amount in a different currency, this contains the
|
||||
@@ -126,6 +127,10 @@ internal_reference string Customer's refe
|
||||
|
||||
The ``event`` attribute has been added. The organizer-level endpoint has been added.
|
||||
|
||||
.. versionchanged:: 2024.8
|
||||
|
||||
The ``tax_code`` attribute has been added.
|
||||
|
||||
|
||||
List of all invoices
|
||||
--------------------
|
||||
@@ -203,6 +208,7 @@ List of all invoices
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
"tax_name": "VAT",
|
||||
"tax_code": "S/standard",
|
||||
"tax_rate": "0.00"
|
||||
}
|
||||
],
|
||||
@@ -342,6 +348,7 @@ Fetching individual invoices
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
"tax_name": "VAT",
|
||||
"tax_code": "S/standard",
|
||||
"tax_rate": "0.00"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -84,6 +84,7 @@ fees list of objects List of fees in
|
||||
├ tax_rate decimal (string) VAT rate applied for this fee
|
||||
├ tax_value money (string) VAT included in this fee
|
||||
├ tax_rule integer The ID of the used tax rule (or ``null``)
|
||||
├ tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
|
||||
└ canceled boolean Whether or not this fee has been canceled.
|
||||
downloads list of objects List of ticket download options for order-wise ticket
|
||||
downloading. This might be a multi-page PDF or a ZIP
|
||||
@@ -159,6 +160,10 @@ cancellation_date datetime Time of order c
|
||||
|
||||
The ``cancellation_date`` attribute has been added and can also be used as an ordering key.
|
||||
|
||||
.. versionchanged:: 2025.1
|
||||
|
||||
The ``tax_code`` attribute has been added.
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
Order position resource
|
||||
@@ -195,6 +200,7 @@ voucher_budget_use money (string) Amount of money
|
||||
are changed *after* the order was created. Can be ``null``.
|
||||
tax_rate decimal (string) VAT rate applied for this position
|
||||
tax_value money (string) VAT included in this position
|
||||
tax_code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
|
||||
tax_rule integer The ID of the used tax rule (or ``null``)
|
||||
secret string Secret code printed on the tickets for validation
|
||||
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
|
||||
@@ -255,6 +261,10 @@ pdf_data object Data object req
|
||||
|
||||
The attribute ``print_logs`` has been added.
|
||||
|
||||
.. versionchanged:: 2025.1
|
||||
|
||||
The ``tax_code`` attribute has been added.
|
||||
|
||||
.. _order-payment-resource:
|
||||
|
||||
Order payment resource
|
||||
@@ -406,6 +416,7 @@ List of all orders
|
||||
"tax_rate": "0.00",
|
||||
"tax_value": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_code": null,
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
@@ -645,6 +656,7 @@ Fetching individual orders
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_value": "0.00",
|
||||
"tax_code": null,
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
@@ -843,7 +855,7 @@ Generating new secrets
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/regenerate_secrets/
|
||||
|
||||
Triggers generation of new ``secret`` attributes for both the order and all order positions.
|
||||
Triggers generation of new ``secret`` and ``ẁeb_secret`` attributes for both the order and all order positions.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -874,7 +886,7 @@ Generating new secrets
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/regenerate_secrets/
|
||||
|
||||
Triggers generation of a new ``secret`` attribute for a single order position.
|
||||
Triggers generation of a new ``secret`` and ``web_secret`` attribute for a single order position.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@@ -1613,6 +1625,7 @@ List of all order positions
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_value": "0.00",
|
||||
"tax_code": null,
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"discount": null,
|
||||
"pseudonymization_id": "MQLJvANO3B",
|
||||
@@ -1739,6 +1752,7 @@ Fetching individual positions
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": null,
|
||||
"tax_value": "0.00",
|
||||
"tax_code": null,
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
|
||||
+112
-1
@@ -249,7 +249,7 @@ Endpoints
|
||||
"orderposition": null,
|
||||
"cartposition": null,
|
||||
"voucher": null
|
||||
},
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
@@ -260,3 +260,114 @@ Endpoints
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_block/
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_block/
|
||||
|
||||
Set the ``blocked`` attribute to ``true`` for a large number of seats at once.
|
||||
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
|
||||
You can pass up to 10,000 seats in one request.
|
||||
|
||||
The endpoint will return an error if you pass a seat ID that does not exist.
|
||||
However, it will not return an error if one of the passed seats is already blocked or sold.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ids": [12, 45, 56]
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param subevent_id: The ``id`` field of the subevent to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The seat could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/seats/bulk_unblock/
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/subevents/(id)/seats/bulk_unblock/
|
||||
|
||||
Set the ``blocked`` attribute to ``false`` for a large number of seats at once.
|
||||
You can pass either a list of ``id`` values or a list of ``seat_guid`` values.
|
||||
You can pass up to 10,000 seats in one request.
|
||||
|
||||
The endpoint will return an error if you pass a seat ID that does not exist.
|
||||
However, it will not return an error if one of the passed seat is already unblocked or is sold.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ids": [12, 45, 56]
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/seats/bulk_unblock/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"seat_guids": ["6c0e29e5-05d6-421f-99f3-afd01478ecad", "c2899340-e2e7-4d05-8100-000a4b6d7cf4"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param subevent_id: The ``id`` field of the subevent to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The seat could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or event does not exist **or** you have no permission to change this resource.
|
||||
:statuscode 404: Seat does not exist; or the endpoint without subevent id was used for event with subevents, or vice versa.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.. spelling:word-list::
|
||||
|
||||
EN16931
|
||||
DSFinV-K
|
||||
|
||||
.. _rest-taxrules:
|
||||
|
||||
Tax rules
|
||||
@@ -18,6 +23,7 @@ id integer Internal ID of
|
||||
name multi-lingual string The tax rules' name
|
||||
internal_name string An optional name that is only used in the backend
|
||||
rate decimal (string) Tax rate in percent
|
||||
code string Codified reason for tax rate (or ``null``), see :ref:`rest-taxcodes`.
|
||||
price_includes_tax boolean If ``true`` (default), tax is assumed to be included in
|
||||
the specified product price
|
||||
eu_reverse_charge boolean **DEPRECATED**. If ``true``, EU reverse charge rules
|
||||
@@ -42,6 +48,42 @@ custom_rules object Dynamic rules s
|
||||
|
||||
The ``custom_rules`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 2023.8
|
||||
|
||||
The ``code`` attribute has been added.
|
||||
|
||||
.. _rest-taxcodes:
|
||||
|
||||
Tax codes
|
||||
---------
|
||||
|
||||
For integration with external systems, such as electronic invoicing or bookkeeping systems, the tax rate itself is often
|
||||
not sufficient information. For example, there could be many different reasons why a sale has a tax rate of 0 %, but the
|
||||
external handling of the transaction depends on which reason applies. Therefore, pretix allows to supply a codified
|
||||
reason that allows us to understand what the specific legal situation is. These tax codes are modeled after a combination
|
||||
of the code lists from the European standard EN16931 and the German standard DSFinV-K.
|
||||
|
||||
The following codes are supported:
|
||||
|
||||
- ``S/standard`` -- Standard VAT rate in the merchant country
|
||||
- ``S/reduced`` -- Reduced VAT rate in the merchant country
|
||||
- ``S/averaged`` -- Averaged VAT rate in the merchant country (known use case: agricultural businesses in Germany)
|
||||
- ``AE`` -- Reverse charge
|
||||
- ``O`` -- Services outside of scope of tax
|
||||
- ``E`` -- Exempt from tax (no reason given)
|
||||
- ``E/<reason>`` -- Exempt from tax, where ``<reason>`` is one of the codes listed in the `VATEX code list`_ version 5.0.
|
||||
- ``Z`` -- Zero-rated goods
|
||||
- ``G`` -- Free export item, VAT not charged
|
||||
- ``K`` -- VAT exempt for EEA intra-community supply of goods and services
|
||||
- ``L`` -- Canary Islands general indirect tax
|
||||
- ``M`` -- Tax for production, services and importation in Ceuta and Melilla
|
||||
- ``B`` -- Transferred (VAT), only in Italy
|
||||
|
||||
The code set in the ``code`` attribute of the tax rule is used by default. When ``eu_reverse_charge`` is active, the
|
||||
code is replaced by ``AE`` for reverse charge sales and by ``O`` for non-EU sales. When configuring custom rules, you
|
||||
should actively set a ``"code"`` key on each rule. Only for ``"action": "reverse"`` we automatically apply the code
|
||||
``AE``, in all other cases the default ``code`` of the tax rule is selected.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -74,6 +116,7 @@ Endpoints
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
"price_includes_tax": true,
|
||||
"eu_reverse_charge": false,
|
||||
@@ -115,6 +158,7 @@ Endpoints
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
"price_includes_tax": true,
|
||||
"eu_reverse_charge": false,
|
||||
@@ -164,6 +208,7 @@ Endpoints
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "19.00",
|
||||
"price_includes_tax": true,
|
||||
"eu_reverse_charge": false,
|
||||
@@ -212,6 +257,7 @@ Endpoints
|
||||
"id": 1,
|
||||
"name": {"en": "VAT"},
|
||||
"internal_name": "VAT",
|
||||
"code": "S/standard",
|
||||
"rate": "20.00",
|
||||
"price_includes_tax": true,
|
||||
"eu_reverse_charge": false,
|
||||
@@ -258,3 +304,4 @@ Endpoints
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it **or** this tax rule cannot be deleted since it is currently in use.
|
||||
|
||||
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/tax-rules-custom.schema.json
|
||||
.. _VATEX code list: https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists
|
||||
+7
-7
@@ -32,7 +32,7 @@ dependencies = [
|
||||
"bleach==6.2.*",
|
||||
"celery==5.4.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=3.4.2",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.14.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"Django[argon2]==4.2.*,>=4.2.15",
|
||||
@@ -44,7 +44,7 @@ dependencies = [
|
||||
"django-formtools==2.5.1",
|
||||
"django-hierarkey==1.2.*",
|
||||
"django-hijack==3.7.*",
|
||||
"django-i18nfield==1.9.*,>=1.9.4",
|
||||
"django-i18nfield==1.10.*",
|
||||
"django-libsass==0.9",
|
||||
"django-localflavor==4.0",
|
||||
"django-markup",
|
||||
@@ -53,7 +53,7 @@ dependencies = [
|
||||
"django-phonenumber-field==7.3.*",
|
||||
"django-redis==5.4.*",
|
||||
"django-scopes==2.0.*",
|
||||
"django-statici18n==2.5.*",
|
||||
"django-statici18n==2.6.*",
|
||||
"djangorestframework==3.15.*",
|
||||
"dnspython==2.7.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
@@ -74,9 +74,9 @@ dependencies = [
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.9.*",
|
||||
"phonenumberslite==8.13.*",
|
||||
"Pillow==11.0.*",
|
||||
"Pillow==11.1.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==5.28.*",
|
||||
"protobuf==5.29.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==2.22",
|
||||
@@ -97,10 +97,10 @@ dependencies = [
|
||||
"text-unidecode==1.*",
|
||||
"tlds>=2020041600",
|
||||
"tqdm==4.*",
|
||||
"ua-parser==0.18.*",
|
||||
"ua-parser==1.0.*",
|
||||
"vat_moss_forked==2020.3.20.0.11.0",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.2.*",
|
||||
"webauthn==2.4.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2024.11.0"
|
||||
__version__ = "2024.12.0.dev0"
|
||||
|
||||
@@ -103,7 +103,7 @@ class SalesChannelMigrationMixin:
|
||||
]
|
||||
})
|
||||
|
||||
if data["sales_channels"] == all_channels:
|
||||
if set(data["sales_channels"]) == all_channels:
|
||||
data["all_sales_channels"] = True
|
||||
data["limit_sales_channels"] = []
|
||||
else:
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
@@ -43,6 +43,7 @@ from django.utils.translation import gettext as _
|
||||
from django_countries.serializers import CountryFieldMixin
|
||||
from pytz import common_timezones
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import ChoiceField, Field
|
||||
from rest_framework.relations import SlugRelatedField
|
||||
|
||||
@@ -436,7 +437,8 @@ class CloneEventSerializer(EventSerializer):
|
||||
testmode = validated_data.pop('testmode', None)
|
||||
has_subevents = validated_data.pop('has_subevents', None)
|
||||
tz = validated_data.pop('timezone', None)
|
||||
sales_channels = validated_data.pop('sales_channels', None)
|
||||
all_sales_channels = validated_data.pop('all_sales_channels', None)
|
||||
limit_sales_channels = validated_data.pop('limit_sales_channels', None)
|
||||
date_admission = validated_data.pop('date_admission', None)
|
||||
new_event = super().create({**validated_data, 'plugins': None})
|
||||
|
||||
@@ -449,8 +451,9 @@ class CloneEventSerializer(EventSerializer):
|
||||
new_event.is_public = is_public
|
||||
if testmode is not None:
|
||||
new_event.testmode = testmode
|
||||
if sales_channels is not None:
|
||||
new_event.sales_channels = sales_channels
|
||||
if all_sales_channels is not None or limit_sales_channels is not None:
|
||||
new_event.all_sales_channels = all_sales_channels
|
||||
new_event.limit_sales_channels.set(limit_sales_channels)
|
||||
if has_subevents is not None:
|
||||
new_event.has_subevents = has_subevents
|
||||
if has_subevents is not None:
|
||||
@@ -678,8 +681,8 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = TaxRule
|
||||
fields = ('id', 'name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country', 'internal_name',
|
||||
'keep_gross_if_rate_changes', 'custom_rules')
|
||||
fields = ('id', 'name', 'rate', 'code', 'price_includes_tax', 'eu_reverse_charge', 'home_country',
|
||||
'internal_name', 'keep_gross_if_rate_changes', 'custom_rules')
|
||||
|
||||
|
||||
class EventSettingsSerializer(SettingsSerializer):
|
||||
@@ -989,6 +992,40 @@ def prefetch_by_id(items, qs, id_attr, target_attr):
|
||||
setattr(item, target_attr, result.get(getattr(item, id_attr)))
|
||||
|
||||
|
||||
class SeatBulkBlockInputSerializer(serializers.Serializer):
|
||||
ids = serializers.ListField(child=serializers.IntegerField(), required=False, allow_empty=True)
|
||||
seat_guids = serializers.ListField(child=serializers.CharField(), required=False, allow_empty=True)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
data = super().to_internal_value(data)
|
||||
|
||||
if data.get("seat_guids") and data.get("ids"):
|
||||
raise ValidationError("Please pass either seat_guids or ids.")
|
||||
|
||||
if data.get("seat_guids"):
|
||||
seat_ids = data["seat_guids"]
|
||||
if len(seat_ids) > 10000:
|
||||
raise ValidationError({"seat_guids": ["Please do not pass over 10000 seats."]})
|
||||
|
||||
seats = {s.seat_guid: s for s in self.context["queryset"].filter(seat_guid__in=seat_ids)}
|
||||
for s in seat_ids:
|
||||
if s not in seats:
|
||||
raise ValidationError({"seat_guids": [f"The seat '{s}' does not exist."]})
|
||||
elif data.get("ids"):
|
||||
seat_ids = data["ids"]
|
||||
if len(seat_ids) > 10000:
|
||||
raise ValidationError({"ids": ["Please do not pass over 10000 seats."]})
|
||||
|
||||
seats = self.context["queryset"].in_bulk(seat_ids)
|
||||
for s in seat_ids:
|
||||
if s not in seats:
|
||||
raise ValidationError({"ids": [f"The seat '{s}' does not exist."]})
|
||||
else:
|
||||
raise ValidationError("Please pass either seat_guids or ids.")
|
||||
|
||||
return {"seats": seats.values()}
|
||||
|
||||
|
||||
class SeatSerializer(I18nAwareModelSerializer):
|
||||
orderposition = serializers.IntegerField(source='orderposition_id')
|
||||
cartposition = serializers.IntegerField(source='cartposition_id')
|
||||
|
||||
@@ -19,57 +19,8 @@
|
||||
# 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.conf import settings
|
||||
from django.core.validators import URLValidator
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import Field
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
|
||||
|
||||
class I18nField(Field):
|
||||
def __init__(self, **kwargs):
|
||||
self.allow_blank = kwargs.pop('allow_blank', False)
|
||||
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
|
||||
self.max_length = kwargs.pop('max_length', None)
|
||||
self.min_length = kwargs.pop('min_length', None)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
if hasattr(value, 'data'):
|
||||
if isinstance(value.data, dict):
|
||||
return value.data
|
||||
elif value.data is None:
|
||||
return None
|
||||
else:
|
||||
return {
|
||||
settings.LANGUAGE_CODE: str(value.data)
|
||||
}
|
||||
elif value is None:
|
||||
return None
|
||||
else:
|
||||
return {
|
||||
settings.LANGUAGE_CODE: str(value)
|
||||
}
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, str):
|
||||
return LazyI18nString(data)
|
||||
elif isinstance(data, dict):
|
||||
if any([k not in dict(settings.LANGUAGES) for k in data.keys()]):
|
||||
raise ValidationError('Invalid languages included.')
|
||||
return LazyI18nString(data)
|
||||
else:
|
||||
raise ValidationError('Invalid data type.')
|
||||
|
||||
|
||||
class I18nAwareModelSerializer(ModelSerializer):
|
||||
pass
|
||||
|
||||
|
||||
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
|
||||
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
|
||||
from i18nfield.rest_framework import I18nAwareModelSerializer, I18nField
|
||||
|
||||
|
||||
class I18nURLField(I18nField):
|
||||
@@ -84,3 +35,10 @@ class I18nURLField(I18nField):
|
||||
else:
|
||||
URLValidator()(value.data)
|
||||
return value
|
||||
|
||||
|
||||
__all__ = [
|
||||
"I18nAwareModelSerializer", # for backwards compatibility
|
||||
"I18nField", # for backwards compatibility
|
||||
"I18nURLField",
|
||||
]
|
||||
|
||||
@@ -512,11 +512,12 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
|
||||
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
|
||||
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
|
||||
'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
|
||||
'print_logs', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id', 'pdf_data', 'seat',
|
||||
'canceled', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
|
||||
read_only_fields = (
|
||||
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
|
||||
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
|
||||
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use'
|
||||
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id',
|
||||
'pdf_data', 'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -642,7 +643,8 @@ class OrderPaymentDateField(serializers.DateField):
|
||||
class OrderFeeSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = OrderFee
|
||||
fields = ('id', 'fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
|
||||
fields = ('id', 'fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule',
|
||||
'tax_code', 'canceled')
|
||||
|
||||
|
||||
class PaymentURLField(serializers.URLField):
|
||||
@@ -1676,7 +1678,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = InvoiceLine
|
||||
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
|
||||
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type',
|
||||
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
|
||||
'fee_internal_type', 'event_location')
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import serializers, views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import (
|
||||
NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
@@ -50,8 +51,9 @@ from pretix.api.auth.permission import EventCRUDPermission
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.event import (
|
||||
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
|
||||
EventSettingsSerializer, ItemMetaPropertiesSerializer, SeatSerializer,
|
||||
SubEventSerializer, TaxRuleSerializer,
|
||||
EventSettingsSerializer, ItemMetaPropertiesSerializer,
|
||||
SeatBulkBlockInputSerializer, SeatSerializer, SubEventSerializer,
|
||||
TaxRuleSerializer,
|
||||
)
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import (
|
||||
@@ -237,9 +239,9 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
disabled = {m: 'disabled' for m in current_plugins_value if m not in updated_plugins_value}
|
||||
changed = merge_dicts(enabled, disabled)
|
||||
|
||||
for module, action in changed.items():
|
||||
for module, operation in changed.items():
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.plugins.' + action,
|
||||
'pretix.event.plugins.' + operation,
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'plugin': module}
|
||||
@@ -744,3 +746,24 @@ class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
auth=self.request.auth,
|
||||
data={"seats": [serializer.instance.pk]},
|
||||
)
|
||||
|
||||
def bulk_change_blocked(self, blocked):
|
||||
s = SeatBulkBlockInputSerializer(
|
||||
data=self.request.data,
|
||||
context={"event": self.request.event, "queryset": self.get_queryset()},
|
||||
)
|
||||
s.is_valid(raise_exception=True)
|
||||
|
||||
seats = s.validated_data["seats"]
|
||||
for seat in seats:
|
||||
seat.blocked = blocked
|
||||
Seat.objects.bulk_update(seats, ["blocked"], batch_size=1000)
|
||||
return Response({})
|
||||
|
||||
@action(methods=["POST"], detail=False)
|
||||
def bulk_block(self, request, *args, **kwargs):
|
||||
return self.bulk_change_blocked(True)
|
||||
|
||||
@action(methods=["POST"], detail=False)
|
||||
def bulk_unblock(self, request, *args, **kwargs):
|
||||
return self.bulk_change_blocked(False)
|
||||
|
||||
@@ -35,6 +35,7 @@ from django.db.models import (
|
||||
from django.db.models.functions import Coalesce, Concat
|
||||
from django.http import FileResponse, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import formats
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import gettext as _
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
@@ -67,6 +68,7 @@ from pretix.api.serializers.orderchange import (
|
||||
OrderPositionInfoPatchSerializer,
|
||||
)
|
||||
from pretix.api.views import RichOrderingFilter
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedTicket, Checkin, Device, EventMetaValue,
|
||||
@@ -97,7 +99,6 @@ from pretix.base.services.tickets import generate
|
||||
from pretix.base.signals import (
|
||||
order_modified, order_paid, order_placed, register_ticket_outputs,
|
||||
)
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.control.signals import order_search_filter_q
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
@@ -646,6 +647,8 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
order = self.get_object()
|
||||
order.secret = generate_secret()
|
||||
for op in order.all_positions.all():
|
||||
op.web_secret = generate_secret()
|
||||
op.save(update_fields=["web_secret"])
|
||||
assign_ticket_secret(
|
||||
request.event, op, force_invalidate=True, save=True
|
||||
)
|
||||
@@ -1228,9 +1231,10 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
price = get_price(**kwargs)
|
||||
tr = kwargs.get('tax_rule', kwargs.get('item').tax_rule)
|
||||
with language(data.get('locale') or self.request.event.settings.locale, self.request.event.settings.region):
|
||||
gross_formatted = formats.localize_input(round_decimal(price.gross, self.request.event.currency))
|
||||
return Response({
|
||||
'gross': price.gross,
|
||||
'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True),
|
||||
'gross_formatted': gross_formatted,
|
||||
'net': price.net,
|
||||
'rate': price.rate,
|
||||
'name': str(price.name),
|
||||
|
||||
@@ -35,6 +35,7 @@ from django.utils.translation import get_language, gettext_lazy as _
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.signals import register_html_mail_renderers
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
|
||||
from pretix.base.services.placeholders import ( # noqa
|
||||
get_available_placeholders, PlaceholderContext
|
||||
@@ -79,7 +80,7 @@ class BaseHTMLMailRenderer:
|
||||
return self.identifier
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order=None,
|
||||
position=None) -> str:
|
||||
position=None, context=None) -> str:
|
||||
"""
|
||||
This method should generate the HTML part of the email.
|
||||
|
||||
@@ -88,6 +89,7 @@ class BaseHTMLMailRenderer:
|
||||
:param subject: The email subject.
|
||||
:param order: The order if this email is connected to one, otherwise ``None``.
|
||||
:param position: The order position if this email is connected to one, otherwise ``None``.
|
||||
:param context: Context to use to render placeholders in the plain body
|
||||
:return: An HTML string
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -134,8 +136,10 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
||||
def compile_markdown(self, plaintext):
|
||||
return markdown_compile_email(plaintext)
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
|
||||
body_md = self.compile_markdown(plain_body)
|
||||
if context:
|
||||
body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
htmlctx = {
|
||||
'site': settings.PRETIX_INSTANCE_NAME,
|
||||
'site_url': settings.SITE_URL,
|
||||
|
||||
@@ -100,7 +100,7 @@ class MarkdownTextarea(forms.Textarea):
|
||||
|
||||
|
||||
class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea):
|
||||
def format_output(self, rendered_widgets) -> str:
|
||||
def format_output(self, rendered_widgets, id_) -> str:
|
||||
rendered_widgets = rendered_widgets + [
|
||||
'<div class="i18n-field-markdown-note">%s</div>' % (
|
||||
_("You can use {markup_name} in this field.").format(
|
||||
@@ -108,11 +108,11 @@ class I18nMarkdownTextarea(i18nfield.forms.I18nTextarea):
|
||||
)
|
||||
)
|
||||
]
|
||||
return super().format_output(rendered_widgets)
|
||||
return super().format_output(rendered_widgets, id_)
|
||||
|
||||
|
||||
class I18nMarkdownTextInput(i18nfield.forms.I18nTextInput):
|
||||
def format_output(self, rendered_widgets) -> str:
|
||||
def format_output(self, rendered_widgets, id_) -> str:
|
||||
rendered_widgets = rendered_widgets + [
|
||||
'<div class="i18n-field-markdown-note">%s</div>' % (
|
||||
_("You can use {markup_name} in this field.").format(
|
||||
@@ -120,7 +120,7 @@ class I18nMarkdownTextInput(i18nfield.forms.I18nTextInput):
|
||||
)
|
||||
)
|
||||
]
|
||||
return super().format_output(rendered_widgets)
|
||||
return super().format_output(rendered_widgets, id_)
|
||||
|
||||
|
||||
SECRET_REDACTED = '*****'
|
||||
|
||||
@@ -54,6 +54,7 @@ from django.core.validators import (
|
||||
from django.db.models import QuerySet
|
||||
from django.forms import Select, widgets
|
||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
@@ -77,7 +78,7 @@ from pretix.base.i18n import (
|
||||
get_babel_locale, get_language_without_region, language,
|
||||
)
|
||||
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
|
||||
from pretix.base.models.tax import ask_for_vat_id
|
||||
from pretix.base.services.tax import (
|
||||
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
||||
)
|
||||
@@ -276,6 +277,10 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
return value
|
||||
|
||||
|
||||
def name_parts_is_empty(name_parts_dict):
|
||||
return not any(k != "_scheme" and v for k, v in name_parts_dict.items())
|
||||
|
||||
|
||||
class WrappedPhonePrefixSelect(Select):
|
||||
initial = None
|
||||
|
||||
@@ -602,6 +607,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
questions = pos.item.questions_to_ask
|
||||
event = kwargs.pop('event')
|
||||
self.all_optional = kwargs.pop('all_optional', False)
|
||||
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -676,7 +682,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
|
||||
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
||||
add_fields['street'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
required=self.attendee_addresses_required,
|
||||
label=_('Address'),
|
||||
widget=forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
@@ -686,7 +692,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
initial=(cartpos.street if cartpos else orderpos.street),
|
||||
)
|
||||
add_fields['zipcode'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
required=False,
|
||||
max_length=30,
|
||||
label=_('ZIP code'),
|
||||
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
||||
@@ -695,7 +701,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
}),
|
||||
)
|
||||
add_fields['city'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
required=False,
|
||||
label=_('City'),
|
||||
max_length=255,
|
||||
initial=(cartpos.city if cartpos else orderpos.city),
|
||||
@@ -707,11 +713,12 @@ class BaseQuestionsForm(forms.Form):
|
||||
add_fields['country'] = CountryField(
|
||||
countries=CachedCountries
|
||||
).formfield(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
required=self.attendee_addresses_required,
|
||||
label=_('Country'),
|
||||
initial=country,
|
||||
widget=forms.Select(attrs={
|
||||
'autocomplete': 'country',
|
||||
'data-country-information-url': reverse('js_helpers.states'),
|
||||
}),
|
||||
)
|
||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||
@@ -946,9 +953,9 @@ class BaseQuestionsForm(forms.Form):
|
||||
d = super().clean()
|
||||
|
||||
if self.address_validation:
|
||||
self.cleaned_data = d = validate_address(d, True)
|
||||
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
|
||||
|
||||
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if not d.get('state'):
|
||||
self.add_error('state', _('This field is required.'))
|
||||
|
||||
@@ -1005,7 +1012,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
'street': forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
'placeholder': _('Street and Number'),
|
||||
'autocomplete': 'street-address'
|
||||
'autocomplete': 'street-address',
|
||||
}),
|
||||
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
||||
'country': forms.Select(attrs={
|
||||
@@ -1021,13 +1028,25 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
'data-display-dependency': '#id_is_business_1',
|
||||
'autocomplete': 'organization',
|
||||
}),
|
||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
|
||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
||||
'internal_reference': forms.TextInput,
|
||||
}
|
||||
labels = {
|
||||
'is_business': ''
|
||||
}
|
||||
|
||||
@property
|
||||
def ask_vat_id(self):
|
||||
return self.event.settings.invoice_address_vatid
|
||||
|
||||
@property
|
||||
def address_required(self):
|
||||
return self.event.settings.invoice_address_required
|
||||
|
||||
@property
|
||||
def company_required(self):
|
||||
return self.event.settings.invoice_address_company_required
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = event = kwargs.pop('event')
|
||||
self.request = kwargs.pop('request', None)
|
||||
@@ -1039,7 +1058,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
kwargs['initial']['country'] = guess_country_from_request(self.request, self.event)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not event.settings.invoice_address_vatid:
|
||||
|
||||
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
||||
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
||||
|
||||
if not self.ask_vat_id:
|
||||
del self.fields['vat_id']
|
||||
elif self.validate_vat_id:
|
||||
self.fields['vat_id'].help_text = '<br/>'.join([
|
||||
@@ -1055,6 +1078,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
])
|
||||
|
||||
self.fields['country'].choices = CachedCountries()
|
||||
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
|
||||
|
||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||
fprefix = self.prefix + '-' if self.prefix else ''
|
||||
@@ -1083,18 +1107,22 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
)
|
||||
self.fields['state'].widget.is_required = True
|
||||
|
||||
self.fields['street'].required = False
|
||||
self.fields['zipcode'].required = False
|
||||
self.fields['city'].required = False
|
||||
|
||||
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
|
||||
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
|
||||
self.data = self.data.copy()
|
||||
del self.data[fprefix + 'vat_id']
|
||||
|
||||
if not event.settings.invoice_address_required or self.all_optional:
|
||||
if not self.address_required or self.all_optional:
|
||||
for k, f in self.fields.items():
|
||||
f.required = False
|
||||
f.widget.is_required = False
|
||||
if 'required' in f.widget.attrs:
|
||||
del f.widget.attrs['required']
|
||||
elif event.settings.invoice_address_company_required and not self.all_optional:
|
||||
elif self.company_required and not self.all_optional:
|
||||
self.initial['is_business'] = True
|
||||
|
||||
self.fields['is_business'].widget = BusinessBooleanRadio(require_business=True)
|
||||
@@ -1111,11 +1139,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
label=_('Name'),
|
||||
initial=self.instance.name_parts,
|
||||
)
|
||||
if event.settings.invoice_address_required and not event.settings.invoice_address_company_required and not self.all_optional:
|
||||
if self.address_required and not self.company_required and not self.all_optional:
|
||||
if not event.settings.invoice_name_required:
|
||||
self.fields['name_parts'].widget.attrs['data-required-if'] = '#id_is_business_0'
|
||||
self.fields['name_parts'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_0'
|
||||
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
|
||||
self.fields['company'].widget.attrs['data-required-if'] = '#id_is_business_1'
|
||||
self.fields['company'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_1'
|
||||
|
||||
if not event.settings.invoice_address_beneficiary:
|
||||
del self.fields['beneficiary']
|
||||
@@ -1135,16 +1163,19 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
validate_address # local import to prevent impact on startup time
|
||||
|
||||
data = self.cleaned_data
|
||||
|
||||
if not data.get('is_business'):
|
||||
data['company'] = ''
|
||||
data['vat_id'] = ''
|
||||
if data.get('is_business') and not ask_for_vat_id(data.get('country')):
|
||||
data['vat_id'] = ''
|
||||
if self.event.settings.invoice_address_required:
|
||||
if self.address_validation and self.address_required and not self.all_optional:
|
||||
if data.get('is_business') and not data.get('company'):
|
||||
raise ValidationError(_('You need to provide a company name.'))
|
||||
if not data.get('is_business') and not data.get('name_parts'):
|
||||
raise ValidationError({"company": _('You need to provide a company name.')})
|
||||
if not data.get('is_business') and name_parts_is_empty(data.get('name_parts', {})):
|
||||
raise ValidationError(_('You need to provide your name.'))
|
||||
if not data.get('street') and not data.get('zipcode') and not data.get('city'):
|
||||
raise ValidationError({"street": _('This field is required.')})
|
||||
|
||||
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
||||
self.instance.vat_id_validated = False
|
||||
@@ -1156,7 +1187,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
|
||||
if all(
|
||||
not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts')
|
||||
) and len(data.get('name_parts', {})) == 1:
|
||||
) and name_parts_is_empty(data.get('name_parts', {})):
|
||||
# Do not save the country if it is the only field set -- we don't know the user even checked it!
|
||||
self.cleaned_data['country'] = ''
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ class Command(BaseCommand):
|
||||
try:
|
||||
r = receiver(signal=periodic_task, sender=self)
|
||||
except Exception as err:
|
||||
if isinstance(Exception, KeyboardInterrupt):
|
||||
if isinstance(err, KeyboardInterrupt):
|
||||
raise err
|
||||
if settings.SENTRY_ENABLED:
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Generated by Django 4.2.8 on 2024-07-02 10:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"pretixbase",
|
||||
"0273_remove_checkinlist_auto_checkin_sales_channels",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="invoiceline",
|
||||
name="tax_code",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="orderfee",
|
||||
name="tax_code",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="orderposition",
|
||||
name="tax_code",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="taxrule",
|
||||
name="code",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transaction",
|
||||
name="tax_code",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
]
|
||||
@@ -441,6 +441,7 @@ class Price(DecimalColumnMixin, ImportColumn):
|
||||
position.price = p.gross
|
||||
position.tax_rule = position.item.tax_rule
|
||||
position.tax_rate = p.rate
|
||||
position.tax_code = p.code
|
||||
position.tax_value = p.tax
|
||||
|
||||
|
||||
@@ -584,7 +585,7 @@ class SeatColumn(ImportColumn):
|
||||
raise ValidationError(_('Multiple matching seats were found.'))
|
||||
except Seat.DoesNotExist:
|
||||
raise ValidationError(_('No matching seat was found.'))
|
||||
if not value.is_available() or value in self._cached:
|
||||
if not value.is_available(sales_channel=previous_values.get('sales_channel')) or value in self._cached:
|
||||
raise ValidationError(
|
||||
_('The seat you selected has already been taken. Please select a different seat.'))
|
||||
self._cached.add(value)
|
||||
@@ -753,11 +754,11 @@ def get_order_import_columns(event):
|
||||
AttendeeState(event),
|
||||
Price(event),
|
||||
Secret(event),
|
||||
Saleschannel(event),
|
||||
SeatColumn(event),
|
||||
ValidFrom(event),
|
||||
ValidUntil(event),
|
||||
Locale(event),
|
||||
Saleschannel(event),
|
||||
CheckinAttentionColumn(event),
|
||||
CheckinTextColumn(event),
|
||||
Expires(event),
|
||||
|
||||
@@ -823,6 +823,9 @@ class Event(EventMixin, LoggedModel):
|
||||
self.save()
|
||||
self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk})
|
||||
|
||||
if hasattr(other, 'alternative_domain_assignment'):
|
||||
other.alternative_domain_assignment.domain.event_assignments.create(event=self)
|
||||
|
||||
if not self.all_sales_channels:
|
||||
self.limit_sales_channels.set(
|
||||
self.organizer.sales_channels.filter(
|
||||
|
||||
@@ -362,6 +362,7 @@ class InvoiceLine(models.Model):
|
||||
tax_value = models.DecimalField(max_digits=13, decimal_places=2, default=Decimal('0.00'))
|
||||
tax_rate = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal('0.00'))
|
||||
tax_name = models.CharField(max_length=190)
|
||||
tax_code = models.CharField(max_length=190, null=True, blank=True)
|
||||
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
|
||||
event_date_from = models.DateTimeField(null=True)
|
||||
event_date_to = models.DateTimeField(null=True)
|
||||
|
||||
@@ -837,7 +837,7 @@ class Item(LoggedModel):
|
||||
|
||||
if not self.tax_rule:
|
||||
t = TaxedPrice(gross=price - bundled_sum, net=price - bundled_sum, tax=Decimal('0.00'),
|
||||
rate=Decimal('0.00'), name='')
|
||||
rate=Decimal('0.00'), name='', code=None)
|
||||
else:
|
||||
t = self.tax_rule.tax(price, base_price_is=base_price_is, invoice_address=invoice_address,
|
||||
override_tax_rate=override_tax_rate, currency=currency or self.event.currency,
|
||||
@@ -845,6 +845,7 @@ class Item(LoggedModel):
|
||||
|
||||
if bundled_sum:
|
||||
t.name = "MIXED!"
|
||||
t.code = None
|
||||
t.gross += bundled_sum
|
||||
t.net += bundled_sum_net
|
||||
t.tax += bundled_sum_tax
|
||||
@@ -1258,7 +1259,7 @@ class ItemVariation(models.Model):
|
||||
|
||||
if not self.item.tax_rule:
|
||||
t = TaxedPrice(gross=price, net=price, tax=Decimal('0.00'),
|
||||
rate=Decimal('0.00'), name='')
|
||||
rate=Decimal('0.00'), name='', code=None)
|
||||
else:
|
||||
t = self.item.tax_rule.tax(price, base_price_is=base_price_is, currency=currency,
|
||||
override_tax_rate=override_tax_rate,
|
||||
@@ -1280,6 +1281,7 @@ class ItemVariation(models.Model):
|
||||
t.net += bprice.net - compare_price.net
|
||||
t.tax += bprice.tax - compare_price.tax
|
||||
t.name = "MIXED!"
|
||||
t.code = None
|
||||
|
||||
return t
|
||||
|
||||
|
||||
@@ -159,10 +159,24 @@ class Membership(models.Model):
|
||||
de = date_format(self.date_end, 'SHORT_DATE_FORMAT')
|
||||
return f'{self.membership_type.name}: {self.attendee_name} ({ds} – {de})'
|
||||
|
||||
@property
|
||||
def percentage_used(self):
|
||||
if self.membership_type.max_usages and self.usages:
|
||||
return int(self.usages / self.membership_type.max_usages * 100)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def attendee_name(self):
|
||||
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
return time_machine_now() > self.date_end
|
||||
|
||||
@property
|
||||
def not_yet_valid(self):
|
||||
return time_machine_now() < self.date_start
|
||||
|
||||
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
|
||||
if valid_from_not_chosen:
|
||||
return not self.canceled and self.date_end >= time_machine_now()
|
||||
|
||||
@@ -62,9 +62,10 @@ from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string, salted_hmac
|
||||
from django.utils.encoding import escape_uri_path
|
||||
from django.utils.encoding import escape_uri_path, force_str
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.hashable import make_hashable
|
||||
from django.utils.timezone import get_current_timezone, make_aware, now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_countries.fields import Country
|
||||
@@ -1256,7 +1257,7 @@ class Order(LockModel, LoggedModel):
|
||||
keys = set(target_transaction_count.keys()) | set(current_transaction_count.keys())
|
||||
create = []
|
||||
for k in keys:
|
||||
positionid, itemid, variationid, subeventid, price, taxrate, taxruleid, taxvalue, feetype, internaltype = k
|
||||
positionid, itemid, variationid, subeventid, price, taxrate, taxruleid, taxvalue, feetype, internaltype, taxcode = k
|
||||
d = target_transaction_count[k] - current_transaction_count[k]
|
||||
if d:
|
||||
create.append(Transaction(
|
||||
@@ -1272,6 +1273,7 @@ class Order(LockModel, LoggedModel):
|
||||
tax_rate=taxrate,
|
||||
tax_rule_id=taxruleid,
|
||||
tax_value=taxvalue,
|
||||
tax_code=taxcode,
|
||||
fee_type=feetype,
|
||||
internal_type=internaltype,
|
||||
))
|
||||
@@ -2275,6 +2277,7 @@ class OrderFee(models.Model):
|
||||
FEE_TYPE_SERVICE = "service"
|
||||
FEE_TYPE_CANCELLATION = "cancellation"
|
||||
FEE_TYPE_INSURANCE = "insurance"
|
||||
FEE_TYPE_LATE = "late"
|
||||
FEE_TYPE_OTHER = "other"
|
||||
FEE_TYPE_GIFTCARD = "giftcard"
|
||||
FEE_TYPES = (
|
||||
@@ -2283,6 +2286,7 @@ class OrderFee(models.Model):
|
||||
(FEE_TYPE_SERVICE, _("Service fee")),
|
||||
(FEE_TYPE_CANCELLATION, _("Cancellation fee")),
|
||||
(FEE_TYPE_INSURANCE, _("Insurance fee")),
|
||||
(FEE_TYPE_LATE, _("Late fee")),
|
||||
(FEE_TYPE_OTHER, _("Other fees")),
|
||||
(FEE_TYPE_GIFTCARD, _("Gift card")),
|
||||
)
|
||||
@@ -2311,6 +2315,10 @@ class OrderFee(models.Model):
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
tax_code = models.CharField(
|
||||
max_length=190,
|
||||
null=True, blank=True,
|
||||
)
|
||||
tax_value = models.DecimalField(
|
||||
max_digits=13, decimal_places=2,
|
||||
verbose_name=_('Tax value')
|
||||
@@ -2338,6 +2346,16 @@ class OrderFee(models.Model):
|
||||
self._transaction_key_reset()
|
||||
return super().refresh_from_db(using, fields)
|
||||
|
||||
def get_tax_code_display(self):
|
||||
from pretix.base.models.tax import get_tax_code_labels
|
||||
|
||||
if self.tax_code:
|
||||
choices_dict = get_tax_code_labels()
|
||||
return force_str(
|
||||
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
|
||||
)
|
||||
return ""
|
||||
|
||||
def _transaction_key_reset(self):
|
||||
self.__initial_transaction_key = Transaction.key(self)
|
||||
self.__initial_canceled = self.canceled
|
||||
@@ -2368,9 +2386,11 @@ class OrderFee(models.Model):
|
||||
if self.tax_rule:
|
||||
tax = self.tax_rule.tax(self.value, base_price_is='gross', invoice_address=ia, force_fixed_gross_price=True)
|
||||
self.tax_rate = tax.rate
|
||||
self.tax_code = tax.code
|
||||
self.tax_value = tax.tax
|
||||
else:
|
||||
self.tax_value = Decimal('0.00')
|
||||
self.tax_code = None
|
||||
self.tax_rate = Decimal('0.00')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -2379,6 +2399,7 @@ class OrderFee(models.Model):
|
||||
|
||||
if self.tax_rate is None:
|
||||
self._calculate_tax()
|
||||
|
||||
self.order.touch()
|
||||
|
||||
if not self.get_deferred_fields():
|
||||
@@ -2466,6 +2487,10 @@ class OrderPosition(AbstractPosition):
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
tax_code = models.CharField(
|
||||
max_length=190,
|
||||
null=True, blank=True,
|
||||
)
|
||||
tax_value = models.DecimalField(
|
||||
max_digits=13, decimal_places=2,
|
||||
verbose_name=_('Tax value')
|
||||
@@ -2523,6 +2548,16 @@ class OrderPosition(AbstractPosition):
|
||||
models.UniqueConstraint("organizer", "secret", name="orderposition_organizer_secret_uniq")
|
||||
]
|
||||
|
||||
def get_tax_code_display(self):
|
||||
from pretix.base.models.tax import get_tax_code_labels
|
||||
|
||||
if self.tax_code:
|
||||
choices_dict = get_tax_code_labels()
|
||||
return force_str(
|
||||
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
|
||||
)
|
||||
return ""
|
||||
|
||||
@cached_property
|
||||
def sort_key(self):
|
||||
return self.addon_to.positionid if self.addon_to else self.positionid, self.addon_to_id or 0, self.positionid
|
||||
@@ -2695,11 +2730,13 @@ class OrderPosition(AbstractPosition):
|
||||
if self.tax_rule:
|
||||
tax = self.tax_rule.tax(self.price, invoice_address=ia, base_price_is='gross', force_fixed_gross_price=True)
|
||||
self.tax_rate = tax.rate
|
||||
self.tax_code = tax.code
|
||||
self.tax_value = tax.tax
|
||||
if tax.gross != self.price:
|
||||
raise ValueError('Invalid tax calculation')
|
||||
else:
|
||||
self.tax_value = Decimal('0.00')
|
||||
self.tax_code = None
|
||||
self.tax_rate = Decimal('0.00')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -2970,6 +3007,10 @@ class Transaction(models.Model):
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
tax_code = models.CharField(
|
||||
max_length=190,
|
||||
null=True, blank=True,
|
||||
)
|
||||
tax_value = models.DecimalField(
|
||||
max_digits=13, decimal_places=2,
|
||||
verbose_name=_('Tax value')
|
||||
@@ -2990,17 +3031,27 @@ class Transaction(models.Model):
|
||||
raise ValidationError('Should set either item or fee type')
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def get_tax_code_display(self):
|
||||
from pretix.base.models.tax import get_tax_code_labels
|
||||
|
||||
if self.tax_code:
|
||||
choices_dict = get_tax_code_labels()
|
||||
return force_str(
|
||||
choices_dict.get(make_hashable(self.tax_code), self.tax_code), strings_only=True
|
||||
)
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def key(obj):
|
||||
if isinstance(obj, Transaction):
|
||||
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
|
||||
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
|
||||
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type, obj.tax_code)
|
||||
elif isinstance(obj, OrderPosition):
|
||||
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
|
||||
obj.tax_rule_id, obj.tax_value, None, None)
|
||||
obj.tax_rule_id, obj.tax_value, None, None, obj.tax_code)
|
||||
elif isinstance(obj, OrderFee):
|
||||
return (None, None, None, None, obj.value, obj.tax_rate,
|
||||
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
|
||||
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type, obj.tax_code)
|
||||
raise ValueError('invalid state') # noqa
|
||||
|
||||
@property
|
||||
@@ -3164,6 +3215,7 @@ class CartPosition(AbstractPosition):
|
||||
if line_price.gross != self.line_price_gross or line_price.rate != self.tax_rate:
|
||||
self.line_price_gross = line_price.gross
|
||||
self.tax_rate = line_price.rate
|
||||
self.tax_code = line_price.code
|
||||
self.save(update_fields=['line_price_gross', 'tax_rate'])
|
||||
|
||||
@property
|
||||
@@ -3204,9 +3256,9 @@ class InvoiceAddress(models.Model):
|
||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
|
||||
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
||||
name_parts = models.JSONField(default=dict)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=False)
|
||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
|
||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=True)
|
||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
|
||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
|
||||
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
||||
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
||||
countries=CachedCountries)
|
||||
|
||||
+220
-12
@@ -21,6 +21,7 @@
|
||||
#
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
import jsonschema
|
||||
from django.contrib.staticfiles import finders
|
||||
@@ -30,8 +31,9 @@ from django.db import models
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.formats import localize
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.hashable import make_hashable
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from django.utils.translation import gettext_lazy as _, pgettext, pgettext_lazy
|
||||
from i18nfield.fields import I18nCharField
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
@@ -42,7 +44,7 @@ from pretix.helpers.countries import FastCountryField
|
||||
|
||||
|
||||
class TaxedPrice:
|
||||
def __init__(self, *, gross: Decimal, net: Decimal, tax: Decimal, rate: Decimal, name: str):
|
||||
def __init__(self, *, gross: Decimal, net: Decimal, tax: Decimal, rate: Decimal, name: str, code: Optional[str]):
|
||||
if net + tax != gross:
|
||||
raise ValueError('Net value and tax value need to add to the gross value')
|
||||
self.gross = gross
|
||||
@@ -50,6 +52,7 @@ class TaxedPrice:
|
||||
self.tax = tax
|
||||
self.rate = rate
|
||||
self.name = name
|
||||
self.code = code
|
||||
|
||||
def __repr__(self):
|
||||
return '{} + {}% = {}'.format(localize(self.net), localize(self.rate), localize(self.gross))
|
||||
@@ -72,6 +75,7 @@ class TaxedPrice:
|
||||
tax=newgross - newnet,
|
||||
rate=self.rate,
|
||||
name=self.name,
|
||||
code=self.code,
|
||||
)
|
||||
|
||||
def __mul__(self, other):
|
||||
@@ -85,6 +89,7 @@ class TaxedPrice:
|
||||
tax=newgross - newnet,
|
||||
rate=self.rate,
|
||||
name=self.name,
|
||||
code=self.code,
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
@@ -93,7 +98,8 @@ class TaxedPrice:
|
||||
self.net == other.net and
|
||||
self.tax == other.tax and
|
||||
self.rate == other.rate and
|
||||
self.name == other.name
|
||||
self.name == other.name and
|
||||
self.code == other.code
|
||||
)
|
||||
|
||||
|
||||
@@ -102,7 +108,8 @@ TAXED_ZERO = TaxedPrice(
|
||||
net=Decimal('0.00'),
|
||||
tax=Decimal('0.00'),
|
||||
rate=Decimal('0.00'),
|
||||
name=''
|
||||
name='',
|
||||
code=None,
|
||||
)
|
||||
|
||||
EU_COUNTRIES = {
|
||||
@@ -125,6 +132,152 @@ VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH', 'NO'}
|
||||
format_html_lazy = lazy(format_html, str)
|
||||
|
||||
|
||||
TAX_CODE_LISTS = (
|
||||
# Sources:
|
||||
# https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Registry+of+supporting+artefacts+to+implement+EN16931#RegistryofsupportingartefactstoimplementEN16931-Codelists#RegistryofsupportingartefactstoimplementEN16931-Codelists
|
||||
# https://docs.peppol.eu/poacc/billing/3.0/codelist/vatex/
|
||||
# https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL5305/
|
||||
# https://www.bzst.de/DE/Unternehmen/Aussenpruefungen/DigitaleSchnittstelleFinV/digitaleschnittstellefinv_node.html#js-toc-entry2
|
||||
#
|
||||
# !! When changed, also update tax-rules-custom.schema.json and doc/api/resources/taxrules.rst !!
|
||||
(
|
||||
_("Standard rates"),
|
||||
(
|
||||
# Standard rate in any country, such as 19% in Germany or 20% in Austria
|
||||
# DSFinV-K mapping: 1
|
||||
("S/standard", pgettext_lazy("tax_code", "Standard rate")),
|
||||
|
||||
# Reduced rate in any country, such as 7% in Germany or both 10% and 13% in Austria
|
||||
# DSFinV-K mapping: 2
|
||||
("S/reduced", pgettext_lazy("tax_code", "Reduced rate")),
|
||||
|
||||
# Averaged rate, for example Germany § 24 (1) Nr. 3 UStG "für die übrigen Umsätze" in agricultural and silvicultural businesses
|
||||
# DSFinV-K mapping: 3
|
||||
("S/averaged", pgettext_lazy("tax_code", "Averaged rate (other revenue in a agricultural and silvicultural business)")),
|
||||
|
||||
# We ignore the German special case of the actual silvicultural products as they won't be sold through pretix (DSFinV-K mapping: 4)
|
||||
)
|
||||
),
|
||||
(
|
||||
_("Reverse charge"),
|
||||
(
|
||||
("AE", pgettext_lazy("tax_code", "Reverse charge")),
|
||||
)
|
||||
),
|
||||
(
|
||||
_("Tax free"),
|
||||
(
|
||||
# DSFinV-K mapping: 5
|
||||
("O", pgettext_lazy("tax_code", "Services outside of scope of tax")),
|
||||
|
||||
# DSFinV-K mapping: 6
|
||||
("E", pgettext_lazy("tax_code", "Exempt from tax (no reason given)")),
|
||||
|
||||
# DSFinV-K mapping: 6
|
||||
("Z", pgettext_lazy("tax_code", "Zero-rated goods")),
|
||||
|
||||
# DSFinV-K mapping: 5
|
||||
("G", pgettext_lazy("tax_code", "Free export item, VAT not charged")),
|
||||
|
||||
# DSFinV-K mapping: 6?
|
||||
("K", pgettext_lazy("tax_code", "VAT exempt for EEA intra-community supply of goods and services")),
|
||||
)
|
||||
),
|
||||
(
|
||||
_("Special cases"),
|
||||
(
|
||||
("L", pgettext_lazy("tax_code", "Canary Islands general indirect tax")),
|
||||
("M", pgettext_lazy("tax_code", "Tax for production, services and importation in Ceuta and Melilla")),
|
||||
("B", pgettext_lazy("tax_code", "Transferred (VAT), only in Italy")),
|
||||
)
|
||||
),
|
||||
(
|
||||
_("Exempt with specific reason"),
|
||||
(
|
||||
("E/VATEX-EU-79-C",
|
||||
pgettext_lazy("tax_code", "Exempt based on article 79, point c of Council Directive 2006/112/EC")),
|
||||
*[
|
||||
(
|
||||
f"E/VATEX-EU-132-1{letter.upper()}",
|
||||
lazy(
|
||||
lambda let: pgettext(
|
||||
"tax_code",
|
||||
"Exempt based on article {article}, section {section} ({letter}) of Council "
|
||||
"Directive 2006/112/EC"
|
||||
).format(article="132", section="1", letter=let),
|
||||
str
|
||||
)(letter)
|
||||
) for letter in ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q")
|
||||
],
|
||||
*[
|
||||
(
|
||||
f"E/VATEX-EU-143-1{letter.upper()}",
|
||||
lazy(
|
||||
lambda let: pgettext(
|
||||
"tax_code",
|
||||
"Exempt based on article {article}, section {section} ({letter}) of Council "
|
||||
"Directive 2006/112/EC"
|
||||
).format(article="143", section="1", letter=let),
|
||||
str
|
||||
)(letter)
|
||||
) for letter in ("a", "b", "c", "d", "e", "f", "fa", "g", "h", "i", "j", "k", "l")
|
||||
],
|
||||
*[
|
||||
(
|
||||
f"E/VATEX-EU-148-{letter.upper()}",
|
||||
lazy(
|
||||
lambda let: pgettext(
|
||||
"tax_code",
|
||||
"Exempt based on article {article}, section ({letter}) of Council "
|
||||
"Directive 2006/112/EC"
|
||||
).format(article="148", letter=let),
|
||||
str
|
||||
)(letter)
|
||||
) for letter in ("a", "b", "c", "d", "e", "f", "g")
|
||||
],
|
||||
*[
|
||||
(
|
||||
f"E/VATEX-EU-151-1{letter.upper()}",
|
||||
lazy(
|
||||
lambda let: pgettext(
|
||||
"tax_code",
|
||||
"Exempt based on article {article}, section {section} ({letter}) of Council "
|
||||
"Directive 2006/112/EC"
|
||||
).format(article="151", section="1", letter=let),
|
||||
str
|
||||
)(letter)
|
||||
) for letter in ("a", "aa", "b", "c", "d", "e")
|
||||
],
|
||||
("E/VATEX-EU-309",
|
||||
pgettext_lazy("tax_code", "Exempt based on article 309 of Council Directive 2006/112/EC")),
|
||||
("E/VATEX-EU-D",
|
||||
pgettext_lazy("tax_code", "Intra-Community acquisition from second hand means of transport")),
|
||||
("E/VATEX-EU-F",
|
||||
pgettext_lazy("tax_code", "Intra-Community acquisition of second hand goods")),
|
||||
("E/VATEX-EU-I",
|
||||
pgettext_lazy("tax_code", "Intra-Community acquisition of works of art")),
|
||||
("E/VATEX-EU-J",
|
||||
pgettext_lazy("tax_code", "Intra-Community acquisition of collectors items and antiques")),
|
||||
("E/VATEX-FR-FRANCHISE",
|
||||
pgettext_lazy("tax_code", "France domestic VAT franchise in base")),
|
||||
("E/VATEX-FR-CNWVAT",
|
||||
pgettext_lazy("tax_code", "France domestic Credit Notes without VAT, due to supplier forfeit of VAT for discount")),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_tax_code_labels():
|
||||
flat = []
|
||||
for choice, value in TAX_CODE_LISTS:
|
||||
if isinstance(value, (list, tuple)):
|
||||
flat.extend(value)
|
||||
else:
|
||||
flat.append((choice, value))
|
||||
|
||||
return dict(make_hashable(flat))
|
||||
|
||||
|
||||
def is_eu_country(cc):
|
||||
cc = str(cc)
|
||||
return cc in EU_COUNTRIES
|
||||
@@ -173,6 +326,14 @@ class TaxRule(LoggedModel):
|
||||
help_text=_('Should be short, e.g. "VAT"'),
|
||||
max_length=190,
|
||||
)
|
||||
code = models.CharField(
|
||||
verbose_name=_('Tax code'),
|
||||
help_text=_('If you help us understand what this tax rules legally is, we can use this information for '
|
||||
'eInvoices, exporting to accounting system, etc.'),
|
||||
null=True, blank=True,
|
||||
max_length=190,
|
||||
choices=TAX_CODE_LISTS,
|
||||
)
|
||||
rate = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
@@ -250,6 +411,16 @@ class TaxRule(LoggedModel):
|
||||
if self.eu_reverse_charge and not self.home_country:
|
||||
raise ValidationError(_('You need to set your home country to use the reverse charge feature.'))
|
||||
|
||||
if self.rate != Decimal("0.00") and self.code and (self.code.split("/")[0] in ("O", "E", "Z", "G", "K", "AE")):
|
||||
raise ValidationError({
|
||||
"code": _("A combination of this tax code with a non-zero tax rate does not make sense.")
|
||||
})
|
||||
|
||||
if self.rate == Decimal("0.00") and self.code and (self.code.split("/")[0] in ("S", "L", "M", "B")):
|
||||
raise ValidationError({
|
||||
"code": _("A combination of this tax code with a zero tax rate does not make sense.")
|
||||
})
|
||||
|
||||
def __str__(self):
|
||||
if self.price_includes_tax:
|
||||
s = _('incl. {rate}% {name}').format(rate=self.rate, name=self.name)
|
||||
@@ -276,8 +447,9 @@ class TaxRule(LoggedModel):
|
||||
return Decimal(rule.get('rate'))
|
||||
return Decimal(self.rate)
|
||||
|
||||
def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, invoice_address=None,
|
||||
subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None, force_fixed_gross_price=False):
|
||||
def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, override_tax_code=None,
|
||||
invoice_address=None, subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None,
|
||||
force_fixed_gross_price=False):
|
||||
from .event import Event
|
||||
try:
|
||||
currency = currency or self.event.currency
|
||||
@@ -285,6 +457,13 @@ class TaxRule(LoggedModel):
|
||||
pass
|
||||
|
||||
rate = Decimal(self.rate)
|
||||
code = self.code
|
||||
|
||||
if override_tax_code is not None:
|
||||
code = override_tax_code
|
||||
elif invoice_address:
|
||||
code = self.tax_code_for(invoice_address)
|
||||
|
||||
if override_tax_rate is not None:
|
||||
rate = override_tax_rate
|
||||
elif invoice_address:
|
||||
@@ -317,11 +496,8 @@ class TaxRule(LoggedModel):
|
||||
if rate == Decimal('0.00'):
|
||||
gross = _limit_subtract(base_price, subtract_from_gross)
|
||||
return TaxedPrice(
|
||||
net=gross,
|
||||
gross=gross,
|
||||
tax=Decimal('0.00'),
|
||||
rate=rate,
|
||||
name=self.name,
|
||||
net=gross, gross=gross, tax=Decimal('0.00'),
|
||||
rate=rate, name=self.name, code=code,
|
||||
)
|
||||
|
||||
if base_price_is == 'auto':
|
||||
@@ -346,7 +522,7 @@ class TaxRule(LoggedModel):
|
||||
|
||||
return TaxedPrice(
|
||||
net=net, gross=gross, tax=gross - net,
|
||||
rate=rate, name=self.name
|
||||
rate=rate, name=self.name, code=code,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -427,6 +603,38 @@ class TaxRule(LoggedModel):
|
||||
return True
|
||||
return False
|
||||
|
||||
def tax_code_for(self, invoice_address):
|
||||
if self._custom_rules:
|
||||
rule = self.get_matching_rule(invoice_address)
|
||||
if rule.get("code"):
|
||||
return rule["code"]
|
||||
if rule.get("action", "vat") == "reverse":
|
||||
return "AE"
|
||||
return self.code
|
||||
|
||||
if not self.eu_reverse_charge:
|
||||
# No reverse charge rules? Always apply VAT!
|
||||
return self.code
|
||||
|
||||
if not invoice_address or not invoice_address.country:
|
||||
# No country specified? Always apply VAT!
|
||||
return self.code
|
||||
|
||||
if not is_eu_country(invoice_address.country):
|
||||
# Non-EU country? "Non-taxable" since not in scope
|
||||
return "O"
|
||||
|
||||
if invoice_address.country == self.home_country:
|
||||
# Within same EU country? Always apply VAT!
|
||||
return self.code
|
||||
|
||||
if invoice_address.is_business and invoice_address.vat_id and invoice_address.vat_id_validated:
|
||||
# Reverse charge case
|
||||
return "AE"
|
||||
|
||||
# Consumer in different EU country / invalid VAT
|
||||
return self.code
|
||||
|
||||
def _tax_applicable(self, invoice_address):
|
||||
if self._custom_rules:
|
||||
rule = self.get_matching_rule(invoice_address)
|
||||
|
||||
+34
-33
@@ -1419,50 +1419,51 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
def payment_refund_supported(self, payment: OrderPayment) -> bool:
|
||||
return True
|
||||
|
||||
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
|
||||
from pretix.base.services.cart import add_payment_to_cart
|
||||
def _add_giftcard_to_cart(self, cs, gc):
|
||||
from pretix.base.services.cart import add_payment_to_cart_session
|
||||
|
||||
if gc.currency != self.event.currency:
|
||||
raise ValidationError(_("This gift card does not support this currency."))
|
||||
if gc.testmode and not self.event.testmode:
|
||||
raise ValidationError(_("This gift card can only be used in test mode."))
|
||||
if not gc.testmode and self.event.testmode:
|
||||
raise ValidationError(_("Only test gift cards can be used in test mode."))
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
raise ValidationError(_("This gift card is no longer valid."))
|
||||
if gc.value <= Decimal("0.00"):
|
||||
raise ValidationError(_("All credit on this gift card has been used."))
|
||||
|
||||
for p in cs.get('payments', []):
|
||||
if p['provider'] == self.identifier and p['info_data']['gift_card'] == gc.pk:
|
||||
raise ValidationError(_("This gift card is already used for your payment."))
|
||||
|
||||
add_payment_to_cart_session(
|
||||
cs,
|
||||
self,
|
||||
max_value=gc.value,
|
||||
info_data={
|
||||
'gift_card': gc.pk,
|
||||
'gift_card_secret': gc.secret,
|
||||
}
|
||||
)
|
||||
|
||||
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
|
||||
for p in get_cart(request):
|
||||
if p.item.issue_giftcard:
|
||||
messages.error(request, _("You cannot pay with gift cards when buying a gift card."))
|
||||
return
|
||||
|
||||
cs = cart_session(request)
|
||||
try:
|
||||
gc = self.event.organizer.accepted_gift_cards.get(
|
||||
secret=request.POST.get("giftcard").strip()
|
||||
)
|
||||
if gc.currency != self.event.currency:
|
||||
messages.error(request, _("This gift card does not support this currency."))
|
||||
cs = cart_session(request)
|
||||
try:
|
||||
self._add_giftcard_to_cart(cs, gc)
|
||||
return True
|
||||
except ValidationError as e:
|
||||
messages.error(request, str(e.message))
|
||||
return
|
||||
if gc.testmode and not self.event.testmode:
|
||||
messages.error(request, _("This gift card can only be used in test mode."))
|
||||
return
|
||||
if not gc.testmode and self.event.testmode:
|
||||
messages.error(request, _("Only test gift cards can be used in test mode."))
|
||||
return
|
||||
if gc.expires and gc.expires < time_machine_now():
|
||||
messages.error(request, _("This gift card is no longer valid."))
|
||||
return
|
||||
if gc.value <= Decimal("0.00"):
|
||||
messages.error(request, _("All credit on this gift card has been used."))
|
||||
return
|
||||
|
||||
for p in cs.get('payments', []):
|
||||
if p['provider'] == self.identifier and p['info_data']['gift_card'] == gc.pk:
|
||||
messages.error(request, _("This gift card is already used for your payment."))
|
||||
return
|
||||
|
||||
add_payment_to_cart(
|
||||
request,
|
||||
self,
|
||||
max_value=gc.value,
|
||||
info_data={
|
||||
'gift_card': gc.pk,
|
||||
'gift_card_secret': gc.secret,
|
||||
}
|
||||
)
|
||||
return True
|
||||
except GiftCard.DoesNotExist:
|
||||
if self.event.vouchers.filter(code__iexact=request.POST.get("giftcard")).exists():
|
||||
messages.warning(request, _("You entered a voucher instead of a gift card. Vouchers can only be entered on the first page of the shop below "
|
||||
|
||||
@@ -1426,6 +1426,28 @@ class CartManager:
|
||||
raise CartError(err)
|
||||
|
||||
|
||||
def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
|
||||
"""
|
||||
:param cart_session: The current cart session.
|
||||
:param provider: The instance of your payment provider.
|
||||
:param min_value: The minimum value this payment instrument supports, or ``None`` for unlimited.
|
||||
:param max_value: The maximum value this payment instrument supports, or ``None`` for unlimited. Highly discouraged
|
||||
to use for payment providers which charge a payment fee, as this can be very user-unfriendly if
|
||||
users need a second payment method just for the payment fee of the first method.
|
||||
:param info_data: A dictionary of information that will be passed through to the ``OrderPayment.info_data`` attribute.
|
||||
:return:
|
||||
"""
|
||||
cart_session.setdefault('payments', [])
|
||||
cart_session['payments'].append({
|
||||
'id': str(uuid.uuid4()),
|
||||
'provider': provider.identifier,
|
||||
'multi_use_supported': provider.multi_use_supported,
|
||||
'min_value': str(min_value) if min_value is not None else None,
|
||||
'max_value': str(max_value) if max_value is not None else None,
|
||||
'info_data': info_data or {},
|
||||
})
|
||||
|
||||
|
||||
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
|
||||
"""
|
||||
:param request: The current HTTP request context.
|
||||
@@ -1440,16 +1462,7 @@ def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: D
|
||||
from pretix.presale.views.cart import cart_session
|
||||
|
||||
cs = cart_session(request)
|
||||
cs.setdefault('payments', [])
|
||||
|
||||
cs['payments'].append({
|
||||
'id': str(uuid.uuid4()),
|
||||
'provider': provider.identifier,
|
||||
'multi_use_supported': provider.multi_use_supported,
|
||||
'min_value': str(min_value) if min_value is not None else None,
|
||||
'max_value': str(max_value) if max_value is not None else None,
|
||||
'info_data': info_data or {},
|
||||
})
|
||||
add_payment_to_cart_session(cs, provider, min_value, max_value, info_data)
|
||||
|
||||
|
||||
def get_fees(event, request, total, invoice_address, payments, positions):
|
||||
@@ -1500,6 +1513,7 @@ def get_fees(event, request, total, invoice_address, payments, positions):
|
||||
value=payment_fee,
|
||||
tax_rate=payment_fee_tax.rate,
|
||||
tax_value=payment_fee_tax.tax,
|
||||
tax_code=payment_fee_tax.code,
|
||||
tax_rule=payment_fee_tax_rule
|
||||
))
|
||||
|
||||
|
||||
@@ -271,7 +271,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
event_date_from=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from,
|
||||
event_date_to=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to,
|
||||
event_location=location if invoice.event.settings.invoice_event_location else None,
|
||||
tax_rate=p.tax_rate, tax_name=p.tax_rule.name if p.tax_rule else ''
|
||||
tax_rate=p.tax_rate,
|
||||
tax_code=p.tax_code,
|
||||
tax_name=p.tax_rule.name if p.tax_rule else ''
|
||||
)
|
||||
|
||||
if p.tax_rule and p.tax_rule.is_reverse_charge(ia) and p.price and not p.tax_value:
|
||||
@@ -305,6 +307,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
),
|
||||
tax_value=fee.tax_value,
|
||||
tax_rate=fee.tax_rate,
|
||||
tax_code=fee.tax_code,
|
||||
tax_name=fee.tax_rule.name if fee.tax_rule else '',
|
||||
fee_type=fee.fee_type,
|
||||
fee_internal_type=fee.internal_type or None,
|
||||
@@ -491,13 +494,13 @@ def build_preview_invoice_pdf(event):
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=_("Sample product {}").format(i + 1),
|
||||
gross_value=tax.gross, tax_value=tax.tax,
|
||||
tax_rate=tax.rate, tax_name=tax.name
|
||||
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
|
||||
)
|
||||
else:
|
||||
for i in range(5):
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=_("Sample product A"),
|
||||
gross_value=100, tax_value=0, tax_rate=0
|
||||
gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
|
||||
)
|
||||
|
||||
return event.invoice_renderer.generate(invoice)
|
||||
|
||||
@@ -76,7 +76,7 @@ from pretix.base.services.tasks import TransactionAwareTask
|
||||
from pretix.base.services.tickets import get_tickets_for_order
|
||||
from pretix.base.signals import email_filter, global_email_filter
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.ical import get_private_icals
|
||||
@@ -311,7 +311,13 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
try:
|
||||
if plain_text_only:
|
||||
body_html = None
|
||||
elif 'context' in inspect.signature(renderer.render).parameters:
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without context argument because context argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||
else:
|
||||
# Backwards compatibility
|
||||
@@ -323,6 +329,8 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
logger.exception('Could not render HTML body')
|
||||
body_html = None
|
||||
|
||||
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||
|
||||
send_task = mail_send_task.si(
|
||||
to=[email] if isinstance(email, str) else list(email),
|
||||
cc=cc,
|
||||
@@ -655,7 +663,7 @@ def render_mail(template, context):
|
||||
if isinstance(template, LazyI18nString):
|
||||
body = str(template)
|
||||
if context:
|
||||
body = format_map(body, context)
|
||||
body = format_map(body, context, mode=SafeFormatter.MODE_IGNORE_RICH)
|
||||
else:
|
||||
tpl = get_template(template)
|
||||
body = tpl.render(context)
|
||||
|
||||
@@ -118,7 +118,7 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
|
||||
c.assign(record.get(c.identifier), order, position, order._address)
|
||||
|
||||
if position.seat is not None:
|
||||
lock_seats.append(position.seat)
|
||||
lock_seats.append((order.sales_channel, position.seat))
|
||||
except (ValidationError, ImportError) as e:
|
||||
raise DataImportError(
|
||||
_('Invalid data in row {row}: {message}').format(row=i, message=str(e))
|
||||
@@ -128,9 +128,9 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
|
||||
with transaction.atomic():
|
||||
# We don't support vouchers, quotas, or memberships here, so we only need to lock if seats are in use
|
||||
if lock_seats:
|
||||
lock_objects(lock_seats, shared_lock_objects=[event])
|
||||
for s in lock_seats:
|
||||
if not s.is_available():
|
||||
lock_objects([s for c, s in lock_seats], shared_lock_objects=[event])
|
||||
for c, s in lock_seats:
|
||||
if not s.is_available(sales_channel=c):
|
||||
raise DataImportError(_('The seat you selected has already been taken. Please select a different seat.'))
|
||||
|
||||
save_transactions = []
|
||||
|
||||
@@ -1721,16 +1721,17 @@ class OrderChangeManager:
|
||||
|
||||
try:
|
||||
new_rate = tax_rule.tax_rate_for(ia)
|
||||
new_code = tax_rule.tax_code_for(ia)
|
||||
except TaxRule.SaleNotAllowed:
|
||||
raise OrderError(error_messages['tax_rule_country_blocked'])
|
||||
# We use override_tax_rate to make sure .tax() doesn't get clever and re-adjusts the pricing itself
|
||||
if new_rate != pos.tax_rate:
|
||||
if new_rate != pos.tax_rate or new_code != pos.tax_code:
|
||||
if keep == 'net':
|
||||
new_tax = tax_rule.tax(pos.price - pos.tax_value, base_price_is='net', currency=self.event.currency,
|
||||
override_tax_rate=new_rate)
|
||||
override_tax_rate=new_rate, override_tax_code=new_code)
|
||||
else:
|
||||
new_tax = tax_rule.tax(pos.price, base_price_is='gross', currency=self.event.currency,
|
||||
override_tax_rate=new_rate)
|
||||
override_tax_rate=new_rate, override_tax_code=new_code)
|
||||
self._totaldiff += new_tax.gross - pos.price
|
||||
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
|
||||
self._invoice_dirty = True
|
||||
@@ -2304,6 +2305,7 @@ class OrderChangeManager:
|
||||
op.position.price = op.price.gross
|
||||
op.position.tax_rate = op.price.rate
|
||||
op.position.tax_value = op.price.tax
|
||||
op.position.tax_code = op.price.code
|
||||
op.position.save()
|
||||
elif isinstance(op, self.TaxRuleOperation):
|
||||
if isinstance(op.position, OrderPosition):
|
||||
@@ -2400,7 +2402,7 @@ class OrderChangeManager:
|
||||
elif isinstance(op, self.AddOperation):
|
||||
pos = OrderPosition.objects.create(
|
||||
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
||||
price=op.price.gross, order=self.order, tax_rate=op.price.rate,
|
||||
price=op.price.gross, order=self.order, tax_rate=op.price.rate, tax_code=op.price.code,
|
||||
tax_value=op.price.tax, tax_rule=op.item.tax_rule,
|
||||
positionid=nextposid, subevent=op.subevent, seat=op.seat,
|
||||
used_membership=op.membership, valid_from=op.valid_from, valid_until=op.valid_until,
|
||||
@@ -2423,6 +2425,8 @@ class OrderChangeManager:
|
||||
elif isinstance(op, self.SplitOperation):
|
||||
split_positions.append(op.position)
|
||||
elif isinstance(op, self.RegenerateSecretOperation):
|
||||
op.position.web_secret = generate_secret()
|
||||
op.position.save(update_fields=["web_secret"])
|
||||
assign_ticket_secret(
|
||||
event=self.event, position=op.position, force_invalidate=True, save=True
|
||||
)
|
||||
@@ -2529,6 +2533,7 @@ class OrderChangeManager:
|
||||
'new_order': split_order.code,
|
||||
})
|
||||
op.order = split_order
|
||||
op.web_secret = generate_secret()
|
||||
assign_ticket_secret(
|
||||
self.event, position=op, force_invalidate=True,
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ from decimal import Decimal
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -39,7 +40,8 @@ from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
|
||||
from pretix.base.signals import (
|
||||
register_mail_placeholders, register_text_placeholders,
|
||||
)
|
||||
from pretix.helpers.format import SafeFormatter
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.helpers.format import PlainHtmlAlternativeString, SafeFormatter
|
||||
|
||||
logger = logging.getLogger('pretix.base.services.placeholders')
|
||||
|
||||
@@ -107,6 +109,91 @@ class SimpleFunctionalTextPlaceholder(BaseTextPlaceholder):
|
||||
return self._sample
|
||||
|
||||
|
||||
class BaseRichTextPlaceholder(BaseTextPlaceholder):
|
||||
"""
|
||||
This is the base class for all placeholders which can render either to plain text
|
||||
or to a rich HTML element.
|
||||
"""
|
||||
|
||||
def __init__(self, identifier, args):
|
||||
self._identifier = identifier
|
||||
self._args = args
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self._identifier
|
||||
|
||||
@property
|
||||
def required_context(self):
|
||||
return self._args
|
||||
|
||||
@property
|
||||
def is_block(self):
|
||||
return False
|
||||
|
||||
def render(self, context):
|
||||
return PlainHtmlAlternativeString(
|
||||
self.render_plain(**{k: context[k] for k in self._args}),
|
||||
self.render_html(**{k: context[k] for k in self._args}),
|
||||
self.is_block,
|
||||
)
|
||||
|
||||
def render_html(self, **kwargs):
|
||||
"""
|
||||
HTML rendering of the placeholder. Should return "safe" HTML, i.e. everything needs to be
|
||||
escaped.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def render_plain(self, **kwargs):
|
||||
"""
|
||||
Plain text rendering of the placeholder.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def render_sample(self, event):
|
||||
return PlainHtmlAlternativeString(
|
||||
self.render_sample_plain(event=event),
|
||||
self.render_sample_html(event=event),
|
||||
self.is_block,
|
||||
)
|
||||
|
||||
def render_sample_html(self, event):
|
||||
raise NotImplementedError
|
||||
|
||||
def render_sample_plain(self, event):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SimpleButtonPlaceholder(BaseRichTextPlaceholder):
|
||||
def __init__(self, identifier, args, url_func, text_func, sample_url_func, sample_text_func):
|
||||
super().__init__(identifier, args)
|
||||
self._url_func = url_func
|
||||
self._text_func = text_func
|
||||
self._sample_url_func = sample_url_func
|
||||
self._sample_text_func = sample_text_func
|
||||
|
||||
def render_html(self, **context):
|
||||
text = self._text_func(**{k: context[k] for k in self._args})
|
||||
url = self._url_func(**{k: context[k] for k in self._args})
|
||||
return f'<a href="{url}" class="button">{escape(text)}</a>'
|
||||
|
||||
def render_plain(self, **context):
|
||||
text = self._text_func(**{k: context[k] for k in self._args})
|
||||
url = self._url_func(**{k: context[k] for k in self._args})
|
||||
return f'{text}: {url}'
|
||||
|
||||
def render_sample_html(self, event):
|
||||
text = self._sample_text_func(event)
|
||||
url = self._sample_url_func(event)
|
||||
return f'<a href="{url}" class="button">{escape(text)}</a>'
|
||||
|
||||
def render_sample_plain(self, event):
|
||||
text = self._sample_text_func(event)
|
||||
url = self._sample_url_func(event)
|
||||
return f'{text}: {url}'
|
||||
|
||||
|
||||
class PlaceholderContext(SafeFormatter):
|
||||
"""
|
||||
Holds the contextual arguments and corresponding list of available placeholders for formatting
|
||||
@@ -284,6 +371,27 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['order', 'event'],
|
||||
url_func=lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_secret()
|
||||
}
|
||||
),
|
||||
text_func=lambda order, event: _("View order details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'hash': '98kusd8ofsj8dnkd'
|
||||
}
|
||||
),
|
||||
sample_text_func=lambda event: _("View order details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
@@ -348,6 +456,27 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleButtonPlaceholder(
|
||||
'url_button', ['event', 'position'],
|
||||
url_func=lambda event, position: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
),
|
||||
text_func=lambda event, position: _("View registration details"),
|
||||
sample_url_func=lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'position': '123'
|
||||
}
|
||||
),
|
||||
sample_text_func=lambda event: _("View registration details"),
|
||||
),
|
||||
SimpleFunctionalTextPlaceholder(
|
||||
'url_info_change', ['position', 'event'], lambda position, event: build_absolute_uri(
|
||||
event,
|
||||
@@ -603,8 +732,8 @@ def base_placeholders(sender, **kwargs):
|
||||
|
||||
|
||||
class FormPlaceholderMixin:
|
||||
def _set_field_placeholders(self, fn, base_parameters):
|
||||
placeholders = get_available_placeholders(self.event, base_parameters)
|
||||
def _set_field_placeholders(self, fn, base_parameters, rich=False):
|
||||
placeholders = get_available_placeholders(self.event, base_parameters, rich=rich)
|
||||
ht = format_placeholders_help_text(placeholders, self.event)
|
||||
if self.fields[fn].help_text:
|
||||
self.fields[fn].help_text += ' ' + str(ht)
|
||||
@@ -615,7 +744,7 @@ class FormPlaceholderMixin:
|
||||
)
|
||||
|
||||
|
||||
def get_available_placeholders(event, base_parameters):
|
||||
def get_available_placeholders(event, base_parameters, rich=False):
|
||||
if 'order' in base_parameters:
|
||||
base_parameters.append('invoice_address')
|
||||
base_parameters.append('position_or_address')
|
||||
@@ -624,6 +753,35 @@ def get_available_placeholders(event, base_parameters):
|
||||
if not isinstance(val, (list, tuple)):
|
||||
val = [val]
|
||||
for v in val:
|
||||
if isinstance(v, BaseRichTextPlaceholder) and not rich:
|
||||
continue
|
||||
if all(rp in base_parameters for rp in v.required_context):
|
||||
params[v.identifier] = v
|
||||
return params
|
||||
|
||||
|
||||
def get_sample_context(event, context_parameters, rich=True):
|
||||
context_dict = {}
|
||||
lbl = _('This value will be replaced based on dynamic parameters.')
|
||||
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items():
|
||||
sample = v.render_sample(event)
|
||||
if isinstance(sample, PlainHtmlAlternativeString):
|
||||
context_dict[k] = PlainHtmlAlternativeString(
|
||||
sample.plain,
|
||||
'<{el} class="placeholder placeholder-html" title="{title}">{html}</{el}>'.format(
|
||||
el='div' if sample.is_block else 'span',
|
||||
title=lbl,
|
||||
html=sample.html,
|
||||
)
|
||||
)
|
||||
elif str(sample).strip().startswith('* ') or str(sample).startswith(' '):
|
||||
context_dict[k] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
lbl,
|
||||
markdown_compile_email(str(sample))
|
||||
)
|
||||
else:
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
lbl,
|
||||
escape(sample)
|
||||
)
|
||||
return context_dict
|
||||
|
||||
@@ -91,9 +91,11 @@ def get_price(item: Item, variation: ItemVariation = None,
|
||||
|
||||
if custom_price_is_net:
|
||||
price = tax_rule.tax(max(custom_price, price.net), base_price_is='net', override_tax_rate=price.rate,
|
||||
override_tax_code=price.code,
|
||||
invoice_address=invoice_address, subtract_from_gross=bundled_sum)
|
||||
else:
|
||||
price = tax_rule.tax(max(custom_price, price.gross), base_price_is='gross', override_tax_rate=price.rate,
|
||||
override_tax_code=price.code,
|
||||
invoice_address=invoice_address, subtract_from_gross=bundled_sum)
|
||||
else:
|
||||
price = tax_rule.tax(price, invoice_address=invoice_address, subtract_from_gross=bundled_sum)
|
||||
@@ -146,10 +148,12 @@ def get_line_price(price_after_voucher: Decimal, custom_price_input: Decimal, cu
|
||||
|
||||
if custom_price_input_is_net:
|
||||
price = tax_rule.tax(max(custom_price_input, price.net), base_price_is='net', override_tax_rate=price.rate,
|
||||
invoice_address=invoice_address, subtract_from_gross=bundled_sum)
|
||||
override_tax_code=price.code, invoice_address=invoice_address,
|
||||
subtract_from_gross=bundled_sum)
|
||||
else:
|
||||
price = tax_rule.tax(max(custom_price_input, price.gross), base_price_is='gross', override_tax_rate=price.rate,
|
||||
invoice_address=invoice_address, subtract_from_gross=bundled_sum)
|
||||
override_tax_code=price.code, invoice_address=invoice_address,
|
||||
subtract_from_gross=bundled_sum)
|
||||
else:
|
||||
price = tax_rule.tax(price_after_voucher, invoice_address=invoice_address, subtract_from_gross=bundled_sum,
|
||||
base_price_is='gross' if is_bundled else 'auto')
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#
|
||||
import logging
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.timezone import now
|
||||
@@ -97,9 +98,9 @@ def preview(event: int, provider: str):
|
||||
event = Event.objects.get(id=event)
|
||||
|
||||
with rolledback_transaction(), language(event.settings.locale, event.settings.region):
|
||||
item = event.items.create(name=_("Sample product"), default_price=42.23,
|
||||
item = event.items.create(name=_("Sample product"), default_price=Decimal('42.23'),
|
||||
description=_("Sample product description"))
|
||||
item2 = event.items.create(name=_("Sample workshop"), default_price=23.40)
|
||||
item2 = event.items.create(name=_("Sample workshop"), default_price=Decimal('23.40'))
|
||||
|
||||
from pretix.base.models import Order
|
||||
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
||||
|
||||
@@ -56,6 +56,7 @@ from django.utils.translation import (
|
||||
from django_countries.fields import Country
|
||||
from hierarkey.models import GlobalSettingsBase, Hierarkey
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||
from i18nfield.rest_framework import I18nField
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from phonenumbers import PhoneNumber, parse
|
||||
from rest_framework import serializers
|
||||
@@ -63,7 +64,7 @@ from rest_framework import serializers
|
||||
from pretix.api.serializers.fields import (
|
||||
ListMultipleChoiceField, UploadedFileField,
|
||||
)
|
||||
from pretix.api.serializers.i18n import I18nField, I18nURLField
|
||||
from pretix.api.serializers.i18n import I18nURLField
|
||||
from pretix.base.forms import I18nMarkdownTextarea, I18nURLFormField
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
|
||||
from pretix.base.reldate import (
|
||||
|
||||
@@ -131,6 +131,9 @@
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
.content table td.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a.button {
|
||||
display: inline-block;
|
||||
@@ -178,6 +181,9 @@
|
||||
pre, pre code {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.text-right, .content table td.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
{% if rtl %}
|
||||
body {
|
||||
@@ -186,6 +192,9 @@
|
||||
.content {
|
||||
text-align: right;
|
||||
}
|
||||
.text-right, .content table td.text-right {
|
||||
text-align: left;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% block addcss %}{% endblock %}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io 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.utils.html import format_html
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def icon(key, *args, **kwargs):
|
||||
return format_html(
|
||||
'<span class="fa fa-{} {}" aria-hidden="true"></span>',
|
||||
key,
|
||||
kwargs["class"] if "class" in kwargs else "",
|
||||
)
|
||||
@@ -52,12 +52,12 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
# would make the numbers incorrect. If this branch executes, it's likely a bug in
|
||||
# pretix, but we won't show wrong numbers!
|
||||
if hide_currency:
|
||||
return floatformat(value, 2)
|
||||
return floatformat(value, "2g")
|
||||
else:
|
||||
return '{} {}'.format(arg, floatformat(value, 2))
|
||||
return '{} {}'.format(arg, floatformat(value, "2g"))
|
||||
|
||||
if hide_currency:
|
||||
return floatformat(value, places)
|
||||
return floatformat(value, f"{places}g")
|
||||
|
||||
locale_parts = translation.get_language().split("-", 1)
|
||||
locale = locale_parts[0]
|
||||
@@ -70,7 +70,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
try:
|
||||
return format_currency(value, arg, locale=locale)
|
||||
except:
|
||||
return '{} {}'.format(arg, floatformat(value, places))
|
||||
return '{} {}'.format(arg, floatformat(value, f"{places}g"))
|
||||
|
||||
|
||||
@register.filter("money_numberfield")
|
||||
|
||||
@@ -305,6 +305,7 @@ def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes
|
||||
source,
|
||||
extensions=[
|
||||
'markdown.extensions.sane_lists',
|
||||
'markdown.extensions.tables',
|
||||
EmailNl2BrExtension(),
|
||||
LinkifyAndCleanExtension(
|
||||
linker,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io 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.utils.html import format_html, mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def textbubble(type, *args, **kwargs):
|
||||
return format_html(
|
||||
'<span class="textbubble-{}">{}',
|
||||
type or "info",
|
||||
"" if "icon" not in kwargs else format_html(
|
||||
'<i class="fa fa-{}" aria-hidden="true"></i> ',
|
||||
kwargs["icon"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def endtextbubble():
|
||||
return mark_safe('</span>')
|
||||
@@ -22,16 +22,30 @@
|
||||
import pycountry
|
||||
from django.http import JsonResponse
|
||||
|
||||
from pretix.base.addressvalidation import (
|
||||
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
|
||||
)
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES
|
||||
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||
|
||||
|
||||
def states(request):
|
||||
cc = request.GET.get("country", "DE")
|
||||
info = {
|
||||
'street': {'required': True},
|
||||
'zipcode': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||
'city': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED},
|
||||
'state': {'visible': cc in COUNTRIES_WITH_STATE_IN_ADDRESS, 'required': cc in COUNTRIES_WITH_STATE_IN_ADDRESS},
|
||||
'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False},
|
||||
}
|
||||
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
return JsonResponse({'data': []})
|
||||
return JsonResponse({'data': [], **info, })
|
||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||
return JsonResponse({'data': [
|
||||
{'name': s.name, 'code': s.code[3:]}
|
||||
for s in sorted(statelist, key=lambda s: s.name)
|
||||
]})
|
||||
return JsonResponse({
|
||||
'data': [
|
||||
{'name': s.name, 'code': s.code[3:]}
|
||||
for s in sorted(statelist, key=lambda s: s.name)
|
||||
],
|
||||
**info,
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from urllib.parse import urlencode
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pycountry
|
||||
@@ -63,6 +63,7 @@ from pretix.base.forms import (
|
||||
)
|
||||
from pretix.base.models import Event, Organizer, TaxRule, Team
|
||||
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
||||
from pretix.base.models.tax import TAX_CODE_LISTS
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.base.services.placeholders import FormPlaceholderMixin
|
||||
from pretix.base.settings import (
|
||||
@@ -76,8 +77,10 @@ from pretix.control.forms import (
|
||||
)
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.multidomain.models import AlternativeDomainAssignment, KnownDomain
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri, get_organizer_domain,
|
||||
)
|
||||
from pretix.plugins.banktransfer.payment import BankTransfer
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
@@ -363,14 +366,9 @@ class EventUpdateForm(I18nModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.change_slug = kwargs.pop('change_slug', False)
|
||||
self.domain = kwargs.pop('domain', False)
|
||||
|
||||
kwargs.setdefault('initial', {})
|
||||
self.instance = kwargs['instance']
|
||||
if self.domain and self.instance:
|
||||
initial_domain = self.instance.domains.first()
|
||||
if initial_domain:
|
||||
kwargs['initial'].setdefault('domain', initial_domain.domainname)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.change_slug:
|
||||
@@ -379,48 +377,54 @@ class EventUpdateForm(I18nModelForm):
|
||||
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||
'Sample Conference Center\nHeidelberg, Germany'
|
||||
)
|
||||
if self.domain:
|
||||
|
||||
try:
|
||||
self.fields['domain'] = forms.CharField(
|
||||
max_length=255,
|
||||
label=_('Custom domain'),
|
||||
label=_('Domain'),
|
||||
initial=self.instance.domain.domainname,
|
||||
required=False,
|
||||
disabled=True,
|
||||
help_text=_('You can configure this in your organizer settings.')
|
||||
)
|
||||
except KnownDomain.DoesNotExist:
|
||||
domain = get_organizer_domain(self.instance.organizer)
|
||||
try:
|
||||
current_domain_assignment = self.instance.alternative_domain_assignment
|
||||
except AlternativeDomainAssignment.DoesNotExist:
|
||||
current_domain_assignment = None
|
||||
self.fields['domain'] = forms.ChoiceField(
|
||||
label=_('Domain'),
|
||||
help_text=_('You can add more domains in your organizer account.'),
|
||||
choices=[('', _('Same as organizer account') + (f" ({domain})" if domain else ""))] + [
|
||||
(d.domainname, d.domainname) for d in self.instance.organizer.domains.filter(mode=KnownDomain.MODE_ORG_ALT_DOMAIN)
|
||||
],
|
||||
initial=current_domain_assignment.domain_id if current_domain_assignment else "",
|
||||
required=False,
|
||||
help_text=_('You need to configure the custom domain in the webserver beforehand.')
|
||||
)
|
||||
self.fields['limit_sales_channels'].queryset = self.event.organizer.sales_channels.all()
|
||||
self.fields['limit_sales_channels'].widget = SalesChannelCheckboxSelectMultiple(self.event, attrs={
|
||||
'data-inverse-dependency': '<[name$=all_sales_channels]',
|
||||
}, choices=self.fields['limit_sales_channels'].widget.choices)
|
||||
|
||||
def clean_domain(self):
|
||||
d = self.cleaned_data['domain']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(event=self.instance.pk).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit)
|
||||
|
||||
if self.domain:
|
||||
current_domain = instance.domains.first()
|
||||
if self.cleaned_data['domain']:
|
||||
if current_domain and current_domain.domainname != self.cleaned_data['domain']:
|
||||
current_domain.delete()
|
||||
KnownDomain.objects.create(
|
||||
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
|
||||
)
|
||||
elif not current_domain:
|
||||
KnownDomain.objects.create(
|
||||
organizer=instance.organizer, event=instance, domainname=self.cleaned_data['domain']
|
||||
)
|
||||
elif current_domain:
|
||||
current_domain.delete()
|
||||
try:
|
||||
current_domain_assignment = instance.alternative_domain_assignment
|
||||
except AlternativeDomainAssignment.DoesNotExist:
|
||||
current_domain_assignment = None
|
||||
if self.cleaned_data['domain'] and not hasattr(instance, 'domain'):
|
||||
domain = self.instance.organizer.domains.get(mode=KnownDomain.MODE_ORG_ALT_DOMAIN, domainname=self.cleaned_data["domain"])
|
||||
AlternativeDomainAssignment.objects.update_or_create(
|
||||
event=instance,
|
||||
defaults={
|
||||
"domain": domain,
|
||||
}
|
||||
)
|
||||
instance.cache.clear()
|
||||
elif current_domain_assignment:
|
||||
current_domain_assignment.delete()
|
||||
instance.cache.clear()
|
||||
|
||||
return instance
|
||||
@@ -1382,7 +1386,7 @@ class MailSettingsForm(FormPlaceholderMixin, SettingsForm):
|
||||
self.event.meta_values_cached = self.event.meta_values.select_related('property').all()
|
||||
|
||||
for k, v in self.base_context.items():
|
||||
self._set_field_placeholders(k, v)
|
||||
self._set_field_placeholders(k, v, rich=k.startswith('mail_text_'))
|
||||
|
||||
for k, v in list(self.fields.items()):
|
||||
if k.endswith('_attendee') and not event.settings.attendee_emails_asked:
|
||||
@@ -1501,6 +1505,11 @@ class TaxRuleLineForm(I18nForm):
|
||||
('require_approval', _('Order requires approval')),
|
||||
],
|
||||
)
|
||||
code = forms.ChoiceField(
|
||||
label=_("Tax code"),
|
||||
choices=[("", _("Default tax code")), *TAX_CODE_LISTS],
|
||||
required=False,
|
||||
)
|
||||
rate = forms.DecimalField(
|
||||
label=_('Deviating tax rate'),
|
||||
max_digits=10, decimal_places=2,
|
||||
@@ -1515,6 +1524,43 @@ class TaxRuleLineForm(I18nForm):
|
||||
})
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.parent_form = kwargs.pop("parent_form")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
parent_code = self.parent_form.cleaned_data.get("code")
|
||||
parent_rate = self.parent_form.cleaned_data.get("rate")
|
||||
|
||||
code = d.get("code") or parent_code
|
||||
rate = d.get("rate")
|
||||
if rate is None:
|
||||
rate = parent_rate
|
||||
|
||||
if d.get("action") in ("reverse", "no", "block") and d.get("rate"):
|
||||
raise ValidationError(_("A combination of this calculation mode with a non-zero tax rate does not make sense."))
|
||||
|
||||
if d.get("action") == "reverse" and d.get("code") and code != "AE":
|
||||
# Reverse charge but code is not reverse charge -- this is the one case we ignore if the "default code"
|
||||
# is used because it is the one scenario we can auto-fix
|
||||
raise ValidationError(_("This combination of calculation mode and tax code does not make sense."))
|
||||
|
||||
if d.get("action") == "no" and code and code.split("/")[0] in ("S", "AE", "L", "M", "B"):
|
||||
# No VAT but code indicates VAT
|
||||
raise ValidationError(_("This combination of calculation mode and tax code does not make sense."))
|
||||
|
||||
if d.get("action") == "vat" and code and rate != Decimal("0.00") and code.split("/")[0] in ("O", "E", "Z", "G", "K", "AE"):
|
||||
# VAT, but code indicates exempt
|
||||
raise ValidationError(_("A combination of this tax code with a non-zero tax rate does not make sense."))
|
||||
|
||||
if d.get("action") == "vat" and code and rate == Decimal("0.00") and code.split("/")[0] in ("S", "L", "M", "B"):
|
||||
# no VAT, but code indicates non-exempt
|
||||
raise ValidationError(_("A combination of this tax code with a zero tax rate does not make sense."))
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class I18nBaseFormSet(I18nFormSetMixin, forms.BaseFormSet):
|
||||
# compatibility shim for django-i18nfield library
|
||||
@@ -1526,8 +1572,16 @@ class I18nBaseFormSet(I18nFormSetMixin, forms.BaseFormSet):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class BaseTaxRuleLineFormSet(I18nBaseFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.parent_form = kwargs.pop('parent_form')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_kwargs['parent_form'] = self.parent_form
|
||||
|
||||
|
||||
TaxRuleLineFormSet = formset_factory(
|
||||
TaxRuleLineForm, formset=I18nBaseFormSet,
|
||||
TaxRuleLineForm, formset=BaseTaxRuleLineFormSet,
|
||||
can_order=True, can_delete=True, extra=0
|
||||
)
|
||||
|
||||
@@ -1535,7 +1589,16 @@ TaxRuleLineFormSet = formset_factory(
|
||||
class TaxRuleForm(I18nModelForm):
|
||||
class Meta:
|
||||
model = TaxRule
|
||||
fields = ['name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country', 'internal_name', 'keep_gross_if_rate_changes']
|
||||
fields = [
|
||||
'name',
|
||||
'rate',
|
||||
'price_includes_tax',
|
||||
'code',
|
||||
'eu_reverse_charge',
|
||||
'home_country',
|
||||
'internal_name',
|
||||
'keep_gross_if_rate_changes'
|
||||
]
|
||||
|
||||
|
||||
class WidgetCodeForm(forms.Form):
|
||||
|
||||
@@ -490,7 +490,9 @@ class OrderPositionChangeForm(forms.Form):
|
||||
)
|
||||
operation_secret = forms.BooleanField(
|
||||
required=False,
|
||||
label=_('Generate a new secret')
|
||||
label=_('Generate a new secret'),
|
||||
help_text=_('This affects both the ticket secret (often used as a QR code) as well as the link used to '
|
||||
'individually access the ticket.')
|
||||
)
|
||||
operation_cancel = forms.BooleanField(
|
||||
required=False,
|
||||
|
||||
@@ -133,63 +133,108 @@ class OrganizerDeleteForm(forms.Form):
|
||||
class OrganizerUpdateForm(OrganizerForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.domain = kwargs.pop('domain', False)
|
||||
self.change_slug = kwargs.pop('change_slug', False)
|
||||
kwargs.setdefault('initial', {})
|
||||
self.instance = kwargs['instance']
|
||||
if self.domain and self.instance:
|
||||
initial_domain = self.instance.domains.filter(event__isnull=True).first()
|
||||
if initial_domain:
|
||||
kwargs['initial'].setdefault('domain', initial_domain.domainname)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.change_slug:
|
||||
self.fields['slug'].widget.attrs['readonly'] = 'readonly'
|
||||
if self.domain:
|
||||
self.fields['domain'] = forms.CharField(
|
||||
max_length=255,
|
||||
label=_('Custom domain'),
|
||||
required=False,
|
||||
help_text=_('You need to configure the custom domain in the webserver beforehand.')
|
||||
)
|
||||
|
||||
def clean_domain(self):
|
||||
d = self.cleaned_data['domain']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.pk,
|
||||
event__isnull=True).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def clean_slug(self):
|
||||
if self.change_slug:
|
||||
return self.cleaned_data['slug']
|
||||
return self.instance.slug
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit)
|
||||
|
||||
if self.domain:
|
||||
current_domain = instance.domains.filter(event__isnull=True).first()
|
||||
if self.cleaned_data['domain']:
|
||||
if current_domain and current_domain.domainname != self.cleaned_data['domain']:
|
||||
current_domain.delete()
|
||||
KnownDomain.objects.create(organizer=instance, domainname=self.cleaned_data['domain'])
|
||||
elif not current_domain:
|
||||
KnownDomain.objects.create(organizer=instance, domainname=self.cleaned_data['domain'])
|
||||
elif current_domain:
|
||||
current_domain.delete()
|
||||
instance.cache.clear()
|
||||
for ev in instance.events.all():
|
||||
ev.cache.clear()
|
||||
class KnownDomainForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = KnownDomain
|
||||
fields = ["domainname", "mode", "event"]
|
||||
field_classes = {
|
||||
"event": SafeModelChoiceField,
|
||||
}
|
||||
|
||||
return instance
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["event"].queryset = self.organizer.events.all()
|
||||
if self.instance and self.instance.pk:
|
||||
self.fields["domainname"].widget.attrs['readonly'] = 'readonly'
|
||||
|
||||
def clean_domainname(self):
|
||||
if self.instance and self.instance.pk:
|
||||
return self.instance.domainname
|
||||
d = self.cleaned_data['domainname']
|
||||
if d:
|
||||
if d == urlparse(settings.SITE_URL).hostname:
|
||||
raise ValidationError(
|
||||
_('You cannot choose the base domain of this installation.')
|
||||
)
|
||||
if KnownDomain.objects.filter(domainname=d).exclude(organizer=self.instance.organizer).exists():
|
||||
raise ValidationError(
|
||||
_('This domain is already in use for a different event or organizer.')
|
||||
)
|
||||
return d
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_ORG_DOMAIN and d["event"]:
|
||||
raise ValidationError(
|
||||
_("Do not choose an event for this mode.")
|
||||
)
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_ORG_ALT_DOMAIN and d["event"]:
|
||||
raise ValidationError(
|
||||
_("Do not choose an event for this mode. You can assign events to this domain in event settings.")
|
||||
)
|
||||
|
||||
if d["mode"] == KnownDomain.MODE_EVENT_DOMAIN and not d["event"]:
|
||||
raise ValidationError(
|
||||
_("You need to choose an event.")
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class BaseKnownDomainFormSet(forms.BaseInlineFormSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['organizer'] = self.organizer
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
organizer=self.organizer,
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
data = [f.cleaned_data for f in self.forms]
|
||||
|
||||
if len([d for d in data if d.get("mode") == KnownDomain.MODE_ORG_DOMAIN and not d.get("DELETE")]) > 1:
|
||||
raise ValidationError(_("You may set only one organizer domain."))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
KnownDomainFormset = inlineformset_factory(
|
||||
Organizer, KnownDomain,
|
||||
KnownDomainForm,
|
||||
formset=BaseKnownDomainFormSet,
|
||||
can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
|
||||
|
||||
class SafeOrderPositionChoiceField(forms.ModelChoiceField):
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
||||
<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>
|
||||
{% endcompress %}
|
||||
{{ html_head|safe }}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.slug layout="control" %}
|
||||
{% if form.domain %}
|
||||
{% bootstrap_field form.domain layout="control" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.date_from layout="control" %}
|
||||
{% bootstrap_field form.date_to layout="control" %}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.internal_name layout="control" %}
|
||||
{% bootstrap_field form.rate addon_after="%" layout="control" %}
|
||||
{% bootstrap_field form.code layout="control" %}
|
||||
{% bootstrap_field form.price_includes_tax layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@@ -52,6 +53,18 @@
|
||||
{% trans "All of these rules will only apply if an invoice address is set." %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<strong>{% trans "Condition" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<strong>{% trans "Calculation" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-4 col-sm-offset-6 col-md-offset-0">
|
||||
<strong>{% trans "Reason" %}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="formset tax-rules-formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
@@ -65,14 +78,17 @@
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field formset.empty_form.country layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-4">
|
||||
{% bootstrap_field formset.empty_form.address_type layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field formset.empty_form.action layout='inline' form_group_class="" %}
|
||||
{% bootstrap_field formset.empty_form.rate layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 text-right flip">
|
||||
<div class="col-sm-6 col-md-3 col-lg-4 col-sm-offset-6 col-md-offset-0">
|
||||
{% bootstrap_field formset.empty_form.code layout='inline' form_group_class="" %}
|
||||
{% bootstrap_field formset.empty_form.invoice_text layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 col-sm-offset-6 col-md-offset-0 text-right flip">
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
@@ -80,12 +96,6 @@
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-4 col-md-offset-3">
|
||||
{% bootstrap_field formset.empty_form.invoice_text layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field formset.empty_form.rate layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
@@ -100,14 +110,17 @@
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field form.country layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-4">
|
||||
{% bootstrap_field form.address_type layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field form.action layout='inline' form_group_class="" %}
|
||||
{% bootstrap_field form.rate layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 text-right flip">
|
||||
<div class="col-sm-6 col-md-3 col-lg-4 col-sm-offset-6 col-md-offset-0">
|
||||
{% bootstrap_field form.code layout='inline' form_group_class="" %}
|
||||
{% bootstrap_field form.invoice_text layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2 col-sm-offset-6 col-md-offset-0 text-right flip">
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
@@ -115,12 +128,6 @@
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-4 col-md-offset-3">
|
||||
{% bootstrap_field form.invoice_text layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
{% bootstrap_field form.rate layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -220,7 +220,6 @@
|
||||
{% endif %}
|
||||
{% bootstrap_field formset.empty_form.available_from visibility_field=formset.empty_form.available_from_mode layout="control_with_visibility" %}
|
||||
{% bootstrap_field formset.empty_form.available_until visibility_field=formset.empty_form.available_until_mode layout="control_with_visibility" %}
|
||||
{% bootstrap_field formset.empty_form.available_until layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.all_sales_channels layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.limit_sales_channels layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.hide_without_voucher layout="control" %}
|
||||
|
||||
@@ -202,6 +202,12 @@
|
||||
+ {{ position.tax_rate }}%)
|
||||
</small>
|
||||
{% endif %}
|
||||
{% if position.tax_code %}
|
||||
<br>
|
||||
<small>
|
||||
{{ position.get_tax_code_display }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-4 field-container">
|
||||
{% bootstrap_field position.form.price addon_after=request.event.currency layout='inline' %}
|
||||
@@ -420,6 +426,12 @@
|
||||
+ {{ fee.tax_rate }}%)
|
||||
</small>
|
||||
{% endif %}
|
||||
{% if fee.tax_code %}
|
||||
<br>
|
||||
<small>
|
||||
{{ fee.get_tax_code_display }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-4 field-container">
|
||||
{% bootstrap_field fee.form.value addon_after=request.event.currency layout='inline' %}
|
||||
|
||||
@@ -46,11 +46,13 @@
|
||||
<div id="cp{{ pos.id }}">
|
||||
<div class="panel-body">
|
||||
{% for form in forms %}
|
||||
{% if form.pos.item != pos.item %}
|
||||
{# Add-Ons #}
|
||||
<legend>+ {{ form.pos.item }}</legend>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
<div class="profile-scope">
|
||||
{% if form.pos.item != pos.item %}
|
||||
{# Add-Ons #}
|
||||
<legend>+ {{ form.pos.item }}</legend>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="control" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Product" %}</th>
|
||||
<th class="text-right flip">{% trans "Tax rate" %}</th>
|
||||
<th>{% trans "Tax code" %}</th>
|
||||
<th class="text-right flip">{% trans "Quantity" %}</th>
|
||||
<th class="text-right flip">{% trans "Single price" %}</th>
|
||||
<th class="text-right flip">{% trans "Total tax value" %}</th>
|
||||
@@ -52,6 +53,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right flip">{{ t.tax_rate }} %</td>
|
||||
<td>{{ t.get_tax_code_display }}</td>
|
||||
<td class="text-right flip">{{ t.count }} ×</td>
|
||||
<td class="text-right flip">{{ t.price|money:request.event.currency }}</td>
|
||||
<td class="text-right flip">{{ t.full_tax_value|money:request.event.currency }}</td>
|
||||
@@ -64,8 +66,8 @@
|
||||
<td>
|
||||
<strong>{% trans "Sum" %}</strong>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right flip">
|
||||
<strong>
|
||||
|
||||
@@ -294,6 +294,71 @@
|
||||
<legend>{% trans "Invoices" %}</legend>
|
||||
{% bootstrap_field sform.invoice_regenerate_allowed layout="control" %}
|
||||
</fieldset>
|
||||
{% if domain_formset %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Domains" %}</legend>
|
||||
<div class="alert alert-warning">
|
||||
{% trans "This dialog is intended for advanced users." %}
|
||||
{% trans "The domain needs to be configured on your webserver before it can be used here." %}
|
||||
</div>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ domain_formset.prefix }}">
|
||||
{{ domain_formset.management_form }}
|
||||
{% bootstrap_formset_errors domain_formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in domain_formset %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.domainname layout='' form_group_class="" %}
|
||||
{% bootstrap_form_errors form %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field form.mode layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field form.event layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<label aria-hidden="true"> </label><br>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ domain_formset.empty_form.id }}
|
||||
{% bootstrap_field domain_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field domain_formset.empty_form.domainname layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field domain_formset.empty_form.mode layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% bootstrap_field domain_formset.empty_form.event layout='' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right flip">
|
||||
<label aria-hidden="true"> </label><br>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add domain" %}</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -38,6 +38,7 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.db.models import (
|
||||
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum,
|
||||
)
|
||||
@@ -47,7 +48,6 @@ from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils import formats
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
@@ -67,6 +67,7 @@ from pretix.control.signals import (
|
||||
from pretix.helpers.daterange import daterange
|
||||
|
||||
from ...base.models.orders import CancellationRequest
|
||||
from ...base.templatetags.money import money_filter
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
|
||||
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
|
||||
@@ -111,7 +112,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
|
||||
return [
|
||||
{
|
||||
'content': None if lazy else NUM_WIDGET.format(num=tickc, text=_('Attendees (ordered)')),
|
||||
'content': None if lazy else NUM_WIDGET.format(num=intcomma(tickc), text=_('Attendees (ordered)')),
|
||||
'lazy': 'attendees-ordered',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
@@ -121,7 +122,7 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
|
||||
},
|
||||
{
|
||||
'content': None if lazy else NUM_WIDGET.format(num=paidc, text=_('Attendees (paid)')),
|
||||
'content': None if lazy else NUM_WIDGET.format(num=intcomma(paidc), text=_('Attendees (paid)')),
|
||||
'lazy': 'attendees-paid',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
@@ -132,7 +133,9 @@ def base_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
},
|
||||
{
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
num=formats.localize(round_decimal(rev, sender.currency)), text=_('Total revenue ({currency})').format(currency=sender.currency)),
|
||||
num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
|
||||
text=_('Total revenue ({currency})').format(currency=sender.currency)
|
||||
),
|
||||
'lazy': 'total-revenue',
|
||||
'display_size': 'small',
|
||||
'priority': 100,
|
||||
@@ -207,7 +210,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
num=str(happy), text=_('available to give to people on waiting list')
|
||||
num=intcomma(happy), text=_('available to give to people on waiting list')
|
||||
),
|
||||
'lazy': 'waitinglist-avail',
|
||||
'priority': 50,
|
||||
@@ -217,7 +220,7 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
})
|
||||
})
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(num=str(wles.count()), text=_('total waiting list length')),
|
||||
'content': None if lazy else NUM_WIDGET.format(num=intcomma(wles.count()), text=_('total waiting list length')),
|
||||
'lazy': 'waitinglist-length',
|
||||
'display_size': 'small',
|
||||
'priority': 50,
|
||||
@@ -245,7 +248,7 @@ def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
|
||||
status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
||||
num='{}/{}'.format(intcomma(left), intcomma(q.size)) if q.size is not None else '\u221e',
|
||||
text=_('{quota} left').format(quota=escape(q.name))
|
||||
),
|
||||
'lazy': 'quota-{}'.format(q.pk),
|
||||
@@ -297,7 +300,7 @@ def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
|
||||
for cl in qs:
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
num='{}/{}'.format(cl.inside_count, cl.position_count),
|
||||
num='{}/{}'.format(intcomma(cl.inside_count), intcomma(cl.position_count)),
|
||||
text=_('Present – {list}').format(list=escape(cl.name))
|
||||
),
|
||||
'lazy': 'checkin-{}'.format(cl.pk),
|
||||
|
||||
@@ -62,7 +62,7 @@ from django.http import (
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
|
||||
@@ -100,9 +100,12 @@ from ...base.models.items import (
|
||||
Item, ItemCategory, ItemMetaProperty, Question, Quota,
|
||||
)
|
||||
from ...base.services.mail import prefix_subject
|
||||
from ...base.services.placeholders import get_sample_context
|
||||
from ...base.settings import LazyI18nStringList
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from ...helpers.format import format_map
|
||||
from ...helpers.format import (
|
||||
PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -239,7 +242,6 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
kwargs = super().get_form_kwargs()
|
||||
if self.request.user.has_active_staff_session(self.request.session.session_key):
|
||||
kwargs['change_slug'] = True
|
||||
kwargs['domain'] = True
|
||||
return kwargs
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -717,20 +719,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||
s = str(p.render_sample(self.request.event))
|
||||
if s.strip().startswith('* '):
|
||||
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
markdown_compile_email(s)
|
||||
)
|
||||
else:
|
||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(s)
|
||||
)
|
||||
return ctx
|
||||
return get_sample_context(self.request.event, MailSettingsForm.base_context[item])
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
preview_item = request.POST.get('item', '')
|
||||
@@ -752,9 +741,15 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
bleach.clean(v), self.placeholders(preview_item), raise_on_missing=True
|
||||
), highlight=True)
|
||||
else:
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
format_map(v, self.placeholders(preview_item), raise_on_missing=True)
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs[self.supported_locale[idx]] = format_map(
|
||||
markdown_compile_email(
|
||||
format_map(v, placeholders, raise_on_missing=True)
|
||||
),
|
||||
placeholders,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
|
||||
PlaceholderValidator.error_message)
|
||||
@@ -777,13 +772,18 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||
ctx[p.identifier] = escape(str(p.render_sample(self.request.event)))
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item], rich=True).values():
|
||||
sample = p.render_sample(self.request.event)
|
||||
if isinstance(sample, PlainHtmlAlternativeString):
|
||||
ctx[p.identifier] = sample
|
||||
else:
|
||||
ctx[p.identifier] = conditional_escape(sample)
|
||||
return ctx
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
v = str(request.event.settings.mail_text_order_placed)
|
||||
v = format_map(v, self.placeholders('mail_text_order_placed'))
|
||||
context = self.placeholders('mail_text_order_placed')
|
||||
v = format_map(v, context)
|
||||
renderers = request.event.get_html_mail_renderers()
|
||||
if request.GET.get('renderer') in renderers:
|
||||
with rolledback_transaction():
|
||||
@@ -801,7 +801,8 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
str(request.event.settings.mail_text_signature),
|
||||
gettext('Your order: %(code)s') % {'code': order.code},
|
||||
order,
|
||||
position=None
|
||||
position=None,
|
||||
context=context,
|
||||
)
|
||||
r = HttpResponse(v, content_type='text/html')
|
||||
r._csp_ignore = True
|
||||
@@ -1196,17 +1197,22 @@ class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = None
|
||||
form = self.get_form()
|
||||
form = self.form
|
||||
if form.is_valid() and self.formset.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
@cached_property
|
||||
def form(self):
|
||||
return self.get_form()
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
return TaxRuleLineFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
event=self.request.event,
|
||||
parent_form=self.form,
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -1247,17 +1253,22 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(self.get_queryset())
|
||||
form = self.get_form()
|
||||
form = self.form
|
||||
if form.is_valid() and self.formset.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
@cached_property
|
||||
def form(self):
|
||||
return self.get_form()
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
return TaxRuleLineFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
event=self.request.event,
|
||||
parent_form=self.form,
|
||||
initial=json.loads(self.object.custom_rules) if self.object.custom_rules else []
|
||||
)
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ from pretix.control.signals import order_search_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
@@ -2241,6 +2241,8 @@ class OrderContactChange(OrderView):
|
||||
changed = True
|
||||
self.order.secret = generate_secret()
|
||||
for op in self.order.all_positions.all():
|
||||
op.web_secret = generate_secret()
|
||||
op.save(update_fields=["web_secret"])
|
||||
assign_ticket_secret(
|
||||
self.request.event, position=op, force_invalidate=True, save=True
|
||||
)
|
||||
@@ -2351,7 +2353,7 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
||||
'subject': mark_safe(_('Subject: {subject}').format(
|
||||
subject=prefix_subject(order.event, escape(email_subject), highlight=True)
|
||||
)),
|
||||
'html': markdown_compile_email(email_content)
|
||||
'html': format_map(markdown_compile_email(email_content), email_context, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
}
|
||||
return self.get(self.request, *self.args, **self.kwargs)
|
||||
else:
|
||||
|
||||
@@ -104,11 +104,11 @@ from pretix.control.forms.organizer import (
|
||||
CustomerCreateForm, CustomerUpdateForm, DeviceBulkEditForm, DeviceForm,
|
||||
EventMetaPropertyAllowedValueFormSet, EventMetaPropertyForm, GateForm,
|
||||
GiftCardAcceptanceInviteForm, GiftCardCreateForm, GiftCardUpdateForm,
|
||||
MailSettingsForm, MembershipTypeForm, MembershipUpdateForm,
|
||||
OrganizerDeleteForm, OrganizerFooterLinkFormset, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, ReusableMediumCreateForm,
|
||||
ReusableMediumUpdateForm, SalesChannelForm, SSOClientForm, SSOProviderForm,
|
||||
TeamForm, WebHookForm,
|
||||
KnownDomainFormset, MailSettingsForm, MembershipTypeForm,
|
||||
MembershipUpdateForm, OrganizerDeleteForm, OrganizerFooterLinkFormset,
|
||||
OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm,
|
||||
ReusableMediumCreateForm, ReusableMediumUpdateForm, SalesChannelForm,
|
||||
SSOClientForm, SSOProviderForm, TeamForm, WebHookForm,
|
||||
)
|
||||
from pretix.control.forms.rrule import RRuleForm
|
||||
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||
@@ -122,7 +122,7 @@ from pretix.control.views.mailsetup import MailSettingsSetupView
|
||||
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 format_map
|
||||
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.presale.forms.customer import TokenGenerator
|
||||
@@ -357,9 +357,10 @@ class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
|
||||
highlight=True,
|
||||
)
|
||||
else:
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
format_map(v, self.placeholders(preview_item))
|
||||
)
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs[self.supported_locale[idx]] = format_map(markdown_compile_email(
|
||||
format_map(v, placeholders)
|
||||
), placeholders, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
|
||||
return JsonResponse({
|
||||
'item': preview_item,
|
||||
@@ -447,6 +448,10 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def get_object(self, queryset=None) -> Organizer:
|
||||
return self.object
|
||||
|
||||
@cached_property
|
||||
def domain_config(self):
|
||||
return self.request.user.has_active_staff_session(self.request.session.session_key)
|
||||
|
||||
@cached_property
|
||||
def sform(self):
|
||||
return OrganizerSettingsForm(
|
||||
@@ -461,6 +466,8 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['footer_links_formset'] = self.footer_links_formset
|
||||
if self.domain_config:
|
||||
context['domain_formset'] = self.domain_formset
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
@@ -483,6 +490,8 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
self.request.organizer.log_action('pretix.organizer.footerlinks.changed', user=self.request.user, data={
|
||||
'data': self.footer_links_formset.cleaned_data
|
||||
})
|
||||
if self.domain_config and self.domain_formset.has_changed():
|
||||
self._save_domain_config()
|
||||
if form.has_changed():
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.changed',
|
||||
@@ -493,10 +502,22 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def _save_domain_config(self):
|
||||
for form in self.domain_formset.initial_forms:
|
||||
if form.instance.pk and form.has_changed():
|
||||
self.object.domains.get(pk=form.instance.pk).log_delete(self.request.user)
|
||||
self.domain_formset.save()
|
||||
for new_obj in self.domain_formset.new_objects:
|
||||
new_obj.log_create(self.request.user)
|
||||
for ch_obj, form in self.domain_formset.changed_objects:
|
||||
ch_obj.log_create(self.request.user)
|
||||
self.request.organizer.cache.clear()
|
||||
for ev in self.request.organizer.events.all():
|
||||
ev.cache.clear()
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
if self.request.user.has_active_staff_session(self.request.session.session_key):
|
||||
kwargs['domain'] = True
|
||||
kwargs['change_slug'] = True
|
||||
return kwargs
|
||||
|
||||
@@ -508,7 +529,7 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid() and self.footer_links_formset.is_valid():
|
||||
if form.is_valid() and self.sform.is_valid() and self.footer_links_formset.is_valid() and (not self.domain_config or self.domain_formset.is_valid()):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
@@ -519,6 +540,11 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
organizer=self.object,
|
||||
prefix="footer-links", instance=self.object)
|
||||
|
||||
@cached_property
|
||||
def domain_formset(self):
|
||||
return KnownDomainFormset(self.request.POST if self.request.method == "POST" else None, prefix="domains",
|
||||
instance=self.object, organizer=self.object)
|
||||
|
||||
def save_footer_links_formset(self, obj):
|
||||
self.footer_links_formset.save()
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from django.http import (
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -59,12 +59,12 @@ from django.views.generic import (
|
||||
)
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.models import (
|
||||
CartPosition, LogEntry, Voucher, WaitingListEntry,
|
||||
)
|
||||
from pretix.base.models.vouchers import generate_codes
|
||||
from pretix.base.services.mail import prefix_subject
|
||||
from pretix.base.services.placeholders import get_sample_context
|
||||
from pretix.base.services.vouchers import vouchers_send
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.base.views.tasks import AsyncFormView
|
||||
@@ -74,7 +74,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import voucher_form_class
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
@@ -549,22 +549,10 @@ class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
base_ctx = ['event', 'name']
|
||||
if item == 'send_message':
|
||||
base_ctx += ['voucher_list']
|
||||
for p in get_available_placeholders(self.request.event, base_ctx).values():
|
||||
s = str(p.render_sample(self.request.event))
|
||||
if s.strip().startswith('* ') or s.startswith(' '):
|
||||
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
markdown_compile_email(s)
|
||||
)
|
||||
else:
|
||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
escape(s)
|
||||
)
|
||||
ctx = get_sample_context(self.request.event, base_ctx)
|
||||
return self.SafeDict(ctx)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -579,9 +567,10 @@ class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
|
||||
highlight=True
|
||||
)
|
||||
else:
|
||||
msgs["all"] = markdown_compile_email(
|
||||
format_map(request.POST.get(preview_item), self.placeholders(preview_item))
|
||||
)
|
||||
placeholders = self.placeholders(preview_item)
|
||||
msgs["all"] = format_map(markdown_compile_email(
|
||||
format_map(request.POST.get(preview_item), placeholders)
|
||||
), placeholders, mode=SafeFormatter.MODE_RICH_TO_HTML)
|
||||
|
||||
return JsonResponse({
|
||||
'item': preview_item,
|
||||
|
||||
@@ -25,14 +25,29 @@ from string import Formatter
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlainHtmlAlternativeString:
|
||||
def __init__(self, plain, html, is_block=False):
|
||||
self.plain = plain
|
||||
self.html = html
|
||||
self.is_block = is_block
|
||||
|
||||
def __repr__(self):
|
||||
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
|
||||
|
||||
|
||||
class SafeFormatter(Formatter):
|
||||
"""
|
||||
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
|
||||
(b) does not allow any unwanted shenanigans like attribute access or format specifiers.
|
||||
"""
|
||||
def __init__(self, context, raise_on_missing=False):
|
||||
MODE_IGNORE_RICH = 0
|
||||
MODE_RICH_TO_PLAIN = 1
|
||||
MODE_RICH_TO_HTML = 2
|
||||
|
||||
def __init__(self, context, raise_on_missing=False, mode=MODE_IGNORE_RICH):
|
||||
self.context = context
|
||||
self.raise_on_missing = raise_on_missing
|
||||
self.mode = mode
|
||||
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
return self.get_value(field_name, args, kwargs), field_name
|
||||
@@ -40,14 +55,22 @@ class SafeFormatter(Formatter):
|
||||
def get_value(self, key, args, kwargs):
|
||||
if not self.raise_on_missing and key not in self.context:
|
||||
return '{' + str(key) + '}'
|
||||
return self.context[key]
|
||||
r = self.context[key]
|
||||
if isinstance(r, PlainHtmlAlternativeString):
|
||||
if self.mode == self.MODE_IGNORE_RICH:
|
||||
return '{' + str(key) + '}'
|
||||
elif self.mode == self.MODE_RICH_TO_PLAIN:
|
||||
return r.plain
|
||||
elif self.mode == self.MODE_RICH_TO_HTML:
|
||||
return r.html
|
||||
return r
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
# Ignore format _spec
|
||||
# Ignore format_spec
|
||||
return super().format_field(value, '')
|
||||
|
||||
|
||||
def format_map(template, context, raise_on_missing=False):
|
||||
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_IGNORE_RICH):
|
||||
if not isinstance(template, str):
|
||||
template = str(template)
|
||||
return SafeFormatter(context, raise_on_missing).format(template)
|
||||
return SafeFormatter(context, raise_on_missing, mode=mode).format(template)
|
||||
|
||||
+2119
-1846
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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"
|
||||
@@ -622,40 +622,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -705,20 +705,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2234
-1854
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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/"
|
||||
@@ -656,40 +656,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr "تباين اللون سيئ للخلفية البيضاء، الرجاء اختيار لون غامق."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "البحث في الاستفسارات"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "الكل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "لا شيء"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "المختارة فقط"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "قم باستخدم اسم مختلف داخليا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "اضغط لاغلاق الصفحة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "لم تقم بحفظ التعديلات!"
|
||||
|
||||
@@ -753,20 +753,20 @@ msgstr "ستسترد %(currency)%(amount)"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "مطلوب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "المنطقة الزمنية:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "التوقيت المحلي:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2207
-1855
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
||||
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -623,40 +623,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -710,22 +710,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Cistella expirada"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2206
-1839
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2023-09-15 06:00+0000\n"
|
||||
"Last-Translator: Michael <michael.happl@gmx.at>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -650,40 +650,40 @@ msgstr ""
|
||||
"Tato barva je pro text na bílém pozadí špatně kontrastní, zvolte prosím "
|
||||
"tmavší odstín."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Hledaný výraz"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Všechny"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Žádný"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Pouze vybrané"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Interně používat jiný název"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Kliknutím zavřete"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Máte neuložené změny!"
|
||||
|
||||
@@ -740,20 +740,20 @@ msgstr "Dostanete %(currency)s %(amount)s zpět"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Zadejte částku, kterou si organizátor může ponechat."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Zadejte prosím množství pro jeden z typů vstupenek."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "povinný"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Časové pásmo:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Místní čas:"
|
||||
|
||||
|
||||
+2134
-1843
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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"
|
||||
@@ -623,40 +623,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -706,20 +706,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2202
-1849
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-07-10 15:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -670,40 +670,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Klik for at lukke"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du har ændringer, der ikke er gemt!"
|
||||
|
||||
@@ -763,22 +763,22 @@ msgstr "fra %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Kurv udløbet"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Tidszone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Din lokaltid:"
|
||||
|
||||
|
||||
+2209
-1868
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -644,40 +644,40 @@ msgstr ""
|
||||
"Diese Farbe hat einen schlechten Kontrast für Text auf einem weißen "
|
||||
"Hintergrund. Bitte wählen Sie eine dunklere Farbe."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr "Ungültige Seitenzahl."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sie haben ungespeicherte Änderungen!"
|
||||
|
||||
@@ -732,20 +732,20 @@ msgstr "Sie erhalten %(currency)s %(amount)s zurück"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Bitte geben Sie den Betrag ein, den der Veranstalter einbehalten darf."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -66,10 +66,12 @@ BSD
|
||||
bspw
|
||||
Bokmål
|
||||
Boleto
|
||||
Buchhaltungs
|
||||
Bundles
|
||||
Butterfly
|
||||
bzw
|
||||
ca
|
||||
Ceuta
|
||||
Cc
|
||||
chardet
|
||||
charge
|
||||
@@ -158,9 +160,12 @@ Inc
|
||||
inkl
|
||||
innenname
|
||||
innennamen
|
||||
innergemeinschaftliche
|
||||
Innergemeinschaftlicher
|
||||
Input
|
||||
Installations
|
||||
integrationen
|
||||
intra
|
||||
INV
|
||||
invalidieren
|
||||
invalidiert
|
||||
@@ -177,10 +182,12 @@ Kombitickets
|
||||
Kompatibilitätsmodus
|
||||
Konfigurations
|
||||
Kosovo
|
||||
land
|
||||
landesspezifische
|
||||
Lead
|
||||
Leaflet
|
||||
Linktext
|
||||
lit
|
||||
Logindaten
|
||||
Lösch
|
||||
loszulegen
|
||||
@@ -188,6 +195,7 @@ Ltd
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
Melilla
|
||||
Mercado
|
||||
Merchandise
|
||||
Meta
|
||||
@@ -269,6 +277,7 @@ rückabgewickelt
|
||||
Rundungsdifferenzen
|
||||
Sa
|
||||
Saalplan
|
||||
Sammlungsstücken
|
||||
SAQ
|
||||
SCA
|
||||
Scan
|
||||
@@ -357,6 +366,7 @@ USt
|
||||
Überzahlten
|
||||
Validierung
|
||||
Venmo
|
||||
Veranstalterdomain
|
||||
Veranstaltereinstellungen
|
||||
Veranstalterkonten
|
||||
Veranstalterkonto
|
||||
@@ -367,6 +377,7 @@ Veranstalterseite
|
||||
Veranstalterübersicht
|
||||
veranstalterweiten
|
||||
Veranstaltungs
|
||||
Veranstalterdomain
|
||||
veranstaltungsweiten
|
||||
Verfügbarkeitsberechnung
|
||||
Verfügbarkeitsstatus
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
@@ -643,40 +643,40 @@ msgstr ""
|
||||
"Diese Farbe hat einen schlechten Kontrast für Text auf einem weißen "
|
||||
"Hintergrund. Bitte wähle eine dunklere Farbe."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Gib eine Seitenzahl zwischen 1 und %(max)s ein."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr "Ungültige Seitenzahl."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du hast ungespeicherte Änderungen!"
|
||||
|
||||
@@ -731,20 +731,20 @@ msgstr "Du erhältst %(currency)s %(amount)s zurück"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Bitte gib den Betrag ein, den der Veranstalter einbehalten darf."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Bitte trage eine Menge für eines der Produkte ein."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -66,10 +66,12 @@ BSD
|
||||
bspw
|
||||
Bokmål
|
||||
Boleto
|
||||
Buchhaltungs
|
||||
Bundles
|
||||
Butterfly
|
||||
bzw
|
||||
ca
|
||||
Ceuta
|
||||
Cc
|
||||
chardet
|
||||
charge
|
||||
@@ -158,9 +160,12 @@ Inc
|
||||
inkl
|
||||
innenname
|
||||
innennamen
|
||||
innergemeinschaftliche
|
||||
Innergemeinschaftlicher
|
||||
Input
|
||||
Installations
|
||||
integrationen
|
||||
intra
|
||||
INV
|
||||
invalidieren
|
||||
invalidiert
|
||||
@@ -177,10 +182,12 @@ Kombitickets
|
||||
Kompatibilitätsmodus
|
||||
Konfigurations
|
||||
Kosovo
|
||||
land
|
||||
landesspezifische
|
||||
Lead
|
||||
Leaflet
|
||||
Linktext
|
||||
lit
|
||||
Logindaten
|
||||
Lösch
|
||||
loszulegen
|
||||
@@ -188,6 +195,7 @@ Ltd
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
Melilla
|
||||
Mercado
|
||||
Merchandise
|
||||
Meta
|
||||
@@ -269,6 +277,7 @@ rückabgewickelt
|
||||
Rundungsdifferenzen
|
||||
Sa
|
||||
Saalplan
|
||||
Sammlungsstücken
|
||||
SAQ
|
||||
SCA
|
||||
Scan
|
||||
@@ -357,6 +366,7 @@ USt
|
||||
Überzahlten
|
||||
Validierung
|
||||
Venmo
|
||||
Veranstalterdomain
|
||||
Veranstaltereinstellungen
|
||||
Veranstalterkonten
|
||||
Veranstalterkonto
|
||||
@@ -367,6 +377,7 @@ Veranstalterseite
|
||||
Veranstalterübersicht
|
||||
veranstalterweiten
|
||||
Veranstaltungs
|
||||
Veranstalterdomain
|
||||
veranstaltungsweiten
|
||||
Verfügbarkeitsberechnung
|
||||
Verfügbarkeitsstatus
|
||||
|
||||
+2119
-1846
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: 2024-11-19 15:34+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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"
|
||||
@@ -622,40 +622,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -705,20 +705,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2251
-1878
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
|
||||
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-12-22 00:00+0000\n"
|
||||
"Last-Translator: Dimitris Tsimpidis <tsimpidisd@gmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"el/>\n"
|
||||
"Language: el\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 3.5.1\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -31,7 +31,7 @@ msgstr "Σχόλιο:"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
msgstr ""
|
||||
msgstr "PayPal"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
|
||||
msgid "Venmo"
|
||||
@@ -64,7 +64,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
msgstr "Τραπεζική μεταφορά"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
||||
msgid "Bancontact"
|
||||
@@ -79,10 +79,8 @@ msgid "SOFORT"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:46
|
||||
#, fuzzy
|
||||
#| msgid "Yes"
|
||||
msgid "eps"
|
||||
msgstr "Ναι"
|
||||
msgstr "EPS"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:47
|
||||
msgid "MyBank"
|
||||
@@ -147,11 +145,11 @@ msgstr "Συνέχεια"
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:317
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:341
|
||||
msgid "Confirming your payment …"
|
||||
msgstr ""
|
||||
msgstr "Επιβεβαίωση πληρωμής…"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:254
|
||||
msgid "Payment method unavailable"
|
||||
msgstr ""
|
||||
msgstr "Μη διαθέσιμος τρόπος πληρωμής"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
@@ -176,10 +174,8 @@ msgid "Total"
|
||||
msgstr "Σύνολο"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:291
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Επικοινωνία με το Stripe …"
|
||||
msgstr "Επικοινωνία με την τράπεζα …"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
@@ -686,40 +682,40 @@ msgstr ""
|
||||
"Το χρώμα σας έχει κακή αντίθεση για κείμενο σε λευκό φόντο, επιλέξτε μια πιο "
|
||||
"σκούρα σκιά."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Όλα"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Κανένας"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Χρησιμοποιήστε διαφορετικό όνομα εσωτερικά"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Κάντε κλικ για να κλείσετε"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -781,22 +777,22 @@ msgstr "απο %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Το καλάθι έληξε"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2119
-1846
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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"
|
||||
@@ -622,40 +622,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -705,20 +705,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2251
-1908
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-11-18 15:48+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/es/>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/es/>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -642,40 +642,40 @@ msgstr ""
|
||||
"Tu color tiene mal contraste para un texto con fondo blanco, por favor, "
|
||||
"escoge un tono más oscuro."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Consulta de búsqueda"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Ninguno"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Solamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Introduce un número de página entre 1 y %(max)s."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr "Número de página inválido."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nombre diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "¡Tienes cambios sin guardar!"
|
||||
|
||||
@@ -730,20 +730,20 @@ msgstr "Obtienes %(currency)s %(price)s de vuelta"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Por favor, ingrese el importe que el organizador puede quedarse."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Por favor, introduzca un valor para cada tipo de entrada."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "campo requerido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Su hora local:"
|
||||
|
||||
|
||||
+2119
-1846
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+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"
|
||||
@@ -622,40 +622,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -705,20 +705,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2212
-1840
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-09-06 08:47+0000\n"
|
||||
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -635,40 +635,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Guztiak"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -721,20 +721,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2856
-2484
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -651,40 +651,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Kaikki"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Käytä toista nimeä sisäisesti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Sulje klikkaamalla"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sinulla on tallentamattomia muutoksia!"
|
||||
|
||||
@@ -738,22 +738,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Ostoskori on vanhentunut"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Aikavyöhyke:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2214
-1872
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||
"PO-Revision-Date: 2024-11-16 05:00+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2024-12-03 20:00+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"fr/>\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -150,7 +150,7 @@ msgstr "Méthode de paiement non disponible"
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr "Commandes placées"
|
||||
msgstr "Commandes réalisées"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
@@ -643,40 +643,40 @@ msgstr ""
|
||||
"Votre choix de couleur n'a pas un bon contraste avec du texte sur un fond "
|
||||
"blanc, SVP choisissez un ton plus sombre."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Requête de recherche"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Tous"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Seuls les sélectionnés"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Saisir le numéro de page entre 1 et %(max)s."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr "Numéro de page invalide."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Utiliser un nom différent en interne"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Cliquez pour fermer"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Vous avez des modifications non sauvegardées !"
|
||||
|
||||
@@ -729,20 +729,20 @@ msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
"Veuillez indiquer le montant que l'organisateur est autorisé à retenir."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "SVP entrez une quantité pour un de vos types de billets."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "obligatoire"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Fuseau horaire :"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "Votre heure locale :"
|
||||
|
||||
|
||||
+2221
-1876
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2022-02-22 22:00+0000\n"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -658,40 +658,40 @@ msgstr ""
|
||||
"A túa cor ten mal contraste para un texto con fondo branco. Por favor, "
|
||||
"escolle un ton máis escuro."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr "Consultar unha procura"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr "Ningún"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr "Soamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nome diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Tes cambios sen gardar!"
|
||||
|
||||
@@ -744,20 +744,20 @@ msgstr "Obtés %(currency)s %(price)s de volta"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Por favor, ingrese a cantidade que pode conservar o organizador."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Por favor, introduza un valor para cada tipo de entrada."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr "campo requirido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr "A súa hora local:"
|
||||
|
||||
|
||||
+2123
-1846
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: 2024-11-08 13:45+0000\n"
|
||||
"POT-Creation-Date: 2024-12-16 14:20+0000\n"
|
||||
"PO-Revision-Date: 2021-09-24 13:54+0000\n"
|
||||
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
||||
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:442
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:460
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:465
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:808
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:969
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1009
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1084
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -717,20 +717,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:449
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:454
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:485
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:506
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:588
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:607
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:550
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:569
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:598
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:560
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
+2121
-1846
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