forked from CGM_Public/pretix_original
Compare commits
113
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff815056b | ||
|
|
45391b104a | ||
|
|
54d3d20d70 | ||
|
|
be1cb12286 | ||
|
|
c65b7f711d | ||
|
|
8e3d5511e2 | ||
|
|
9acfcf2526 | ||
|
|
32fd9f2f4d | ||
|
|
47ec03baac | ||
|
|
04884ce874 | ||
|
|
3337783d1f | ||
|
|
391bbc25f8 | ||
|
|
68017cc8cb | ||
|
|
5fee16035f | ||
|
|
6d32aa5220 | ||
|
|
3461c59cfe | ||
|
|
21b4dacee9 | ||
|
|
2663e9ff32 | ||
|
|
a601c75923 | ||
|
|
f94227f00f | ||
|
|
a0c1e5369c | ||
|
|
633bfcf73a | ||
|
|
0d3b5b82c1 | ||
|
|
ab95f33546 | ||
|
|
5034b366c5 | ||
|
|
03d3c389da | ||
|
|
3e934acfa0 | ||
|
|
d2a364e848 | ||
|
|
2824b40299 | ||
|
|
c6c2c90908 | ||
|
|
d4ae7df2ec | ||
|
|
79dd7fb596 | ||
|
|
5ed87cd019 | ||
|
|
ccdcbe0cc5 | ||
|
|
4f8607a9db | ||
|
|
57ecaa2676 | ||
|
|
96fd2b1a95 | ||
|
|
5cf24fb6a6 | ||
|
|
1d2ea35a39 | ||
|
|
ac98ae7941 | ||
|
|
a0d055e202 | ||
|
|
27ec5ca006 | ||
|
|
9d2edc405d | ||
|
|
fb95fe7cf6 | ||
|
|
5b5360ef8b | ||
|
|
129d10ca35 | ||
|
|
093a705ff9 | ||
|
|
6130ae4630 | ||
|
|
11a8ed6c7a | ||
|
|
f6392592c5 | ||
|
|
ecb9ad28ea | ||
|
|
45a506fd37 | ||
|
|
3b16e6356b | ||
|
|
9583a50c4e | ||
|
|
6e6d6b2746 | ||
|
|
7266d90c6b | ||
|
|
5e4e88c91d | ||
|
|
e74d12e8b8 | ||
|
|
a5c39271dd | ||
|
|
3170744c56 | ||
|
|
9ec161561b | ||
|
|
aff4f4b8f8 | ||
|
|
75addfe9f4 | ||
|
|
4b05ce5835 | ||
|
|
34c247f423 | ||
|
|
3aad6852cb | ||
|
|
5cdb07bce6 | ||
|
|
6cb2d68948 | ||
|
|
4a7a6273c6 | ||
|
|
ebe343458a | ||
|
|
f9a93b765c | ||
|
|
5aba1f9a23 | ||
|
|
a4eed87396 | ||
|
|
08879d0d55 | ||
|
|
c276a19bcc | ||
|
|
1e3c6e0b68 | ||
|
|
4e283eb560 | ||
|
|
52a1983630 | ||
|
|
3d85d9d865 | ||
|
|
4ca9a43890 | ||
|
|
d8bac7db65 | ||
|
|
91de0f93e6 | ||
|
|
901565203b | ||
|
|
14c6c9c0d7 | ||
|
|
6de6cf6c08 | ||
|
|
29306b3a4d | ||
|
|
ca69996611 | ||
|
|
16419b6ae4 | ||
|
|
d6258b9b54 | ||
|
|
6f75608196 | ||
|
|
6ef88e009b | ||
|
|
957100a195 | ||
|
|
112ef0908f | ||
|
|
91aaff7359 | ||
|
|
8ab61e2c38 | ||
|
|
c8ba5cc427 | ||
|
|
5ebad31b7d | ||
|
|
0429377f7d | ||
|
|
76e4b797a1 | ||
|
|
5f0009c996 | ||
|
|
de63a4be01 | ||
|
|
f3432139cb | ||
|
|
0b82ac9115 | ||
|
|
eb685b5141 | ||
|
|
5f7f0bd8f1 | ||
|
|
9fcef2dcaa | ||
|
|
fc3b186b93 | ||
|
|
a406884575 | ||
|
|
57ccd5f289 | ||
|
|
f4ac7e7f65 | ||
|
|
81d7045b31 | ||
|
|
f9502a3212 | ||
|
|
a31f624417 |
@@ -31,8 +31,6 @@ subevent integer ID of the date
|
|||||||
position_count integer Number of tickets that match this list (read-only).
|
position_count integer Number of tickets that match this list (read-only).
|
||||||
checkin_count integer Number of check-ins performed on this list (read-only).
|
checkin_count integer Number of check-ins performed on this list (read-only).
|
||||||
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
|
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
|
||||||
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
|
|
||||||
**Deprecated, will be removed in pretix 2024.10.** Use :ref:`rest-autocheckinrules`: instead.
|
|
||||||
allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in.
|
allow_multiple_entries boolean If ``true``, subsequent scans of a ticket on this list should not show a warning but instead be stored as an additional check-in.
|
||||||
allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan.
|
allow_entry_after_exit boolean If ``true``, subsequent scans of a ticket on this list are valid if the last scan of the ticket was an exit scan.
|
||||||
rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged.
|
rules object Custom check-in logic. The contents of this field are currently not considered a stable API and modifications through the API are highly discouraged.
|
||||||
@@ -91,10 +89,7 @@ Endpoints
|
|||||||
"allow_entry_after_exit": true,
|
"allow_entry_after_exit": true,
|
||||||
"exit_all_at": null,
|
"exit_all_at": null,
|
||||||
"rules": {},
|
"rules": {},
|
||||||
"addon_match": false,
|
"addon_match": false
|
||||||
"auto_checkin_sales_channels": [
|
|
||||||
"pretixpos"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -146,10 +141,7 @@ Endpoints
|
|||||||
"allow_entry_after_exit": true,
|
"allow_entry_after_exit": true,
|
||||||
"exit_all_at": null,
|
"exit_all_at": null,
|
||||||
"rules": {},
|
"rules": {},
|
||||||
"addon_match": false,
|
"addon_match": false
|
||||||
"auto_checkin_sales_channels": [
|
|
||||||
"pretixpos"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
@@ -246,10 +238,7 @@ Endpoints
|
|||||||
"subevent": null,
|
"subevent": null,
|
||||||
"allow_multiple_entries": false,
|
"allow_multiple_entries": false,
|
||||||
"allow_entry_after_exit": true,
|
"allow_entry_after_exit": true,
|
||||||
"addon_match": false,
|
"addon_match": false
|
||||||
"auto_checkin_sales_channels": [
|
|
||||||
"pretixpos"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
@@ -271,10 +260,7 @@ Endpoints
|
|||||||
"subevent": null,
|
"subevent": null,
|
||||||
"allow_multiple_entries": false,
|
"allow_multiple_entries": false,
|
||||||
"allow_entry_after_exit": true,
|
"allow_entry_after_exit": true,
|
||||||
"addon_match": false,
|
"addon_match": false
|
||||||
"auto_checkin_sales_channels": [
|
|
||||||
"pretixpos"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
|
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
|
||||||
@@ -326,10 +312,7 @@ Endpoints
|
|||||||
"subevent": null,
|
"subevent": null,
|
||||||
"allow_multiple_entries": false,
|
"allow_multiple_entries": false,
|
||||||
"allow_entry_after_exit": true,
|
"allow_entry_after_exit": true,
|
||||||
"addon_match": false,
|
"addon_match": false
|
||||||
"auto_checkin_sales_channels": [
|
|
||||||
"pretixpos"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to modify
|
:param organizer: The ``slug`` field of the organizer to modify
|
||||||
@@ -342,7 +325,7 @@ Endpoints
|
|||||||
|
|
||||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
|
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/checkinlist/(id)/
|
||||||
|
|
||||||
Delete a check-in list. Note that this also deletes the information on all check-ins performed via this list.
|
Delete a check-in list. **Note that this also deletes the information on all check-ins performed via this list.**
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
|
|||||||
@@ -352,12 +352,12 @@ Fetching individual invoices
|
|||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
:param event: The ``slug`` field of the event to fetch
|
:param event: The ``slug`` field of the event to fetch
|
||||||
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
|
:param number: The ``number`` field of the invoice to fetch
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/download/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/download/
|
||||||
|
|
||||||
Download an invoice in PDF format.
|
Download an invoice in PDF format.
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ Fetching individual invoices
|
|||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
:param event: The ``slug`` field of the event to fetch
|
:param event: The ``slug`` field of the event to fetch
|
||||||
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
|
:param number: The ``number`` field of the invoice to fetch
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||||
@@ -397,7 +397,7 @@ Modifying invoices
|
|||||||
|
|
||||||
Invoices cannot be edited directly, but the following actions can be triggered:
|
Invoices cannot be edited directly, but the following actions can be triggered:
|
||||||
|
|
||||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/reissue/
|
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/reissue/
|
||||||
|
|
||||||
Cancels the invoice and creates a new one.
|
Cancels the invoice and creates a new one.
|
||||||
|
|
||||||
@@ -419,13 +419,13 @@ Invoices cannot be edited directly, but the following actions can be triggered:
|
|||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
:param event: The ``slug`` field of the event to fetch
|
:param event: The ``slug`` field of the event to fetch
|
||||||
:param invoice_no: The ``invoice_no`` field of the invoice to reissue
|
:param number: The ``number`` field of the invoice to reissue
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 400: The invoice has already been canceled
|
:statuscode 400: The invoice has already been canceled
|
||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
||||||
|
|
||||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/regenerate/
|
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/regenerate/
|
||||||
|
|
||||||
Re-generates the invoice from order data.
|
Re-generates the invoice from order data.
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ Invoices cannot be edited directly, but the following actions can be triggered:
|
|||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
:param event: The ``slug`` field of the event to fetch
|
:param event: The ``slug`` field of the event to fetch
|
||||||
:param invoice_no: The ``invoice_no`` field of the invoice to regenerate
|
:param number: The ``number`` field of the invoice to regenerate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 400: The invoice has already been canceled
|
:statuscode 400: The invoice has already been canceled
|
||||||
:statuscode 401: Authentication failure
|
:statuscode 401: Authentication failure
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ url string The full URL to
|
|||||||
payments list of objects List of payment processes (see below)
|
payments list of objects List of payment processes (see below)
|
||||||
refunds list of objects List of refund processes (see below)
|
refunds list of objects List of refund processes (see below)
|
||||||
last_modified datetime Last modification of this object
|
last_modified datetime Last modification of this object
|
||||||
|
cancellation_date datetime Time of order cancellation (or ``null``). **Note**:
|
||||||
|
Will not be set for partial cancellations and is not
|
||||||
|
reliable for orders that have been cancelled,
|
||||||
|
reactivated and cancelled again.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
@@ -151,6 +155,9 @@ last_modified datetime Last modificati
|
|||||||
|
|
||||||
The ``expires`` attribute can now be passed during order creation.
|
The ``expires`` attribute can now be passed during order creation.
|
||||||
|
|
||||||
|
.. versionchanged:: 2024.11
|
||||||
|
|
||||||
|
The ``cancellation_date`` attribute has been added and can also be used as an ordering key.
|
||||||
|
|
||||||
.. _order-position-resource:
|
.. _order-position-resource:
|
||||||
|
|
||||||
@@ -464,14 +471,15 @@ List of all orders
|
|||||||
"provider": "banktransfer"
|
"provider": "banktransfer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refunds": []
|
"refunds": [],
|
||||||
|
"cancellation_date": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code``,
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code``,
|
||||||
``last_modified``, and ``status``. Default: ``datetime``
|
``last_modified``, ``status`` and ``cancellation_date``. Default: ``datetime``
|
||||||
:query string code: Only return orders that match the given order code
|
:query string code: Only return orders that match the given order code
|
||||||
:query string status: Only return orders in the given order status (see above)
|
:query string status: Only return orders in the given order status (see above)
|
||||||
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
|
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
|
||||||
@@ -703,7 +711,8 @@ Fetching individual orders
|
|||||||
"provider": "banktransfer"
|
"provider": "banktransfer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refunds": []
|
"refunds": [],
|
||||||
|
"cancellation_date": null
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
|||||||
@@ -100,3 +100,7 @@ API
|
|||||||
.. automodule:: pretix.base.signals
|
.. automodule:: pretix.base.signals
|
||||||
:no-index:
|
:no-index:
|
||||||
:members: validate_event_settings, api_event_settings_fields
|
:members: validate_event_settings, api_event_settings_fields
|
||||||
|
|
||||||
|
.. automodule:: pretix.api.signals
|
||||||
|
:no-index:
|
||||||
|
:members: register_device_security_profile
|
||||||
|
|||||||
+3
-4
@@ -29,7 +29,7 @@ dependencies = [
|
|||||||
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
|
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
|
||||||
"babel",
|
"babel",
|
||||||
"BeautifulSoup4==4.12.*",
|
"BeautifulSoup4==4.12.*",
|
||||||
"bleach==5.0.*",
|
"bleach==6.2.*",
|
||||||
"celery==5.4.*",
|
"celery==5.4.*",
|
||||||
"chardet==5.2.*",
|
"chardet==5.2.*",
|
||||||
"cryptography>=3.4.2",
|
"cryptography>=3.4.2",
|
||||||
@@ -91,9 +91,8 @@ dependencies = [
|
|||||||
"redis==5.2.*",
|
"redis==5.2.*",
|
||||||
"reportlab==4.2.*",
|
"reportlab==4.2.*",
|
||||||
"requests==2.31.*",
|
"requests==2.31.*",
|
||||||
"sentry-sdk==2.17.*",
|
"sentry-sdk==2.18.*",
|
||||||
"sepaxml==2.6.*",
|
"sepaxml==2.6.*",
|
||||||
"slimit",
|
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
"tlds>=2020041600",
|
"tlds>=2020041600",
|
||||||
@@ -117,7 +116,7 @@ dev = [
|
|||||||
"isort==5.13.*",
|
"isort==5.13.*",
|
||||||
"pep8-naming==0.14.*",
|
"pep8-naming==0.14.*",
|
||||||
"potypo",
|
"potypo",
|
||||||
"pytest-asyncio",
|
"pytest-asyncio>=0.24",
|
||||||
"pytest-cache",
|
"pytest-cache",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"pytest-django==4.*",
|
"pytest-django==4.*",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from rest_framework import exceptions
|
|||||||
from rest_framework.authentication import TokenAuthentication
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
|
||||||
from pretix.api.auth.devicesecurity import (
|
from pretix.api.auth.devicesecurity import (
|
||||||
DEVICE_SECURITY_PROFILES, FullAccessSecurityProfile,
|
FullAccessSecurityProfile, get_all_security_profiles,
|
||||||
)
|
)
|
||||||
from pretix.base.models import Device
|
from pretix.base.models import Device
|
||||||
|
|
||||||
@@ -58,7 +58,8 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
|||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
r = super().authenticate(request)
|
r = super().authenticate(request)
|
||||||
if r and isinstance(r[1], Device):
|
if r and isinstance(r[1], Device):
|
||||||
profile = DEVICE_SECURITY_PROFILES.get(r[1].security_profile, FullAccessSecurityProfile)
|
profiles = get_all_security_profiles()
|
||||||
|
profile = profiles.get(r[1].security_profile, FullAccessSecurityProfile())
|
||||||
if not profile.is_allowed(request):
|
if not profile.is_allowed(request):
|
||||||
raise exceptions.PermissionDenied('Request denied by device security profile.')
|
raise exceptions.PermissionDenied('Request denied by device security profile.')
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -20,13 +20,40 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from pretix.api.signals import register_device_security_profile
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
_ALL_PROFILES = None
|
||||||
|
|
||||||
|
|
||||||
class FullAccessSecurityProfile:
|
class BaseSecurityProfile:
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
"""
|
||||||
|
Unique identifier for this profile.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def verbose_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Human-readable name (can be a ``gettext_lazy`` object).
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def is_allowed(self, request) -> bool:
|
||||||
|
"""
|
||||||
|
Return whether a given request should be allowed.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class FullAccessSecurityProfile(BaseSecurityProfile):
|
||||||
identifier = 'full'
|
identifier = 'full'
|
||||||
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
|
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
|
||||||
|
|
||||||
@@ -34,7 +61,7 @@ class FullAccessSecurityProfile:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class AllowListSecurityProfile:
|
class AllowListSecurityProfile(BaseSecurityProfile):
|
||||||
allowlist = ()
|
allowlist = ()
|
||||||
|
|
||||||
def is_allowed(self, request):
|
def is_allowed(self, request):
|
||||||
@@ -157,88 +184,28 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PretixPosSecurityProfile(AllowListSecurityProfile):
|
def get_all_security_profiles():
|
||||||
identifier = 'pretixpos'
|
global _ALL_PROFILES
|
||||||
verbose_name = _('pretixPOS')
|
|
||||||
allowlist = (
|
if _ALL_PROFILES:
|
||||||
('GET', 'api-v1:version'),
|
return _ALL_PROFILES
|
||||||
('GET', 'api-v1:device.eventselection'),
|
|
||||||
('GET', 'api-v1:idempotency.query'),
|
types = OrderedDict()
|
||||||
('GET', 'api-v1:device.info'),
|
for recv, ret in register_device_security_profile.send(None):
|
||||||
('POST', 'api-v1:device.update'),
|
if isinstance(ret, (list, tuple)):
|
||||||
('POST', 'api-v1:device.revoke'),
|
for r in ret:
|
||||||
('POST', 'api-v1:device.roll'),
|
types[r.identifier] = r
|
||||||
('GET', 'api-v1:event-list'),
|
else:
|
||||||
('GET', 'api-v1:event-detail'),
|
types[ret.identifier] = ret
|
||||||
('GET', 'api-v1:subevent-list'),
|
_ALL_PROFILES = types
|
||||||
('GET', 'api-v1:subevent-detail'),
|
return types
|
||||||
('GET', 'api-v1:itemcategory-list'),
|
|
||||||
('GET', 'api-v1:item-list'),
|
|
||||||
('GET', 'api-v1:question-list'),
|
|
||||||
('GET', 'api-v1:quota-list'),
|
|
||||||
('GET', 'api-v1:taxrule-list'),
|
|
||||||
('GET', 'api-v1:ticketlayout-list'),
|
|
||||||
('GET', 'api-v1:ticketlayoutitem-list'),
|
|
||||||
('GET', 'api-v1:badgelayout-list'),
|
|
||||||
('GET', 'api-v1:badgeitem-list'),
|
|
||||||
('GET', 'api-v1:voucher-list'),
|
|
||||||
('GET', 'api-v1:voucher-detail'),
|
|
||||||
('GET', 'api-v1:order-list'),
|
|
||||||
('POST', 'api-v1:order-list'),
|
|
||||||
('GET', 'api-v1:order-detail'),
|
|
||||||
('DELETE', 'api-v1:orderposition-detail'),
|
|
||||||
('PATCH', 'api-v1:orderposition-detail'),
|
|
||||||
('GET', 'api-v1:orderposition-list'),
|
|
||||||
('GET', 'api-v1:orderposition-answer'),
|
|
||||||
('GET', 'api-v1:orderposition-pdf_image'),
|
|
||||||
('POST', 'api-v1:orderposition-printlog'),
|
|
||||||
('POST', 'api-v1:order-mark-canceled'),
|
|
||||||
('POST', 'api-v1:orderpayment-list'),
|
|
||||||
('POST', 'api-v1:orderrefund-list'),
|
|
||||||
('POST', 'api-v1:orderrefund-done'),
|
|
||||||
('POST', 'api-v1:cartposition-list'),
|
|
||||||
('POST', 'api-v1:cartposition-bulk-create'),
|
|
||||||
('GET', 'api-v1:checkinlist-list'),
|
|
||||||
('POST', 'api-v1:checkinlistpos-redeem'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:order.posprintlog'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:order.poslock'),
|
|
||||||
('DELETE', 'plugins:pretix_posbackend:order.poslock'),
|
|
||||||
('DELETE', 'api-v1:cartposition-detail'),
|
|
||||||
('GET', 'api-v1:giftcard-list'),
|
|
||||||
('POST', 'api-v1:giftcard-transact'),
|
|
||||||
('PATCH', 'api-v1:giftcard-detail'),
|
|
||||||
('GET', 'plugins:pretix_posbackend:posclosing-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:posreceipt-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:posclosing-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:posdebugdump-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:posdebuglogentry-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:posdebuglogentry-bulk-create'),
|
|
||||||
('GET', 'plugins:pretix_posbackend:poscashier-list'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:stripeterminal.token'),
|
|
||||||
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
|
|
||||||
('PUT', 'plugins:pretix_posbackend:file.upload'),
|
|
||||||
('GET', 'api-v1:revokedsecrets-list'),
|
|
||||||
('GET', 'api-v1:blockedsecrets-list'),
|
|
||||||
('GET', 'api-v1:event.settings'),
|
|
||||||
('GET', 'plugins:pretix_seating:event.event'),
|
|
||||||
('GET', 'plugins:pretix_seating:event.event.subevent'),
|
|
||||||
('GET', 'plugins:pretix_seating:event.plan'),
|
|
||||||
('GET', 'plugins:pretix_seating:selection.simple'),
|
|
||||||
('POST', 'api-v1:upload'),
|
|
||||||
('POST', 'api-v1:checkinrpc.redeem'),
|
|
||||||
('GET', 'api-v1:checkinrpc.search'),
|
|
||||||
('POST', 'api-v1:reusablemedium-lookup'),
|
|
||||||
('GET', 'api-v1:reusablemedium-list'),
|
|
||||||
('POST', 'api-v1:reusablemedium-list'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DEVICE_SECURITY_PROFILES = {
|
@receiver(register_device_security_profile, dispatch_uid="base_register_default_security_profiles")
|
||||||
k.identifier: k() for k in (
|
def register_default_webhook_events(sender, **kwargs):
|
||||||
FullAccessSecurityProfile,
|
return (
|
||||||
PretixScanSecurityProfile,
|
FullAccessSecurityProfile(),
|
||||||
PretixScanNoSyncSecurityProfile,
|
PretixScanSecurityProfile(),
|
||||||
PretixScanNoSyncNoSearchSecurityProfile,
|
PretixScanNoSyncSecurityProfile(),
|
||||||
PretixPosSecurityProfile,
|
PretixScanNoSyncNoSearchSecurityProfile(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class CartPositionCreateSerializer(BaseCartPositionCreateSerializer):
|
|||||||
return cid
|
return cid
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data.pop('sales_channel')
|
validated_data.pop('sales_channel', None)
|
||||||
addons_data = validated_data.pop('addons', None)
|
addons_data = validated_data.pop('addons', None)
|
||||||
bundled_data = validated_data.pop('bundled', None)
|
bundled_data = validated_data.pop('bundled', None)
|
||||||
|
|
||||||
|
|||||||
@@ -26,31 +26,22 @@ from rest_framework.exceptions import ValidationError
|
|||||||
from pretix.api.serializers.event import SubEventSerializer
|
from pretix.api.serializers.event import SubEventSerializer
|
||||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
from pretix.base.media import MEDIA_TYPES
|
from pretix.base.media import MEDIA_TYPES
|
||||||
from pretix.base.models import Checkin, CheckinList, SalesChannel
|
from pretix.base.models import Checkin, CheckinList
|
||||||
|
|
||||||
|
|
||||||
class CheckinListSerializer(I18nAwareModelSerializer):
|
class CheckinListSerializer(I18nAwareModelSerializer):
|
||||||
checkin_count = serializers.IntegerField(read_only=True)
|
checkin_count = serializers.IntegerField(read_only=True)
|
||||||
position_count = serializers.IntegerField(read_only=True)
|
position_count = serializers.IntegerField(read_only=True)
|
||||||
auto_checkin_sales_channels = serializers.SlugRelatedField(
|
|
||||||
slug_field="identifier",
|
|
||||||
queryset=SalesChannel.objects.none(),
|
|
||||||
required=False,
|
|
||||||
allow_empty=True,
|
|
||||||
many=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CheckinList
|
model = CheckinList
|
||||||
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
|
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
|
||||||
'include_pending', 'auto_checkin_sales_channels', 'allow_multiple_entries', 'allow_entry_after_exit',
|
'include_pending', 'allow_multiple_entries', 'allow_entry_after_exit',
|
||||||
'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used')
|
'rules', 'exit_all_at', 'addon_match', 'ignore_in_statistics', 'consider_tickets_used')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['auto_checkin_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
|
|
||||||
|
|
||||||
if 'subevent' in self.context['request'].query_params.getlist('expand'):
|
if 'subevent' in self.context['request'].query_params.getlist('expand'):
|
||||||
self.fields['subevent'] = SubEventSerializer(read_only=True)
|
self.fields['subevent'] = SubEventSerializer(read_only=True)
|
||||||
|
|
||||||
|
|||||||
@@ -753,12 +753,12 @@ class OrderSerializer(I18nAwareModelSerializer):
|
|||||||
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||||
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||||
'url', 'customer', 'valid_if_pending', 'api_meta'
|
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date'
|
||||||
)
|
)
|
||||||
read_only_fields = (
|
read_only_fields = (
|
||||||
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
||||||
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
|
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
|
||||||
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel'
|
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from pretix.api.auth.devicesecurity import get_all_security_profiles
|
||||||
from pretix.api.serializers import AsymmetricField
|
from pretix.api.serializers import AsymmetricField
|
||||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
from pretix.api.serializers.order import CompatibleJSONField
|
from pretix.api.serializers.order import CompatibleJSONField
|
||||||
@@ -297,6 +298,7 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
revoked = serializers.BooleanField(read_only=True)
|
revoked = serializers.BooleanField(read_only=True)
|
||||||
initialized = serializers.DateTimeField(read_only=True)
|
initialized = serializers.DateTimeField(read_only=True)
|
||||||
initialization_token = serializers.DateTimeField(read_only=True)
|
initialization_token = serializers.DateTimeField(read_only=True)
|
||||||
|
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
@@ -306,6 +308,10 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
|
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
|
||||||
|
|
||||||
|
|
||||||
class TeamInviteSerializer(serializers.ModelSerializer):
|
class TeamInviteSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -32,10 +32,17 @@ from pretix.helpers.periodic import minimum_interval
|
|||||||
register_webhook_events = Signal()
|
register_webhook_events = Signal()
|
||||||
"""
|
"""
|
||||||
This signal is sent out to get all known webhook events. Receivers should return an
|
This signal is sent out to get all known webhook events. Receivers should return an
|
||||||
instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such
|
instance of a subclass of ``pretix.api.webhooks.WebhookEvent`` or a list of such
|
||||||
instances.
|
instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
register_device_security_profile = Signal()
|
||||||
|
"""
|
||||||
|
This signal is sent out to get all known device security_profiles. Receivers should
|
||||||
|
return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurityProfile``
|
||||||
|
or a list of such instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@receiver(periodic_task)
|
@receiver(periodic_task)
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
if 'subevent' in self.request.query_params.getlist('expand'):
|
if 'subevent' in self.request.query_params.getlist('expand'):
|
||||||
qs = qs.prefetch_related(
|
qs = qs.prefetch_related(
|
||||||
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
|
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
|
||||||
'subevent__seat_category_mappings', 'subevent__meta_values', 'auto_checkin_sales_channels'
|
'subevent__seat_category_mappings', 'subevent__meta_values',
|
||||||
)
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -143,7 +143,9 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
data=self.request.data
|
data=self.request.data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
|
instance.checkins.all().delete()
|
||||||
instance.log_action(
|
instance.log_action(
|
||||||
'pretix.event.checkinlist.deleted',
|
'pretix.event.checkinlist.deleted',
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ class OrderViewSetMixin:
|
|||||||
queryset = Order.objects.none()
|
queryset = Order.objects.none()
|
||||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||||
ordering = ('datetime',)
|
ordering = ('datetime',)
|
||||||
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
|
ordering_fields = ('datetime', 'code', 'status', 'last_modified', 'cancellation_date')
|
||||||
filterset_class = OrderFilter
|
filterset_class = OrderFilter
|
||||||
lookup_field = 'code'
|
lookup_field = 'code'
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class NativeAuthBackend(BaseAuthBackend):
|
|||||||
to log in.
|
to log in.
|
||||||
"""
|
"""
|
||||||
d = OrderedDict([
|
d = OrderedDict([
|
||||||
('email', forms.EmailField(label=_("E-mail"), max_length=254,
|
('email', forms.EmailField(label=_("Email"), max_length=254,
|
||||||
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
|
widget=forms.EmailInput(attrs={'autofocus': 'autofocus'}))),
|
||||||
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput,
|
('password', forms.CharField(label=_("Password"), widget=forms.PasswordInput,
|
||||||
max_length=4096)),
|
max_length=4096)),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def test_custom_smtp_backend(backend: T, from_addr: str) -> None:
|
|||||||
|
|
||||||
class BaseHTMLMailRenderer:
|
class BaseHTMLMailRenderer:
|
||||||
"""
|
"""
|
||||||
This is the base class for all HTML e-mail renderers.
|
This is the base class for all HTML email renderers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, event: Event, organizer=None):
|
def __init__(self, event: Event, organizer=None):
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
|
|||||||
_('Customer ID'),
|
_('Customer ID'),
|
||||||
_('SSO provider'),
|
_('SSO provider'),
|
||||||
_('External identifier'),
|
_('External identifier'),
|
||||||
_('E-mail'),
|
_('Email'),
|
||||||
_('Phone number'),
|
_('Phone number'),
|
||||||
_('Full name'),
|
_('Full name'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
_('Invoice number'),
|
_('Invoice number'),
|
||||||
_('Date'),
|
_('Date'),
|
||||||
_('Order code'),
|
_('Order code'),
|
||||||
_('E-mail address'),
|
_('Email address'),
|
||||||
_('Invoice type'),
|
_('Invoice type'),
|
||||||
_('Cancellation of'),
|
_('Cancellation of'),
|
||||||
_('Language'),
|
_('Language'),
|
||||||
@@ -326,7 +326,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
|||||||
_('Event start date'),
|
_('Event start date'),
|
||||||
_('Date'),
|
_('Date'),
|
||||||
_('Order code'),
|
_('Order code'),
|
||||||
_('E-mail address'),
|
_('Email address'),
|
||||||
_('Invoice type'),
|
_('Invoice type'),
|
||||||
_('Cancellation of'),
|
_('Cancellation of'),
|
||||||
_('Invoice sender:') + ' ' + _('Name'),
|
_('Invoice sender:') + ' ' + _('Name'),
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
headers.append(_('Comment'))
|
headers.append(_('Comment'))
|
||||||
headers.append(_('Follow-up date'))
|
headers.append(_('Follow-up date'))
|
||||||
headers.append(_('Positions'))
|
headers.append(_('Positions'))
|
||||||
headers.append(_('E-mail address verified'))
|
headers.append(_('Email address verified'))
|
||||||
headers.append(_('External customer ID'))
|
headers.append(_('External customer ID'))
|
||||||
headers.append(_('Payment providers'))
|
headers.append(_('Payment providers'))
|
||||||
if form_data.get('include_payment_amounts'):
|
if form_data.get('include_payment_amounts'):
|
||||||
@@ -655,7 +655,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
headers += [
|
headers += [
|
||||||
_('Sales channel'),
|
_('Sales channel'),
|
||||||
_('Order locale'),
|
_('Order locale'),
|
||||||
_('E-mail address verified'),
|
_('Email address verified'),
|
||||||
_('External customer ID'),
|
_('External customer ID'),
|
||||||
_('Check-in lists'),
|
_('Check-in lists'),
|
||||||
_('Payment providers'),
|
_('Payment providers'),
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ class PasswordRecoverForm(forms.Form):
|
|||||||
|
|
||||||
class PasswordForgotForm(forms.Form):
|
class PasswordForgotForm(forms.Form):
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label=_('E-mail'),
|
label=_('Email'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from django.core.validators import (
|
|||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.forms import Select, widgets
|
from django.forms import Select, widgets
|
||||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@@ -77,7 +78,7 @@ from pretix.base.i18n import (
|
|||||||
get_babel_locale, get_language_without_region, language,
|
get_babel_locale, get_language_without_region, language,
|
||||||
)
|
)
|
||||||
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
|
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 (
|
from pretix.base.services.tax import (
|
||||||
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
|
||||||
)
|
)
|
||||||
@@ -602,6 +603,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
questions = pos.item.questions_to_ask
|
questions = pos.item.questions_to_ask
|
||||||
event = kwargs.pop('event')
|
event = kwargs.pop('event')
|
||||||
self.all_optional = kwargs.pop('all_optional', False)
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@@ -676,7 +678,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
|
|
||||||
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
|
||||||
add_fields['street'] = forms.CharField(
|
add_fields['street'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=self.attendee_addresses_required,
|
||||||
label=_('Address'),
|
label=_('Address'),
|
||||||
widget=forms.Textarea(attrs={
|
widget=forms.Textarea(attrs={
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
@@ -686,7 +688,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
initial=(cartpos.street if cartpos else orderpos.street),
|
initial=(cartpos.street if cartpos else orderpos.street),
|
||||||
)
|
)
|
||||||
add_fields['zipcode'] = forms.CharField(
|
add_fields['zipcode'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=False,
|
||||||
max_length=30,
|
max_length=30,
|
||||||
label=_('ZIP code'),
|
label=_('ZIP code'),
|
||||||
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
||||||
@@ -695,7 +697,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
add_fields['city'] = forms.CharField(
|
add_fields['city'] = forms.CharField(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=False,
|
||||||
label=_('City'),
|
label=_('City'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
initial=(cartpos.city if cartpos else orderpos.city),
|
initial=(cartpos.city if cartpos else orderpos.city),
|
||||||
@@ -707,11 +709,12 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
add_fields['country'] = CountryField(
|
add_fields['country'] = CountryField(
|
||||||
countries=CachedCountries
|
countries=CachedCountries
|
||||||
).formfield(
|
).formfield(
|
||||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
required=self.attendee_addresses_required,
|
||||||
label=_('Country'),
|
label=_('Country'),
|
||||||
initial=country,
|
initial=country,
|
||||||
widget=forms.Select(attrs={
|
widget=forms.Select(attrs={
|
||||||
'autocomplete': 'country',
|
'autocomplete': 'country',
|
||||||
|
'data-country-information-url': reverse('js_helpers.states'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||||
@@ -946,9 +949,9 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
d = super().clean()
|
d = super().clean()
|
||||||
|
|
||||||
if self.address_validation:
|
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'):
|
if not d.get('state'):
|
||||||
self.add_error('state', _('This field is required.'))
|
self.add_error('state', _('This field is required.'))
|
||||||
|
|
||||||
@@ -1005,7 +1008,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
'street': forms.Textarea(attrs={
|
'street': forms.Textarea(attrs={
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
'placeholder': _('Street and Number'),
|
'placeholder': _('Street and Number'),
|
||||||
'autocomplete': 'street-address'
|
'autocomplete': 'street-address',
|
||||||
}),
|
}),
|
||||||
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
||||||
'country': forms.Select(attrs={
|
'country': forms.Select(attrs={
|
||||||
@@ -1021,7 +1024,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
'data-display-dependency': '#id_is_business_1',
|
'data-display-dependency': '#id_is_business_1',
|
||||||
'autocomplete': 'organization',
|
'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,
|
'internal_reference': forms.TextInput,
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
@@ -1055,6 +1058,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
])
|
])
|
||||||
|
|
||||||
self.fields['country'].choices = CachedCountries()
|
self.fields['country'].choices = CachedCountries()
|
||||||
|
self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states')
|
||||||
|
|
||||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||||
fprefix = self.prefix + '-' if self.prefix else ''
|
fprefix = self.prefix + '-' if self.prefix else ''
|
||||||
@@ -1083,6 +1087,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
self.fields['state'].widget.is_required = True
|
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.
|
# 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:
|
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
|
||||||
self.data = self.data.copy()
|
self.data = self.data.copy()
|
||||||
@@ -1142,9 +1150,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
data['vat_id'] = ''
|
data['vat_id'] = ''
|
||||||
if self.event.settings.invoice_address_required:
|
if self.event.settings.invoice_address_required:
|
||||||
if data.get('is_business') and not data.get('company'):
|
if data.get('is_business') and not data.get('company'):
|
||||||
raise ValidationError(_('You need to provide a company name.'))
|
raise ValidationError({"company": _('You need to provide a company name.')})
|
||||||
if not data.get('is_business') and not data.get('name_parts'):
|
if not data.get('is_business') and not data.get('name_parts'):
|
||||||
raise ValidationError(_('You need to provide your name.'))
|
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'):
|
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
||||||
self.instance.vat_id_validated = False
|
self.instance.vat_id_validated = False
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ from pretix.control.forms import SingleLanguageWidget
|
|||||||
|
|
||||||
class UserSettingsForm(forms.ModelForm):
|
class UserSettingsForm(forms.ModelForm):
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'duplicate_identifier': _("There already is an account associated with this e-mail address. "
|
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||||
"Please choose a different one."),
|
"Please choose a different one."),
|
||||||
'pw_current': _("Please enter your current password if you want to change your e-mail "
|
'pw_current': _("Please enter your current password if you want to change your email address "
|
||||||
"address or password."),
|
"or password."),
|
||||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||||
'pw_mismatch': _("Please enter the same password twice"),
|
'pw_mismatch': _("Please enter the same password twice"),
|
||||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
def _clean_text(self, text, tags=None):
|
def _clean_text(self, text, tags=None):
|
||||||
return self._normalize(bleach.clean(
|
return self._normalize(bleach.clean(
|
||||||
text,
|
text,
|
||||||
tags=tags or []
|
tags=set(tags) if tags else set()
|
||||||
).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
|
).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
|
||||||
|
|
||||||
|
|
||||||
@@ -461,7 +461,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
def _draw_event(self, canvas):
|
def _draw_event(self, canvas):
|
||||||
def shorten(txt):
|
def shorten(txt):
|
||||||
txt = str(txt)
|
txt = str(txt)
|
||||||
txt = bleach.clean(txt, tags=[]).strip()
|
txt = bleach.clean(txt, tags=set()).strip()
|
||||||
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||||
p_size = p.wrap(self.event_width, self.event_height)
|
p_size = p.wrap(self.event_width, self.event_height)
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ class BaseMediaType:
|
|||||||
def verbose_name(self):
|
def verbose_name(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""
|
||||||
|
This can be:
|
||||||
|
|
||||||
|
- The name of a Font Awesome icon to represent this channel type.
|
||||||
|
- The name of a SVG icon file that is resolvable through the static file system. We recommend to design for a size of 18x14 pixels.
|
||||||
|
"""
|
||||||
|
return "circle"
|
||||||
|
|
||||||
def generate_identifier(self, organizer):
|
def generate_identifier(self, organizer):
|
||||||
if self.medium_created_by_server:
|
if self.medium_created_by_server:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -59,6 +69,7 @@ class BaseMediaType:
|
|||||||
class BarcodePlainMediaType(BaseMediaType):
|
class BarcodePlainMediaType(BaseMediaType):
|
||||||
identifier = 'barcode'
|
identifier = 'barcode'
|
||||||
verbose_name = _('Barcode / QR-Code')
|
verbose_name = _('Barcode / QR-Code')
|
||||||
|
icon = 'qrcode'
|
||||||
medium_created_by_server = True
|
medium_created_by_server = True
|
||||||
supports_giftcard = False
|
supports_giftcard = False
|
||||||
supports_orderposition = True
|
supports_orderposition = True
|
||||||
@@ -75,6 +86,7 @@ class BarcodePlainMediaType(BaseMediaType):
|
|||||||
class NfcUidMediaType(BaseMediaType):
|
class NfcUidMediaType(BaseMediaType):
|
||||||
identifier = 'nfc_uid'
|
identifier = 'nfc_uid'
|
||||||
verbose_name = _('NFC UID-based')
|
verbose_name = _('NFC UID-based')
|
||||||
|
icon = 'pretixbase/img/media/nfc_uid.svg'
|
||||||
medium_created_by_server = False
|
medium_created_by_server = False
|
||||||
supports_giftcard = True
|
supports_giftcard = True
|
||||||
supports_orderposition = False
|
supports_orderposition = False
|
||||||
@@ -114,6 +126,7 @@ class NfcUidMediaType(BaseMediaType):
|
|||||||
class NfcMf0aesMediaType(BaseMediaType):
|
class NfcMf0aesMediaType(BaseMediaType):
|
||||||
identifier = 'nfc_mf0aes'
|
identifier = 'nfc_mf0aes'
|
||||||
verbose_name = 'NFC Mifare Ultralight AES'
|
verbose_name = 'NFC Mifare Ultralight AES'
|
||||||
|
icon = 'pretixbase/img/media/nfc_secure.svg'
|
||||||
medium_created_by_server = False
|
medium_created_by_server = False
|
||||||
supports_giftcard = True
|
supports_giftcard = True
|
||||||
supports_orderposition = False
|
supports_orderposition = False
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
|
|||||||
('password', models.CharField(verbose_name='password', max_length=128)),
|
('password', models.CharField(verbose_name='password', max_length=128)),
|
||||||
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)),
|
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)),
|
||||||
('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')),
|
('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')),
|
||||||
('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='E-mail', null=True,
|
('email', models.EmailField(max_length=191, blank=True, unique=True, verbose_name='Email', null=True,
|
||||||
db_index=True)),
|
db_index=True)),
|
||||||
('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)),
|
('givenname', models.CharField(verbose_name='Given name', max_length=255, blank=True, null=True)),
|
||||||
('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)),
|
('familyname', models.CharField(verbose_name='Family name', max_length=255, blank=True, null=True)),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Migration(migrations.Migration):
|
|||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='E-mail')),
|
('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='Email')),
|
||||||
('givenname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Given name')),
|
('givenname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Given name')),
|
||||||
('familyname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Family name')),
|
('familyname', models.CharField(blank=True, max_length=255, null=True, verbose_name='Family name')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
@@ -232,7 +232,7 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('code', models.CharField(max_length=16, verbose_name='Order code')),
|
('code', models.CharField(max_length=16, verbose_name='Order code')),
|
||||||
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
|
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')),
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
|
||||||
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
|
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
|
||||||
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
|
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
|
||||||
('datetime', models.DateTimeField(verbose_name='Date')),
|
('datetime', models.DateTimeField(verbose_name='Date')),
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('code', models.CharField(max_length=16, verbose_name='Order code')),
|
('code', models.CharField(max_length=16, verbose_name='Order code')),
|
||||||
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
|
('status', models.CharField(choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3, verbose_name='Status')),
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mail')),
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
|
||||||
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
|
('locale', models.CharField(blank=True, max_length=32, null=True, verbose_name='Locale')),
|
||||||
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
|
('secret', models.CharField(default=pretix.base.models.orders.generate_secret, max_length=32)),
|
||||||
('datetime', models.DateTimeField(verbose_name='Date')),
|
('datetime', models.DateTimeField(verbose_name='Date')),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
|
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
|
||||||
('email', models.EmailField(max_length=254, verbose_name='E-mail address')),
|
('email', models.EmailField(max_length=254, verbose_name='Email address')),
|
||||||
('locale', models.CharField(default='en', max_length=190)),
|
('locale', models.CharField(default='en', max_length=190)),
|
||||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
|
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
|
||||||
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
|
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
|
('created', models.DateTimeField(auto_now_add=True, verbose_name='On waiting list since')),
|
||||||
('email', models.EmailField(max_length=254, verbose_name='E-mail address')),
|
('email', models.EmailField(max_length=254, verbose_name='Email address')),
|
||||||
('locale', models.CharField(default='en', max_length=190)),
|
('locale', models.CharField(default='en', max_length=190)),
|
||||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
|
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Event', verbose_name='Event')),
|
||||||
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
|
('item', models.ForeignKey(help_text='The product the user waits for.', on_delete=django.db.models.deletion.CASCADE, related_name='waitinglistentries', to='pretixbase.Item', verbose_name='Product')),
|
||||||
|
|||||||
+1
-1
@@ -163,7 +163,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('action_type', models.CharField(max_length=255)),
|
('action_type', models.CharField(max_length=255)),
|
||||||
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)),
|
('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
|
||||||
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
|
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||||
to='pretixbase.Event')),
|
to='pretixbase.Event')),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('action_type', models.CharField(max_length=255)),
|
('action_type', models.CharField(max_length=255)),
|
||||||
('method', models.CharField(choices=[('mail', 'E-mail')], max_length=255)),
|
('method', models.CharField(choices=[('mail', 'Email')], max_length=255)),
|
||||||
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
|
('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
('enabled', models.BooleanField(default=True)),
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-10-29 15:03
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_autocheckin(apps, schema_editor):
|
||||||
|
CheckinList = apps.get_model("pretixbase", "CheckinList")
|
||||||
|
AutoCheckinRule = apps.get_model("autocheckin", "AutoCheckinRule")
|
||||||
|
|
||||||
|
for cl in CheckinList.objects.filter(auto_checkin_sales_channels__isnull=False).select_related("event", "event__organizer"):
|
||||||
|
sales_channels = cl.auto_checkin_sales_channels.all()
|
||||||
|
all_sales_channels = cl.event.organizer.sales_channels.all()
|
||||||
|
|
||||||
|
if "pretix.plugins.autocheckin" not in cl.event.plugins:
|
||||||
|
cl.event.plugins = cl.event.plugins + ",pretix.plugins.autocheckin"
|
||||||
|
cl.event.save()
|
||||||
|
|
||||||
|
r = AutoCheckinRule.objects.get_or_create(
|
||||||
|
list=cl,
|
||||||
|
event=cl.event,
|
||||||
|
all_products=True,
|
||||||
|
all_payment_methods=True,
|
||||||
|
defaults=dict(
|
||||||
|
mode="placed",
|
||||||
|
all_sales_channels=len(sales_channels) == len(all_sales_channels),
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
if len(sales_channels) != len(all_sales_channels):
|
||||||
|
r.limit_sales_channels.set(sales_channels)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("pretixbase", "0272_printlog"),
|
||||||
|
("autocheckin", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
migrate_autocheckin,
|
||||||
|
migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="checkinlist",
|
||||||
|
name="auto_checkin_sales_channels",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -256,6 +256,9 @@ class SubeventColumnMixin:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
|
||||||
if value in self._subevent_cache:
|
if value in self._subevent_cache:
|
||||||
return self._subevent_cache[value]
|
return self._subevent_cache[value]
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ from pretix.base.signals import order_import_columns
|
|||||||
|
|
||||||
class EmailColumn(ImportColumn):
|
class EmailColumn(ImportColumn):
|
||||||
identifier = 'email'
|
identifier = 'email'
|
||||||
verbose_name = gettext_lazy('E-mail address')
|
verbose_name = gettext_lazy('Email address')
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
if value:
|
if value:
|
||||||
@@ -322,7 +322,7 @@ class AttendeeNamePart(ImportColumn):
|
|||||||
|
|
||||||
class AttendeeEmail(ImportColumn):
|
class AttendeeEmail(ImportColumn):
|
||||||
identifier = 'attendee_email'
|
identifier = 'attendee_email'
|
||||||
verbose_name = gettext_lazy('Attendee e-mail address')
|
verbose_name = gettext_lazy('Attendee email address')
|
||||||
|
|
||||||
def clean(self, value, previous_values):
|
def clean(self, value, previous_values):
|
||||||
if value:
|
if value:
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
|
||||||
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
|
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
|
||||||
verbose_name=_('E-mail'), max_length=190)
|
verbose_name=_('Email'), max_length=190)
|
||||||
fullname = models.CharField(max_length=255, blank=True, null=True,
|
fullname = models.CharField(max_length=255, blank=True, null=True,
|
||||||
verbose_name=_('Full name'))
|
verbose_name=_('Full name'))
|
||||||
is_active = models.BooleanField(default=True,
|
is_active = models.BooleanField(default=True,
|
||||||
|
|||||||
@@ -99,14 +99,6 @@ class CheckinList(LoggedModel):
|
|||||||
verbose_name=_('Automatically check out everyone at'),
|
verbose_name=_('Automatically check out everyone at'),
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
auto_checkin_sales_channels = models.ManyToManyField(
|
|
||||||
"SalesChannel",
|
|
||||||
verbose_name=_('Sales channels to automatically check in'),
|
|
||||||
help_text=_('This option is deprecated and will be removed in the next months. As a replacement, our new plugin '
|
|
||||||
'"Auto check-in" can be used. When we remove this option, we will automatically migrate your event '
|
|
||||||
'to use the new plugin.'),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
rules = models.JSONField(default=dict, blank=True)
|
rules = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
objects = ScopedManager(organizer='event__organizer')
|
objects = ScopedManager(organizer='event__organizer')
|
||||||
@@ -141,7 +133,7 @@ class CheckinList(LoggedModel):
|
|||||||
return self.positions_query(ignore_status=False)
|
return self.positions_query(ignore_status=False)
|
||||||
|
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
def positions_inside_query(self, ignore_status=False, at_time=None):
|
def _filter_positions_inside(self, qs, at_time=None):
|
||||||
if at_time is None:
|
if at_time is None:
|
||||||
c_q = []
|
c_q = []
|
||||||
else:
|
else:
|
||||||
@@ -149,7 +141,7 @@ class CheckinList(LoggedModel):
|
|||||||
|
|
||||||
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
|
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
|
||||||
# Use a simple approach that works on all databases
|
# Use a simple approach that works on all databases
|
||||||
qs = self.positions_query(ignore_status=ignore_status).annotate(
|
qs = qs.annotate(
|
||||||
last_entry=Subquery(
|
last_entry=Subquery(
|
||||||
Checkin.objects.filter(
|
Checkin.objects.filter(
|
||||||
*c_q,
|
*c_q,
|
||||||
@@ -202,7 +194,7 @@ class CheckinList(LoggedModel):
|
|||||||
.values("position_id", "type", "datetime", "cnt_exists_after")
|
.values("position_id", "type", "datetime", "cnt_exists_after")
|
||||||
.query.sql_with_params()
|
.query.sql_with_params()
|
||||||
)
|
)
|
||||||
return self.positions_query(ignore_status=ignore_status).filter(
|
return qs.filter(
|
||||||
pk__in=RawSQL(
|
pk__in=RawSQL(
|
||||||
f"""
|
f"""
|
||||||
SELECT "position_id"
|
SELECT "position_id"
|
||||||
@@ -214,6 +206,10 @@ class CheckinList(LoggedModel):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@scopes_disabled()
|
||||||
|
def positions_inside_query(self, ignore_status=False, at_time=None):
|
||||||
|
return self._filter_positions_inside(self.positions_query(ignore_status=ignore_status), at_time=at_time)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def positions_inside(self):
|
def positions_inside(self):
|
||||||
return self.positions_inside_query(None)
|
return self.positions_inside_query(None)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class Customer(LoggedModel):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190)
|
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('Email'), max_length=190)
|
||||||
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
|
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
|
||||||
password = models.CharField(verbose_name=_('Password'), max_length=128)
|
password = models.CharField(verbose_name=_('Password'), max_length=128)
|
||||||
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
||||||
@@ -392,7 +392,7 @@ class CustomerSSOClient(LoggedModel):
|
|||||||
SCOPE_CHOICES = (
|
SCOPE_CHOICES = (
|
||||||
('openid', _('OpenID Connect access (required)')),
|
('openid', _('OpenID Connect access (required)')),
|
||||||
('profile', _('Profile data (name, addresses)')),
|
('profile', _('Profile data (name, addresses)')),
|
||||||
('email', _('E-mail address')),
|
('email', _('Email address')),
|
||||||
('phone', _('Phone number')),
|
('phone', _('Phone number')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from django.utils.crypto import get_random_string
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_scopes import ScopedManager, scopes_disabled
|
from django_scopes import ScopedManager, scopes_disabled
|
||||||
|
|
||||||
from pretix.api.auth.devicesecurity import DEVICE_SECURITY_PROFILES
|
|
||||||
from pretix.base.models import LoggedModel
|
from pretix.base.models import LoggedModel
|
||||||
|
|
||||||
|
|
||||||
@@ -161,7 +160,6 @@ class Device(LoggedModel):
|
|||||||
)
|
)
|
||||||
security_profile = models.CharField(
|
security_profile = models.CharField(
|
||||||
max_length=190,
|
max_length=190,
|
||||||
choices=[(k, v.verbose_name) for k, v in DEVICE_SECURITY_PROFILES.items()],
|
|
||||||
default='full',
|
default='full',
|
||||||
null=True,
|
null=True,
|
||||||
blank=False
|
blank=False
|
||||||
|
|||||||
@@ -1024,10 +1024,9 @@ class Event(EventMixin, LoggedModel):
|
|||||||
|
|
||||||
checkin_list_map = {}
|
checkin_list_map = {}
|
||||||
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related(
|
for cl in other.checkin_lists.filter(subevent__isnull=True).prefetch_related(
|
||||||
'limit_products', 'auto_checkin_sales_channels'
|
'limit_products'
|
||||||
):
|
):
|
||||||
items = list(cl.limit_products.all())
|
items = list(cl.limit_products.all())
|
||||||
auto_checkin_sales_channels = list(cl.auto_checkin_sales_channels.all())
|
|
||||||
checkin_list_map[cl.pk] = cl
|
checkin_list_map[cl.pk] = cl
|
||||||
cl.pk = None
|
cl.pk = None
|
||||||
cl._prefetched_objects_cache = {}
|
cl._prefetched_objects_cache = {}
|
||||||
@@ -1039,8 +1038,6 @@ class Event(EventMixin, LoggedModel):
|
|||||||
cl.log_action('pretix.object.cloned')
|
cl.log_action('pretix.object.cloned')
|
||||||
for i in items:
|
for i in items:
|
||||||
cl.limit_products.add(item_map[i.pk])
|
cl.limit_products.add(item_map[i.pk])
|
||||||
if auto_checkin_sales_channels:
|
|
||||||
cl.auto_checkin_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in auto_checkin_sales_channels]))
|
|
||||||
|
|
||||||
if other.seating_plan:
|
if other.seating_plan:
|
||||||
if other.seating_plan.organizer_id == self.organizer_id:
|
if other.seating_plan.organizer_id == self.organizer_id:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class NotificationSetting(models.Model):
|
|||||||
:type enabled: bool
|
:type enabled: bool
|
||||||
"""
|
"""
|
||||||
CHANNELS = (
|
CHANNELS = (
|
||||||
('mail', _('E-mail')),
|
('mail', _('Email')),
|
||||||
)
|
)
|
||||||
user = models.ForeignKey('User', on_delete=models.CASCADE,
|
user = models.ForeignKey('User', on_delete=models.CASCADE,
|
||||||
related_name='notification_settings')
|
related_name='notification_settings')
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
)
|
)
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
verbose_name=_('E-mail')
|
verbose_name=_('Email')
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
@@ -317,7 +317,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
)
|
)
|
||||||
email_known_to_work = models.BooleanField(
|
email_known_to_work = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_('E-mail address verified')
|
verbose_name=_('Email address verified')
|
||||||
)
|
)
|
||||||
invoice_dirty = models.BooleanField(
|
invoice_dirty = models.BooleanField(
|
||||||
# Invoice needs to be re-issued when the order is paid again
|
# Invoice needs to be re-issued when the order is paid again
|
||||||
@@ -3204,9 +3204,9 @@ class InvoiceAddress(models.Model):
|
|||||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
|
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_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
|
||||||
name_parts = models.JSONField(default=dict)
|
name_parts = models.JSONField(default=dict)
|
||||||
street = models.TextField(verbose_name=_('Address'), blank=False)
|
street = models.TextField(verbose_name=_('Address'), blank=True)
|
||||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
|
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True)
|
||||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)
|
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True)
|
||||||
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False)
|
||||||
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
|
||||||
countries=CachedCountries)
|
countries=CachedCountries)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class WaitingListEntry(LoggedModel):
|
|||||||
blank=True, default=dict
|
blank=True, default=dict
|
||||||
)
|
)
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
verbose_name=_("E-mail address")
|
verbose_name=_("Email address")
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
|
|||||||
@@ -343,11 +343,13 @@ class CartManager:
|
|||||||
err = error_messages['some_subevent_not_started']
|
err = error_messages['some_subevent_not_started']
|
||||||
cp.addons.all().delete()
|
cp.addons.all().delete()
|
||||||
cp.delete()
|
cp.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end:
|
if cp.subevent and cp.subevent.presale_end and time_machine_now(self.real_now_dt) > cp.subevent.presale_end:
|
||||||
err = error_messages['some_subevent_ended']
|
err = error_messages['some_subevent_ended']
|
||||||
cp.addons.all().delete()
|
cp.addons.all().delete()
|
||||||
cp.delete()
|
cp.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
if cp.subevent:
|
if cp.subevent:
|
||||||
tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
tlv = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||||
@@ -360,6 +362,7 @@ class CartManager:
|
|||||||
err = error_messages['some_subevent_ended']
|
err = error_messages['some_subevent_ended']
|
||||||
cp.addons.all().delete()
|
cp.addons.all().delete()
|
||||||
cp.delete()
|
cp.delete()
|
||||||
|
continue
|
||||||
return err
|
return err
|
||||||
|
|
||||||
def _update_subevents_cache(self, se_ids: List[int]):
|
def _update_subevents_cache(self, se_ids: List[int]):
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ from pretix.base.models import (
|
|||||||
Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order,
|
Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order,
|
||||||
OrderPosition, QuestionOption,
|
OrderPosition, QuestionOption,
|
||||||
)
|
)
|
||||||
from pretix.base.signals import checkin_created, order_placed, periodic_task
|
from pretix.base.signals import checkin_created, periodic_task
|
||||||
from pretix.helpers import OF_SELF
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.jsonlogic import Logic
|
from pretix.helpers.jsonlogic import Logic
|
||||||
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
|
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
|
||||||
@@ -1154,23 +1154,6 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(order_placed, dispatch_uid="legacy_autocheckin_order_placed")
|
|
||||||
def order_placed(sender, **kwargs):
|
|
||||||
order = kwargs['order']
|
|
||||||
event = sender
|
|
||||||
|
|
||||||
cls = list(event.checkin_lists.filter(auto_checkin_sales_channels=order.sales_channel).prefetch_related(
|
|
||||||
'limit_products'))
|
|
||||||
if not cls:
|
|
||||||
return
|
|
||||||
for op in order.positions.all():
|
|
||||||
for cl in cls:
|
|
||||||
if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}:
|
|
||||||
if not cl.subevent_id or cl.subevent_id == op.subevent_id:
|
|
||||||
ci = Checkin.objects.create(position=op, list=cl, auto_checked_in=True, type=Checkin.TYPE_ENTRY)
|
|
||||||
checkin_created.send(event, checkin=ci)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(periodic_task, dispatch_uid="autocheckout_exit_all")
|
@receiver(periodic_task, dispatch_uid="autocheckout_exit_all")
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
def process_exit_all(sender, **kwargs):
|
def process_exit_all(sender, **kwargs):
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
|||||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||||
else:
|
else:
|
||||||
# Backwards compatibility
|
# Backwards compatibility
|
||||||
warnings.warn('E-mail renderer called without position argument because position argument is not '
|
warnings.warn('Email renderer called without position argument because position argument is not '
|
||||||
'supported.',
|
'supported.',
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ DEFAULTS = {
|
|||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'type': bool,
|
'type': bool,
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Require a business addresses"),
|
label=_("Require a business address"),
|
||||||
help_text=_('This will require users to enter a company name.'),
|
help_text=_('This will require users to enter a company name.'),
|
||||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_required'}),
|
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_required'}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -287,9 +287,9 @@ class PhoneNumberShredder(BaseDataShredder):
|
|||||||
|
|
||||||
|
|
||||||
class EmailAddressShredder(BaseDataShredder):
|
class EmailAddressShredder(BaseDataShredder):
|
||||||
verbose_name = _('E-mails')
|
verbose_name = _('Emails')
|
||||||
identifier = 'order_emails'
|
identifier = 'order_emails'
|
||||||
description = _('This will remove all e-mail addresses from orders and attendees, as well as logged email '
|
description = _('This will remove all email addresses from orders and attendees, as well as logged email '
|
||||||
'contents. This will also remove the association to customer accounts.')
|
'contents. This will also remove the association to customer accounts.')
|
||||||
|
|
||||||
def generate_files(self) -> List[Tuple[str, str, str]]:
|
def generate_files(self) -> List[Tuple[str, str, str]]:
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ from tlds import tld_set
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
ALLOWED_TAGS_SNIPPET = [
|
ALLOWED_TAGS_SNIPPET = {
|
||||||
'a',
|
'a',
|
||||||
'abbr',
|
'abbr',
|
||||||
'acronym',
|
'acronym',
|
||||||
@@ -68,8 +68,8 @@ ALLOWED_TAGS_SNIPPET = [
|
|||||||
'strike',
|
'strike',
|
||||||
's',
|
's',
|
||||||
# Update doc/user/markdown.rst if you change this!
|
# Update doc/user/markdown.rst if you change this!
|
||||||
]
|
}
|
||||||
ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET + [
|
ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET | {
|
||||||
'blockquote',
|
'blockquote',
|
||||||
'li',
|
'li',
|
||||||
'ol',
|
'ol',
|
||||||
@@ -91,7 +91,7 @@ ALLOWED_TAGS = ALLOWED_TAGS_SNIPPET + [
|
|||||||
'h6',
|
'h6',
|
||||||
'pre',
|
'pre',
|
||||||
# Update doc/user/markdown.rst if you change this!
|
# Update doc/user/markdown.rst if you change this!
|
||||||
]
|
}
|
||||||
|
|
||||||
ALLOWED_ATTRIBUTES = {
|
ALLOWED_ATTRIBUTES = {
|
||||||
'a': ['href', 'title', 'class'],
|
'a': ['href', 'title', 'class'],
|
||||||
@@ -106,7 +106,7 @@ ALLOWED_ATTRIBUTES = {
|
|||||||
# Update doc/user/markdown.rst if you change this!
|
# Update doc/user/markdown.rst if you change this!
|
||||||
}
|
}
|
||||||
|
|
||||||
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto', 'tel']
|
ALLOWED_PROTOCOLS = {'http', 'https', 'mailto', 'tel'}
|
||||||
|
|
||||||
URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, reverse=True)))
|
URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, reverse=True)))
|
||||||
|
|
||||||
@@ -211,9 +211,9 @@ class CleanPostprocessor(Postprocessor):
|
|||||||
def run(self, text):
|
def run(self, text):
|
||||||
return bleach.clean(
|
return bleach.clean(
|
||||||
text,
|
text,
|
||||||
tags=self.tags,
|
tags=set(self.tags),
|
||||||
attributes=self.attributes,
|
attributes=self.attributes,
|
||||||
protocols=self.protocols,
|
protocols=set(self.protocols),
|
||||||
strip=self.strip
|
strip=self.strip
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes
|
|||||||
EmailNl2BrExtension(),
|
EmailNl2BrExtension(),
|
||||||
LinkifyAndCleanExtension(
|
LinkifyAndCleanExtension(
|
||||||
linker,
|
linker,
|
||||||
tags=allowed_tags,
|
tags=set(allowed_tags),
|
||||||
attributes=allowed_attributes,
|
attributes=allowed_attributes,
|
||||||
protocols=ALLOWED_PROTOCOLS,
|
protocols=ALLOWED_PROTOCOLS,
|
||||||
strip=False,
|
strip=False,
|
||||||
|
|||||||
@@ -22,16 +22,30 @@
|
|||||||
import pycountry
|
import pycountry
|
||||||
from django.http import JsonResponse
|
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
|
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
def states(request):
|
def states(request):
|
||||||
cc = request.GET.get("country", "DE")
|
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:
|
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||||
return JsonResponse({'data': []})
|
return JsonResponse({'data': [], **info, })
|
||||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||||
return JsonResponse({'data': [
|
return JsonResponse({
|
||||||
{'name': s.name, 'code': s.code[3:]}
|
'data': [
|
||||||
for s in sorted(statelist, key=lambda s: s.name)
|
{'name': s.name, 'code': s.code[3:]}
|
||||||
]})
|
for s in sorted(statelist, key=lambda s: s.name)
|
||||||
|
],
|
||||||
|
**info,
|
||||||
|
})
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ from django_scopes.forms import (
|
|||||||
from pretix.base.forms.widgets import SplitDateTimePickerWidget
|
from pretix.base.forms.widgets import SplitDateTimePickerWidget
|
||||||
from pretix.base.models import Gate
|
from pretix.base.models import Gate
|
||||||
from pretix.base.models.checkin import Checkin, CheckinList
|
from pretix.base.models.checkin import Checkin, CheckinList
|
||||||
from pretix.control.forms import (
|
from pretix.control.forms import ItemMultipleChoiceField
|
||||||
ItemMultipleChoiceField, SalesChannelCheckboxSelectMultiple,
|
|
||||||
)
|
|
||||||
from pretix.control.forms.widgets import Select2
|
from pretix.control.forms.widgets import Select2
|
||||||
|
|
||||||
|
|
||||||
@@ -67,10 +65,6 @@ class CheckinListForm(forms.ModelForm):
|
|||||||
kwargs.pop('locales', None)
|
kwargs.pop('locales', None)
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.fields['limit_products'].queryset = self.event.items.all()
|
self.fields['limit_products'].queryset = self.event.items.all()
|
||||||
self.fields['auto_checkin_sales_channels'].queryset = self.event.organizer.sales_channels.all()
|
|
||||||
self.fields['auto_checkin_sales_channels'].widget = SalesChannelCheckboxSelectMultiple(
|
|
||||||
self.event, choices=self.fields['auto_checkin_sales_channels'].widget.choices
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.event.organizer.gates.exists():
|
if not self.event.organizer.gates.exists():
|
||||||
del self.fields['gates']
|
del self.fields['gates']
|
||||||
@@ -102,7 +96,6 @@ class CheckinListForm(forms.ModelForm):
|
|||||||
'limit_products',
|
'limit_products',
|
||||||
'subevent',
|
'subevent',
|
||||||
'include_pending',
|
'include_pending',
|
||||||
'auto_checkin_sales_channels',
|
|
||||||
'allow_multiple_entries',
|
'allow_multiple_entries',
|
||||||
'allow_entry_after_exit',
|
'allow_entry_after_exit',
|
||||||
'rules',
|
'rules',
|
||||||
@@ -125,7 +118,6 @@ class CheckinListForm(forms.ModelForm):
|
|||||||
'limit_products': ItemMultipleChoiceField,
|
'limit_products': ItemMultipleChoiceField,
|
||||||
'gates': SafeModelMultipleChoiceField,
|
'gates': SafeModelMultipleChoiceField,
|
||||||
'subevent': SafeModelChoiceField,
|
'subevent': SafeModelChoiceField,
|
||||||
'auto_checkin_sales_channels': SafeModelMultipleChoiceField,
|
|
||||||
'exit_all_at': NextTimeField,
|
'exit_all_at': NextTimeField,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
|
|||||||
)
|
)
|
||||||
email = forms.CharField(
|
email = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('E-mail address')
|
label=_('Email address')
|
||||||
)
|
)
|
||||||
comment = forms.CharField(
|
comment = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@@ -563,7 +563,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
|
|||||||
email_known_to_work = forms.NullBooleanField(
|
email_known_to_work = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
widget=FilterNullBooleanSelect,
|
widget=FilterNullBooleanSelect,
|
||||||
label=_('E-mail address verified'),
|
label=_('Email address verified'),
|
||||||
)
|
)
|
||||||
total = forms.DecimalField(
|
total = forms.DecimalField(
|
||||||
localize=True,
|
localize=True,
|
||||||
@@ -648,7 +648,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
|
|||||||
)
|
)
|
||||||
self.fields['attendee_email'] = forms.CharField(
|
self.fields['attendee_email'] = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Attendee e-mail address')
|
label=_('Attendee email address')
|
||||||
)
|
)
|
||||||
self.fields['attendee_address_company'] = forms.CharField(
|
self.fields['attendee_address_company'] = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@@ -1967,7 +1967,7 @@ class CheckinListAttendeeFilterForm(FilterForm):
|
|||||||
if s == '1':
|
if s == '1':
|
||||||
qs = qs.filter(last_entry__isnull=False)
|
qs = qs.filter(last_entry__isnull=False)
|
||||||
elif s == '2':
|
elif s == '2':
|
||||||
qs = qs.filter(pk__in=self.list.positions_inside.values_list('pk'))
|
qs = self.list._filter_positions_inside(qs)
|
||||||
elif s == '3':
|
elif s == '3':
|
||||||
qs = qs.filter(last_entry__isnull=False).filter(
|
qs = qs.filter(last_entry__isnull=False).filter(
|
||||||
Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry'))
|
Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry'))
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class UpdateSettingsForm(SettingsForm):
|
|||||||
)
|
)
|
||||||
update_check_email = forms.EmailField(
|
update_check_email = forms.EmailField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_("E-mail notifications"),
|
label=_("Email notifications"),
|
||||||
help_text=_("We will notify you at this address if we detect that a new update is available. This "
|
help_text=_("We will notify you at this address if we detect that a new update is available. This "
|
||||||
"address will not be transmitted to pretix.eu, the emails will be sent by this server "
|
"address will not be transmitted to pretix.eu, the emails will be sent by this server "
|
||||||
"locally.")
|
"locally.")
|
||||||
|
|||||||
@@ -609,6 +609,49 @@ class OrderFeeChangeForm(forms.Form):
|
|||||||
change_decimal_field(self.fields['value'], instance.order.event.currency)
|
change_decimal_field(self.fields['value'], instance.order.event.currency)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderFeeAddForm(forms.Form):
|
||||||
|
fee_type = forms.ChoiceField(choices=OrderFee.FEE_TYPES)
|
||||||
|
value = forms.DecimalField(
|
||||||
|
max_digits=13, decimal_places=2,
|
||||||
|
localize=True,
|
||||||
|
label=_('Price'),
|
||||||
|
help_text=_("including all taxes"),
|
||||||
|
)
|
||||||
|
tax_rule = forms.ModelChoiceField(
|
||||||
|
TaxRule.objects.none(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
description = forms.CharField(required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
order = kwargs.pop('order')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['tax_rule'].queryset = order.event.tax_rules.all()
|
||||||
|
change_decimal_field(self.fields['value'], order.event.currency)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderFeeAddFormset(forms.BaseFormSet):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.order = kwargs.pop('order', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _construct_form(self, i, **kwargs):
|
||||||
|
kwargs['order'] = self.order
|
||||||
|
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,
|
||||||
|
order=self.order,
|
||||||
|
)
|
||||||
|
self.add_fields(form, None)
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class OrderContactForm(forms.ModelForm):
|
class OrderContactForm(forms.ModelForm):
|
||||||
regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'),
|
regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'),
|
||||||
help_text=_('Regenerates the order and ticket secrets. You will '
|
help_text=_('Regenerates the order and ticket secrets. You will '
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from i18nfield.strings import LazyI18nString
|
|||||||
from phonenumber_field.formfields import PhoneNumberField
|
from phonenumber_field.formfields import PhoneNumberField
|
||||||
from pytz import common_timezones
|
from pytz import common_timezones
|
||||||
|
|
||||||
|
from pretix.api.auth.devicesecurity import get_all_security_profiles
|
||||||
from pretix.api.models import WebHook
|
from pretix.api.models import WebHook
|
||||||
from pretix.api.webhooks import get_all_webhook_events
|
from pretix.api.webhooks import get_all_webhook_events
|
||||||
from pretix.base.customersso.oidc import oidc_validate_and_complete_config
|
from pretix.base.customersso.oidc import oidc_validate_and_complete_config
|
||||||
@@ -311,6 +312,11 @@ class DeviceForm(forms.ModelForm):
|
|||||||
'-has_subevents', '-date_from'
|
'-has_subevents', '-date_from'
|
||||||
)
|
)
|
||||||
self.fields['gate'].queryset = organizer.gates.all()
|
self.fields['gate'].queryset = organizer.gates.all()
|
||||||
|
self.fields['security_profile'] = forms.ChoiceField(
|
||||||
|
label=self.fields['security_profile'].label,
|
||||||
|
help_text=self.fields['security_profile'].help_text,
|
||||||
|
choices=[(k, v.verbose_name) for k, v in get_all_security_profiles().items()],
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
d = super().clean()
|
d = super().clean()
|
||||||
@@ -344,6 +350,11 @@ class DeviceBulkEditForm(forms.ModelForm):
|
|||||||
'-has_subevents', '-date_from'
|
'-has_subevents', '-date_from'
|
||||||
)
|
)
|
||||||
self.fields['gate'].queryset = organizer.gates.all()
|
self.fields['gate'].queryset = organizer.gates.all()
|
||||||
|
self.fields['security_profile'] = forms.ChoiceField(
|
||||||
|
label=self.fields['security_profile'].label,
|
||||||
|
help_text=self.fields['security_profile'].help_text,
|
||||||
|
choices=[(k, v.verbose_name) for k, v in get_all_security_profiles().items()],
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
d = super().clean()
|
d = super().clean()
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class StaffSessionForm(forms.ModelForm):
|
|||||||
|
|
||||||
class UserEditForm(forms.ModelForm):
|
class UserEditForm(forms.ModelForm):
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'duplicate_identifier': _("There already is an account associated with this e-mail address. "
|
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||||
"Please choose a different one."),
|
"Please choose a different one."),
|
||||||
'pw_mismatch': _("Please enter the same password twice"),
|
'pw_mismatch': _("Please enter the same password twice"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -613,7 +613,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
|||||||
|
|
||||||
if logentry.action_type == 'pretix.event.order.consent':
|
if logentry.action_type == 'pretix.event.order.consent':
|
||||||
return _('The user confirmed the following message: "{}"').format(
|
return _('The user confirmed the following message: "{}"').format(
|
||||||
bleach.clean(logentry.parsed_data.get('msg'), tags=[], strip=True)
|
bleach.clean(logentry.parsed_data.get('msg'), tags=set(), strip=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
if logentry.action_type == 'pretix.event.order.canceled':
|
if logentry.action_type == 'pretix.event.order.canceled':
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'active': url.url_name == 'event.settings.tickets',
|
'active': url.url_name == 'event.settings.tickets',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('E-mail'),
|
'label': _('Email'),
|
||||||
'url': reverse('control:event.settings.mail', kwargs={
|
'url': reverse('control:event.settings.mail', kwargs={
|
||||||
'event': request.event.slug,
|
'event': request.event.slug,
|
||||||
'organizer': request.event.organizer.slug,
|
'organizer': request.event.organizer.slug,
|
||||||
@@ -132,16 +132,6 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'icon': 'wrench',
|
'icon': 'wrench',
|
||||||
'children': event_settings
|
'children': event_settings
|
||||||
})
|
})
|
||||||
if request.event.has_subevents:
|
|
||||||
nav.append({
|
|
||||||
'label': pgettext_lazy('subevent', 'Dates'),
|
|
||||||
'url': reverse('control:event.subevents', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': ('event.subevent' in url.url_name),
|
|
||||||
'icon': 'calendar',
|
|
||||||
})
|
|
||||||
|
|
||||||
if 'can_change_items' in request.eventpermset:
|
if 'can_change_items' in request.eventpermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
@@ -197,6 +187,18 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if 'can_change_event_settings' in request.eventpermset:
|
||||||
|
if request.event.has_subevents:
|
||||||
|
nav.append({
|
||||||
|
'label': pgettext_lazy('subevent', 'Dates'),
|
||||||
|
'url': reverse('control:event.subevents', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': ('event.subevent' in url.url_name),
|
||||||
|
'icon': 'calendar',
|
||||||
|
})
|
||||||
|
|
||||||
if 'can_view_orders' in request.eventpermset:
|
if 'can_view_orders' in request.eventpermset:
|
||||||
children = [
|
children = [
|
||||||
{
|
{
|
||||||
@@ -496,7 +498,7 @@ def get_organizer_navigation(request):
|
|||||||
'active': url.url_name.startswith('organizer.propert'),
|
'active': url.url_name.startswith('organizer.propert'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('E-mail'),
|
'label': _('Email'),
|
||||||
'url': reverse('control:organizer.settings.mail', kwargs={
|
'url': reverse('control:organizer.settings.mail', kwargs={
|
||||||
'organizer': request.organizer.slug,
|
'organizer': request.organizer.slug,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
<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 "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 "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static "pretixbase/js/addressform.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{{ html_head|safe }}
|
{{ html_head|safe }}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
<strong>
|
<strong>
|
||||||
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=c.position.order.code %}">{{ c.position.order.code }}</a>-{{ c.position.positionid }}
|
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=c.position.order.code %}">{{ c.position.order.code }}</a>-{{ c.position.positionid }}
|
||||||
</strong>
|
</strong>
|
||||||
|
{% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=c.raw_source_type %}
|
||||||
{% if c.position.attendee_name %}
|
{% if c.position.attendee_name %}
|
||||||
<br>
|
<br>
|
||||||
<small>
|
<small>
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="fa fa-qrcode fa-fw"></span>
|
{% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=c.raw_source_type %}
|
||||||
<span title="{{ c.raw_barcode }}">
|
<span title="{{ c.raw_barcode }}">
|
||||||
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
|
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
|
||||||
<button type="button" class="btn btn-xs btn-link btn-clipboard" data-clipboard-text="{{ c.raw_barcode }}">
|
<button type="button" class="btn btn-xs btn-link btn-clipboard" data-clipboard-text="{{ c.raw_barcode }}">
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load getitem %}
|
||||||
|
|
||||||
|
{% if source_type %}
|
||||||
|
{% with media_types|getitem:source_type as media_type %}
|
||||||
|
{% if "." in media_type.icon %}
|
||||||
|
<img src="{% static media_type.icon %}" class="fa-like-image"
|
||||||
|
data-toggle="tooltip" title="{{ media_type.verbose_name }}">
|
||||||
|
{% else %}
|
||||||
|
<span class="fa fa-fw fa-{{ media_type.icon }} text-muted"
|
||||||
|
data-toggle="tooltip" title="{{ media_type.verbose_name }}"></span>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
@@ -185,6 +185,7 @@
|
|||||||
<span class="fa fa-magic text-muted"
|
<span class="fa fa-magic text-muted"
|
||||||
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
|
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% include "pretixcontrol/checkin/fragment_checkin_source_type.html" with source_type=e.last_entry_source_type %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -67,7 +67,6 @@
|
|||||||
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||||
{% bootstrap_field form.addon_match layout="control" %}
|
{% bootstrap_field form.addon_match layout="control" %}
|
||||||
{% bootstrap_field form.exit_all_at layout="control" %}
|
{% bootstrap_field form.exit_all_at layout="control" %}
|
||||||
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
|
|
||||||
{% if form.gates %}
|
{% if form.gates %}
|
||||||
{% bootstrap_field form.gates layout="control" %}
|
{% bootstrap_field form.gates layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -101,7 +101,6 @@
|
|||||||
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th class="iconcol">{% trans "Automated check-in" %}</th>
|
|
||||||
<th>{% trans "Products" %}</th>
|
<th>{% trans "Products" %}</th>
|
||||||
<th class="action-col-2"></th>
|
<th class="action-col-2"></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -137,17 +136,6 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
|
||||||
{% for channel in cl.auto_checkin_sales_channels.all %}
|
|
||||||
{% if "." in channel.icon %}
|
|
||||||
<img src="{% static channel.icon %}" class="fa-like-image"
|
|
||||||
data-toggle="tooltip" title="{{ channel.label }}">
|
|
||||||
{% else %}
|
|
||||||
<span class="fa fa-{{ channel.icon }} text-muted"
|
|
||||||
data-toggle="tooltip" title="{{ channel.label }}"></span>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{% if cl.all_products %}
|
{% if cl.all_products %}
|
||||||
<em>{% trans "All" %}</em>
|
<em>{% trans "All" %}</em>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "E-mail sending" %}</h1>
|
<h1>{% trans "Email sending" %}</h1>
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="panel-group" id="email">
|
<div class="panel-group" id="email">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="panel-body form-horizontal">
|
<div class="panel-body form-horizontal">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
E-mails will be sent through the system's default server. They will show the following
|
Emails will be sent through the system's default server. They will show the following
|
||||||
sender information:
|
sender information:
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="panel-body form-horizontal">
|
<div class="panel-body form-horizontal">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
E-mails will be sent through the system's default server but with your own sender
|
Emails will be sent through the system's default server but with your own sender
|
||||||
address.
|
address.
|
||||||
This will make your emails look more personalized and coming directly from you, but it
|
This will make your emails look more personalized and coming directly from you, but it
|
||||||
also might require some extra steps to ensure good deliverability.
|
also might require some extra steps to ensure good deliverability.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "E-mail sending" %}</h1>
|
<h1>{% trans "Email sending" %}</h1>
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for k, v in request.POST.items %}
|
{% for k, v in request.POST.items %}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "E-mail sending" %}</h1>
|
<h1>{% trans "Email sending" %}</h1>
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for k, v in request.POST.items %}
|
{% for k, v in request.POST.items %}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% load hierarkey_form %}
|
{% load hierarkey_form %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>{% trans "E-mail settings" %}</h1>
|
<h1>{% trans "Email settings" %}</h1>
|
||||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
|
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
|
||||||
mail-preview-url="{% url "control:event.settings.mail.preview" event=request.event.slug organizer=request.event.organizer.slug %}">
|
mail-preview-url="{% url "control:event.settings.mail.preview" event=request.event.slug organizer=request.event.organizer.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
{% bootstrap_field form.mail_attach_ical_description layout="control" %}
|
{% bootstrap_field form.mail_attach_ical_description layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "E-mail design" %}</legend>
|
<legend>{% trans "Email design" %}</legend>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% for r in renderers.values %}
|
{% for r in renderers.values %}
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "E-mail content" %}</legend>
|
<legend>{% trans "Email content" %}</legend>
|
||||||
<h4>{% trans "Text" %}</h4>
|
<h4>{% trans "Text" %}</h4>
|
||||||
<div class="panel-group" id="questions_group">
|
<div class="panel-group" id="questions_group">
|
||||||
{% blocktrans asvar title_placed_order %}Placed order{% endblocktrans %}
|
{% blocktrans asvar title_placed_order %}Placed order{% endblocktrans %}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<h4>{% trans "Customer data (once per order)" %}</h4>
|
<h4>{% trans "Customer data (once per order)" %}</h4>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-3">
|
<label class="control-label col-md-3">
|
||||||
{% trans "E-mail" %}
|
{% trans "Email" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
|||||||
@@ -16,8 +16,18 @@
|
|||||||
{% bootstrap_field form.internal_name layout="control" %}
|
{% bootstrap_field form.internal_name layout="control" %}
|
||||||
</div>
|
</div>
|
||||||
{% bootstrap_field form.description layout="control" %}
|
{% bootstrap_field form.description layout="control" %}
|
||||||
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-lg-9" %}
|
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-md-9" %}
|
||||||
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-lg-9" %}
|
<div class="row" data-display-dependency="#id_category_type_2">
|
||||||
|
<div class="col-md-offset-3 col-md-9">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Please note that cross-selling categories are intended as a marketing feature and are not
|
||||||
|
suitable for strictly ensuring that products are only available in certain combinations.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-md-9" %}
|
||||||
{% bootstrap_field form.cross_selling_match_products layout="control" %}
|
{% bootstrap_field form.cross_selling_match_products layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -296,11 +296,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
<div class="formset" data-formset data-formset-prefix="{{ add_formset.prefix }}">
|
<div class="formset" data-formset data-formset-prefix="{{ add_position_formset.prefix }}">
|
||||||
{{ add_formset.management_form }}
|
{{ add_position_formset.management_form }}
|
||||||
{% bootstrap_formset_errors add_formset %}
|
{% bootstrap_formset_errors add_position_formset %}
|
||||||
<div data-formset-body>
|
<div data-formset-body>
|
||||||
{% for add_form in add_formset %}
|
{% for add_form in add_position_formset %}
|
||||||
<div class="panel panel-default items" data-formset-form data-subevent="0">
|
<div class="panel panel-default items" data-formset-form data-subevent="0">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
@@ -351,25 +351,25 @@
|
|||||||
</button>
|
</button>
|
||||||
{% trans "Add product" %}
|
{% trans "Add product" %}
|
||||||
<div class="sr-only">
|
<div class="sr-only">
|
||||||
{{ add_formset.empty_form.id }}
|
{{ add_position_formset.empty_form.id }}
|
||||||
{% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
{% bootstrap_field add_position_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
{% bootstrap_field add_formset.empty_form.itemvar layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %}
|
||||||
{% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
||||||
{% if add_formset.empty_form.addon_to %}
|
{% if add_position_formset.empty_form.addon_to %}
|
||||||
{% bootstrap_field add_formset.empty_form.addon_to layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.addon_to layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if add_formset.empty_form.subevent %}
|
{% if add_position_formset.empty_form.subevent %}
|
||||||
{% bootstrap_field add_formset.empty_form.subevent layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.subevent layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if add_formset.empty_form.used_membership %}
|
{% if add_position_formset.empty_form.used_membership %}
|
||||||
{% bootstrap_field add_formset.empty_form.used_membership layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.used_membership layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_field add_formset.empty_form.seat layout="control" %}
|
{% bootstrap_field add_position_formset.empty_form.seat layout="control" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -431,13 +431,77 @@
|
|||||||
{% bootstrap_field fee.form.operation_cancel layout='inline' %}
|
{% bootstrap_field fee.form.operation_cancel layout='inline' %}
|
||||||
{% if fee.fee_type == "payment" %}
|
{% if fee.fee_type == "payment" %}
|
||||||
<em class="text-danger">
|
<em class="text-danger">
|
||||||
{% trans "Manually modifying payment fees is discouraged since they might automatically be on subsequent order changes or when choosing a different payment method." %}
|
{% trans "Manually modifying payment fees is discouraged since they might automatically be updated on subsequent order changes or when choosing a different payment method." %}
|
||||||
</em>
|
</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ add_fee_formset.prefix }}">
|
||||||
|
{{ add_fee_formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors add_fee_formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for add_form in add_fee_formset %}
|
||||||
|
<div class="panel panel-default items" data-formset-form data-subevent="0">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||||
|
data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
{% trans "Add fee" %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ add_form.id }}
|
||||||
|
{% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
{% bootstrap_field add_form.fee_type layout='control' %}
|
||||||
|
{% bootstrap_field add_form.value addon_after=request.event.currency layout='control' %}
|
||||||
|
{% bootstrap_field add_form.tax_rule layout='control' %}
|
||||||
|
{% bootstrap_field add_form.description layout='control' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default items" data-formset-form data-subevent="0">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||||
|
data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
{% trans "Add fee" %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ add_fee_formset.empty_form.id }}
|
||||||
|
{% bootstrap_field add_fee_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
{% bootstrap_field add_fee_formset.empty_form.fee_type layout='control' %}
|
||||||
|
{% bootstrap_field add_fee_formset.empty_form.value addon_after=request.event.currency layout='control' %}
|
||||||
|
{% bootstrap_field add_fee_formset.empty_form.tax_rule layout='control' %}
|
||||||
|
{% bootstrap_field add_fee_formset.empty_form.description layout='control' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endescapescript %}
|
||||||
|
</script>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-primary" data-formset-add>
|
||||||
|
<i class="fa fa-plus"></i> {% trans "Add fee" %}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default items">
|
<div class="panel panel-default items">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
|
|||||||
@@ -46,11 +46,13 @@
|
|||||||
<div id="cp{{ pos.id }}">
|
<div id="cp{{ pos.id }}">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for form in forms %}
|
{% for form in forms %}
|
||||||
{% if form.pos.item != pos.item %}
|
<div class="profile-scope">
|
||||||
{# Add-Ons #}
|
{% if form.pos.item != pos.item %}
|
||||||
<legend>+ {{ form.pos.item }}</legend>
|
{# Add-Ons #}
|
||||||
{% endif %}
|
<legend>+ {{ form.pos.item }}</legend>
|
||||||
{% bootstrap_form form layout="control" %}
|
{% endif %}
|
||||||
|
{% bootstrap_form form layout="control" %}
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -198,7 +198,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% if order.status == "n" %}
|
{% if order.status == "n" and not order.require_approval %}
|
||||||
<dt>{% trans "Expiry date" %}</dt>
|
<dt>{% trans "Expiry date" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ order.expires|date:"SHORT_DATETIME_FORMAT" }}
|
{{ order.expires|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.method == "POST" %}
|
{% if request.method == "POST" %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "E-mail preview" %}</legend>
|
<legend>{% trans "Email preview" %}</legend>
|
||||||
<div class="tab-pane mail-preview-group">
|
<div class="tab-pane mail-preview-group">
|
||||||
<div lang="{{ order.locale }}" class="mail-preview">
|
<div lang="{{ order.locale }}" class="mail-preview">
|
||||||
<strong>{{ preview_output.subject }}</strong><br><br>
|
<strong>{{ preview_output.subject }}</strong><br><br>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
{% trans "active" %}
|
{% trans "active" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
<dt>{% trans "E-mail" %}</dt>
|
<dt>{% trans "Email" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ customer.email|default_if_none:"" }}
|
{{ customer.email|default_if_none:"" }}
|
||||||
{% if customer.email and not customer.provider %}
|
{% if customer.email and not customer.provider %}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "E-mail settings" %}</h1>
|
<h1>{% trans "Email settings" %}</h1>
|
||||||
|
|
||||||
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
|
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data"
|
||||||
mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}">
|
mail-preview-url="{% url "control:organizer.settings.mail.preview" organizer=request.organizer.slug %}">
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
{% bootstrap_field form.mail_bcc layout="control" %}
|
{% bootstrap_field form.mail_bcc layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "E-mail content" %}</legend>
|
<legend>{% trans "Email content" %}</legend>
|
||||||
<div class="panel-group" id="questions_group">
|
<div class="panel-group" id="questions_group">
|
||||||
{% blocktrans asvar title_customer_registration %}Customer account registration{% endblocktrans %}
|
{% blocktrans asvar title_customer_registration %}Customer account registration{% endblocktrans %}
|
||||||
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="customer_registration" title=title_customer_registration items="mail_subject_customer_registration,mail_text_customer_registration" %}
|
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="customer_registration" title=title_customer_registration items="mail_subject_customer_registration,mail_text_customer_registration" %}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Notification type" %}</th>
|
<th>{% trans "Notification type" %}</th>
|
||||||
<th class="text-center">{% trans "E-Mail notification" %}</th>
|
<th class="text-center">{% trans "Email notification" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% trans "E-mail address" %}
|
{% trans "Email address" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'ordering' '-email' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'ordering' 'email' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ class Forgot(TemplateView):
|
|||||||
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
|
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
|
||||||
|
|
||||||
except SendMailException:
|
except SendMailException:
|
||||||
logger.exception('Sending password reset e-mail to \"' + email + '\" failed.')
|
logger.exception('Sending password reset email to \"' + email + '\" failed.')
|
||||||
|
|
||||||
except RepeatedResetDenied:
|
except RepeatedResetDenied:
|
||||||
pass
|
pass
|
||||||
@@ -354,10 +354,10 @@ class Forgot(TemplateView):
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
if has_redis:
|
if has_redis:
|
||||||
messages.info(request, _('If the address is registered to valid account, then we have sent you an e-mail containing further instructions. '
|
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions. '
|
||||||
'Please note that we will send at most one email every 24 hours.'))
|
'Please note that we will send at most one email every 24 hours.'))
|
||||||
else:
|
else:
|
||||||
messages.info(request, _('If the address is registered to valid account, then we have sent you an e-mail containing further instructions.'))
|
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
|
||||||
|
|
||||||
return redirect('control:auth.forgot')
|
return redirect('control:auth.forgot')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ from django.views.generic import FormView, ListView, TemplateView
|
|||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
|
|
||||||
from pretix.api.views.checkin import _redeem_process
|
from pretix.api.views.checkin import _redeem_process
|
||||||
|
from pretix.base.media import MEDIA_TYPES
|
||||||
from pretix.base.models import Checkin, Order, OrderPosition
|
from pretix.base.models import Checkin, Order, OrderPosition
|
||||||
from pretix.base.models.checkin import CheckinList
|
from pretix.base.models.checkin import CheckinList
|
||||||
from pretix.base.services.checkin import (
|
from pretix.base.services.checkin import (
|
||||||
@@ -81,9 +82,7 @@ class CheckInListQueryMixin:
|
|||||||
position_id=OuterRef('pk'),
|
position_id=OuterRef('pk'),
|
||||||
list_id=self.list.pk,
|
list_id=self.list.pk,
|
||||||
type=Checkin.TYPE_ENTRY
|
type=Checkin.TYPE_ENTRY
|
||||||
).order_by().values('position_id').annotate(
|
).order_by('-datetime').values('position_id')
|
||||||
m=Max('datetime')
|
|
||||||
).values('m')
|
|
||||||
cqs_exit = Checkin.objects.filter(
|
cqs_exit = Checkin.objects.filter(
|
||||||
position_id=OuterRef('pk'),
|
position_id=OuterRef('pk'),
|
||||||
list_id=self.list.pk,
|
list_id=self.list.pk,
|
||||||
@@ -103,7 +102,7 @@ class CheckInListQueryMixin:
|
|||||||
status_q,
|
status_q,
|
||||||
order__event=self.request.event,
|
order__event=self.request.event,
|
||||||
).annotate(
|
).annotate(
|
||||||
last_entry=Subquery(cqs),
|
last_entry=Subquery(cqs[:1].values('datetime')),
|
||||||
last_exit=Subquery(cqs_exit),
|
last_exit=Subquery(cqs_exit),
|
||||||
auto_checked_in=Exists(
|
auto_checked_in=Exists(
|
||||||
Checkin.objects.filter(
|
Checkin.objects.filter(
|
||||||
@@ -112,7 +111,8 @@ class CheckInListQueryMixin:
|
|||||||
list_id=self.list.pk,
|
list_id=self.list.pk,
|
||||||
auto_checked_in=True
|
auto_checked_in=True
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
last_entry_source_type=Subquery(cqs[:1].values('raw_source_type'))
|
||||||
).select_related(
|
).select_related(
|
||||||
'item', 'variation', 'order', 'addon_to'
|
'item', 'variation', 'order', 'addon_to'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
@@ -157,6 +157,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx['media_types'] = MEDIA_TYPES
|
||||||
ctx['checkinlist'] = self.list
|
ctx['checkinlist'] = self.list
|
||||||
if self.request.event.has_subevents:
|
if self.request.event.has_subevents:
|
||||||
ctx['seats'] = (
|
ctx['seats'] = (
|
||||||
@@ -299,7 +300,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related(
|
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related(
|
||||||
"limit_products", "auto_checkin_sales_channels"
|
"limit_products",
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.filter_form.is_valid():
|
if self.filter_form.is_valid():
|
||||||
@@ -497,6 +498,7 @@ class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
ctx['filter_form'] = self.filter_form
|
ctx['filter_form'] = self.filter_form
|
||||||
|
ctx['media_types'] = MEDIA_TYPES
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
|||||||
r._csp_ignore = True
|
r._csp_ignore = True
|
||||||
return r
|
return r
|
||||||
else:
|
else:
|
||||||
raise Http404(_('Unknown e-mail renderer.'))
|
raise Http404(_('Unknown email renderer.'))
|
||||||
|
|
||||||
|
|
||||||
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
|
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ class CategoryUpdate(EventPermissionRequiredMixin, UpdateView):
|
|||||||
messages.success(self.request, _('Your changes have been saved.'))
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
if form.has_changed():
|
if form.has_changed():
|
||||||
self.object.log_action(
|
self.object.log_action(
|
||||||
'pretix.event.category.reordered', user=self.request.user, data={
|
'pretix.event.category.changed', user=self.request.user, data={
|
||||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -122,10 +122,11 @@ from pretix.control.forms.filter import (
|
|||||||
)
|
)
|
||||||
from pretix.control.forms.orders import (
|
from pretix.control.forms.orders import (
|
||||||
CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm,
|
CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm,
|
||||||
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeChangeForm,
|
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeAddForm,
|
||||||
OrderLocaleForm, OrderMailForm, OrderPositionAddForm,
|
OrderFeeAddFormset, OrderFeeChangeForm, OrderLocaleForm, OrderMailForm,
|
||||||
OrderPositionAddFormset, OrderPositionChangeForm, OrderPositionMailForm,
|
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
|
||||||
OrderRefundForm, OtherOperationsForm, ReactivateOrderForm,
|
OrderPositionMailForm, OrderRefundForm, OtherOperationsForm,
|
||||||
|
ReactivateOrderForm,
|
||||||
)
|
)
|
||||||
from pretix.control.forms.rrule import RRuleForm
|
from pretix.control.forms.rrule import RRuleForm
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
@@ -1874,18 +1875,30 @@ class OrderChange(OrderView):
|
|||||||
data=self.request.POST if self.request.method == "POST" else None)
|
data=self.request.POST if self.request.method == "POST" else None)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def add_formset(self):
|
def add_position_formset(self):
|
||||||
ff = formset_factory(
|
ff = formset_factory(
|
||||||
OrderPositionAddForm, formset=OrderPositionAddFormset,
|
OrderPositionAddForm, formset=OrderPositionAddFormset,
|
||||||
can_order=False, can_delete=True, extra=0
|
can_order=False, can_delete=True, extra=0
|
||||||
)
|
)
|
||||||
return ff(
|
return ff(
|
||||||
prefix='add',
|
prefix='add_position',
|
||||||
order=self.order,
|
order=self.order,
|
||||||
items=self.items,
|
items=self.items,
|
||||||
data=self.request.POST if self.request.method == "POST" else None
|
data=self.request.POST if self.request.method == "POST" else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def add_fee_formset(self):
|
||||||
|
ff = formset_factory(
|
||||||
|
OrderFeeAddForm, formset=OrderFeeAddFormset,
|
||||||
|
can_order=False, can_delete=True, extra=0
|
||||||
|
)
|
||||||
|
return ff(
|
||||||
|
prefix='add_fee',
|
||||||
|
order=self.order,
|
||||||
|
data=self.request.POST if self.request.method == "POST" else None
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def items(self):
|
def items(self):
|
||||||
return self.request.event.items.prefetch_related('variations', 'tax_rule').all()
|
return self.request.event.items.prefetch_related('variations', 'tax_rule').all()
|
||||||
@@ -1914,7 +1927,8 @@ class OrderChange(OrderView):
|
|||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['positions'] = self.positions
|
ctx['positions'] = self.positions
|
||||||
ctx['fees'] = self.fees
|
ctx['fees'] = self.fees
|
||||||
ctx['add_formset'] = self.add_formset
|
ctx['add_position_formset'] = self.add_position_formset
|
||||||
|
ctx['add_fee_formset'] = self.add_fee_formset
|
||||||
ctx['other_form'] = self.other_form
|
ctx['other_form'] = self.other_form
|
||||||
ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list
|
ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list
|
||||||
return ctx
|
return ctx
|
||||||
@@ -1929,12 +1943,35 @@ class OrderChange(OrderView):
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_add(self, ocm):
|
def _process_add_fees(self, ocm):
|
||||||
if not self.add_formset.is_valid():
|
if not self.add_fee_formset.is_valid():
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
for f in self.add_formset.forms:
|
for f in self.add_fee_formset.forms:
|
||||||
if f in self.add_formset.deleted_forms or not f.has_changed():
|
if f in self.add_fee_formset.deleted_forms or not f.has_changed():
|
||||||
|
continue
|
||||||
|
|
||||||
|
f = OrderFee(
|
||||||
|
fee_type=f.cleaned_data['fee_type'],
|
||||||
|
value=f.cleaned_data['value'],
|
||||||
|
order=ocm.order,
|
||||||
|
tax_rule=f.cleaned_data['tax_rule'],
|
||||||
|
description=f.cleaned_data['description'],
|
||||||
|
)
|
||||||
|
f._calculate_tax()
|
||||||
|
try:
|
||||||
|
ocm.add_fee(f)
|
||||||
|
except OrderError as e:
|
||||||
|
f.custom_error = str(e)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _process_add_positions(self, ocm):
|
||||||
|
if not self.add_position_formset.is_valid():
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
for f in self.add_position_formset.forms:
|
||||||
|
if f in self.add_position_formset.deleted_forms or not f.has_changed():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if '-' in f.cleaned_data['itemvar']:
|
if '-' in f.cleaned_data['itemvar']:
|
||||||
@@ -1959,7 +1996,7 @@ class OrderChange(OrderView):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_fees(self, ocm):
|
def _process_change_fees(self, ocm):
|
||||||
for f in self.fees:
|
for f in self.fees:
|
||||||
if not f.form.is_valid():
|
if not f.form.is_valid():
|
||||||
return False
|
return False
|
||||||
@@ -1980,7 +2017,7 @@ class OrderChange(OrderView):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_change(self, ocm):
|
def _process_change_positions(self, ocm):
|
||||||
for p in self.positions:
|
for p in self.positions:
|
||||||
if not p.form.is_valid():
|
if not p.form.is_valid():
|
||||||
return False
|
return False
|
||||||
@@ -2061,7 +2098,11 @@ class OrderChange(OrderView):
|
|||||||
notify=notify,
|
notify=notify,
|
||||||
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
|
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
|
||||||
)
|
)
|
||||||
form_valid = self._process_add(ocm) and self._process_fees(ocm) and self._process_change(ocm) and self._process_other(ocm)
|
form_valid = (self._process_add_fees(ocm) and
|
||||||
|
self._process_add_positions(ocm) and
|
||||||
|
self._process_change_fees(ocm) and
|
||||||
|
self._process_change_positions(ocm) and
|
||||||
|
self._process_other(ocm))
|
||||||
|
|
||||||
if not form_valid:
|
if not form_valid:
|
||||||
messages.error(self.request, _('An error occurred. Please see the details below.'))
|
messages.error(self.request, _('An error occurred. Please see the details below.'))
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRe
|
|||||||
|
|
||||||
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
|
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
|
||||||
user=request.user)
|
user=request.user)
|
||||||
messages.success(request, _('We sent out an e-mail containing further instructions.'))
|
messages.success(request, _('We sent out an email containing further instructions.'))
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
|
|||||||
writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
|
writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
_('Name'), _('E-mail address'), _('Phone number'), _('Product'), _('On list since'), _('Status'), _('Voucher code'),
|
_('Name'), _('Email address'), _('Phone number'), _('Product'), _('On list since'), _('Status'), _('Voucher code'),
|
||||||
_('Language'), _('Priority')
|
_('Language'), _('Priority')
|
||||||
]
|
]
|
||||||
if self.request.event.has_subevents:
|
if self.request.event.has_subevents:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -622,40 +622,40 @@ msgid ""
|
|||||||
"darker shade."
|
"darker shade."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
||||||
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
||||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
@@ -656,40 +656,40 @@ msgid ""
|
|||||||
"darker shade."
|
"darker shade."
|
||||||
msgstr "تباين اللون سيئ للخلفية البيضاء، الرجاء اختيار لون غامق."
|
msgstr "تباين اللون سيئ للخلفية البيضاء، الرجاء اختيار لون غامق."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr "البحث في الاستفسارات"
|
msgstr "البحث في الاستفسارات"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "الكل"
|
msgstr "الكل"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "لا شيء"
|
msgstr "لا شيء"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr "المختارة فقط"
|
msgstr "المختارة فقط"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr "قم باستخدم اسم مختلف داخليا"
|
msgstr "قم باستخدم اسم مختلف داخليا"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr "اضغط لاغلاق الصفحة"
|
msgstr "اضغط لاغلاق الصفحة"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr "لم تقم بحفظ التعديلات!"
|
msgstr "لم تقم بحفظ التعديلات!"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
||||||
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
||||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
@@ -623,40 +623,40 @@ msgid ""
|
|||||||
"darker shade."
|
"darker shade."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2023-09-15 06:00+0000\n"
|
"PO-Revision-Date: 2023-09-15 06:00+0000\n"
|
||||||
"Last-Translator: Michael <michael.happl@gmx.at>\n"
|
"Last-Translator: Michael <michael.happl@gmx.at>\n"
|
||||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"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 "
|
"Tato barva je pro text na bílém pozadí špatně kontrastní, zvolte prosím "
|
||||||
"tmavší odstín."
|
"tmavší odstín."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr "Hledaný výraz"
|
msgstr "Hledaný výraz"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Všechny"
|
msgstr "Všechny"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Žádný"
|
msgstr "Žádný"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr "Pouze vybrané"
|
msgstr "Pouze vybrané"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr "Interně používat jiný název"
|
msgstr "Interně používat jiný název"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr "Kliknutím zavřete"
|
msgstr "Kliknutím zavřete"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr "Máte neuložené změny!"
|
msgstr "Máte neuložené změny!"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -623,40 +623,40 @@ msgid ""
|
|||||||
"darker shade."
|
"darker shade."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2024-07-10 15:00+0000\n"
|
"PO-Revision-Date: 2024-07-10 15:00+0000\n"
|
||||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
@@ -670,40 +670,40 @@ msgid ""
|
|||||||
"darker shade."
|
"darker shade."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Alle"
|
msgstr "Alle"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ingen"
|
msgstr "Ingen"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr "Klik for at lukke"
|
msgstr "Klik for at lukke"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr "Du har ændringer, der ikke er gemt!"
|
msgstr "Du har ændringer, der ikke er gemt!"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-15 11:06+0000\n"
|
"POT-Creation-Date: 2024-11-08 13:45+0000\n"
|
||||||
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"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 "
|
"Diese Farbe hat einen schlechten Kontrast für Text auf einem weißen "
|
||||||
"Hintergrund. Bitte wählen Sie eine dunklere Farbe."
|
"Hintergrund. Bitte wählen Sie eine dunklere Farbe."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:495
|
#: pretix/static/pretixcontrol/js/ui/main.js:496
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
#: pretix/static/pretixcontrol/js/ui/main.js:516
|
||||||
msgid "Search query"
|
msgid "Search query"
|
||||||
msgstr "Suchbegriff"
|
msgstr "Suchbegriff"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:513
|
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Alle"
|
msgstr "Alle"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:514
|
#: pretix/static/pretixcontrol/js/ui/main.js:515
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Keine"
|
msgstr "Keine"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:518
|
#: pretix/static/pretixcontrol/js/ui/main.js:519
|
||||||
msgid "Selected only"
|
msgid "Selected only"
|
||||||
msgstr "Nur ausgewählte"
|
msgstr "Nur ausgewählte"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:861
|
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||||
msgid "Enter page number between 1 and %(max)s."
|
msgid "Enter page number between 1 and %(max)s."
|
||||||
msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein."
|
msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:864
|
#: pretix/static/pretixcontrol/js/ui/main.js:865
|
||||||
msgid "Invalid page number."
|
msgid "Invalid page number."
|
||||||
msgstr "Ungültige Seitenzahl."
|
msgstr "Ungültige Seitenzahl."
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1022
|
#: pretix/static/pretixcontrol/js/ui/main.js:1023
|
||||||
msgid "Use a different name internally"
|
msgid "Use a different name internally"
|
||||||
msgstr "Intern einen anderen Namen verwenden"
|
msgstr "Intern einen anderen Namen verwenden"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1062
|
#: pretix/static/pretixcontrol/js/ui/main.js:1063
|
||||||
msgid "Click to close"
|
msgid "Click to close"
|
||||||
msgstr "Klicken zum Schließen"
|
msgstr "Klicken zum Schließen"
|
||||||
|
|
||||||
#: pretix/static/pretixcontrol/js/ui/main.js:1137
|
#: pretix/static/pretixcontrol/js/ui/main.js:1138
|
||||||
msgid "You have unsaved changes!"
|
msgid "You have unsaved changes!"
|
||||||
msgstr "Sie haben ungespeicherte Änderungen!"
|
msgstr "Sie haben ungespeicherte Änderungen!"
|
||||||
|
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ Mitgliedschafts
|
|||||||
Mitgliedschaftsdauer
|
Mitgliedschaftsdauer
|
||||||
min
|
min
|
||||||
Mo
|
Mo
|
||||||
|
MobilePay
|
||||||
MOTO
|
MOTO
|
||||||
Multibanco
|
Multibanco
|
||||||
MwSt
|
MwSt
|
||||||
|
|||||||
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