forked from CGM_Public/pretix_original
Compare commits
3 Commits
checkin-pr
...
pluggable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f724fa780 | ||
|
|
118f61292b | ||
|
|
458a22f6a3 |
@@ -100,3 +100,7 @@ API
|
||||
.. automodule:: pretix.base.signals
|
||||
:no-index:
|
||||
:members: validate_event_settings, api_event_settings_fields
|
||||
|
||||
.. automodule:: pretix.api.signals
|
||||
:no-index:
|
||||
:members: register_device_security_profile
|
||||
|
||||
@@ -117,7 +117,7 @@ dev = [
|
||||
"isort==5.13.*",
|
||||
"pep8-naming==0.14.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=0.24",
|
||||
"pytest-asyncio",
|
||||
"pytest-cache",
|
||||
"pytest-cov",
|
||||
"pytest-django==4.*",
|
||||
|
||||
@@ -27,7 +27,7 @@ from rest_framework import exceptions
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
|
||||
from pretix.api.auth.devicesecurity import (
|
||||
DEVICE_SECURITY_PROFILES, FullAccessSecurityProfile,
|
||||
FullAccessSecurityProfile, get_all_security_profiles,
|
||||
)
|
||||
from pretix.base.models import Device
|
||||
|
||||
@@ -58,7 +58,8 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
||||
def authenticate(self, request):
|
||||
r = super().authenticate(request)
|
||||
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):
|
||||
raise exceptions.PermissionDenied('Request denied by device security profile.')
|
||||
return r
|
||||
|
||||
@@ -20,13 +20,40 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.api.signals import register_device_security_profile
|
||||
|
||||
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'
|
||||
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
|
||||
|
||||
|
||||
class AllowListSecurityProfile:
|
||||
class AllowListSecurityProfile(BaseSecurityProfile):
|
||||
allowlist = ()
|
||||
|
||||
def is_allowed(self, request):
|
||||
@@ -157,88 +184,28 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
||||
)
|
||||
|
||||
|
||||
class PretixPosSecurityProfile(AllowListSecurityProfile):
|
||||
identifier = 'pretixpos'
|
||||
verbose_name = _('pretixPOS')
|
||||
allowlist = (
|
||||
('GET', 'api-v1:version'),
|
||||
('GET', 'api-v1:device.eventselection'),
|
||||
('GET', 'api-v1:idempotency.query'),
|
||||
('GET', 'api-v1:device.info'),
|
||||
('POST', 'api-v1:device.update'),
|
||||
('POST', 'api-v1:device.revoke'),
|
||||
('POST', 'api-v1:device.roll'),
|
||||
('GET', 'api-v1:event-list'),
|
||||
('GET', 'api-v1:event-detail'),
|
||||
('GET', 'api-v1:subevent-list'),
|
||||
('GET', 'api-v1:subevent-detail'),
|
||||
('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'),
|
||||
)
|
||||
def get_all_security_profiles():
|
||||
global _ALL_PROFILES
|
||||
|
||||
if _ALL_PROFILES:
|
||||
return _ALL_PROFILES
|
||||
|
||||
types = OrderedDict()
|
||||
for recv, ret in register_device_security_profile.send(None):
|
||||
if isinstance(ret, (list, tuple)):
|
||||
for r in ret:
|
||||
types[r.identifier] = r
|
||||
else:
|
||||
types[ret.identifier] = ret
|
||||
_ALL_PROFILES = types
|
||||
return types
|
||||
|
||||
|
||||
DEVICE_SECURITY_PROFILES = {
|
||||
k.identifier: k() for k in (
|
||||
FullAccessSecurityProfile,
|
||||
PretixScanSecurityProfile,
|
||||
PretixScanNoSyncSecurityProfile,
|
||||
PretixScanNoSyncNoSearchSecurityProfile,
|
||||
PretixPosSecurityProfile,
|
||||
@receiver(register_device_security_profile, dispatch_uid="base_register_default_security_profiles")
|
||||
def register_default_webhook_events(sender, **kwargs):
|
||||
return (
|
||||
FullAccessSecurityProfile(),
|
||||
PretixScanSecurityProfile(),
|
||||
PretixScanNoSyncSecurityProfile(),
|
||||
PretixScanNoSyncNoSearchSecurityProfile(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
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.i18n import I18nAwareModelSerializer
|
||||
from pretix.api.serializers.order import CompatibleJSONField
|
||||
@@ -297,6 +298,7 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
revoked = serializers.BooleanField(read_only=True)
|
||||
initialized = serializers.DateTimeField(read_only=True)
|
||||
initialization_token = serializers.DateTimeField(read_only=True)
|
||||
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
@@ -306,6 +308,10 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
'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 Meta:
|
||||
|
||||
@@ -32,10 +32,17 @@ from pretix.helpers.periodic import minimum_interval
|
||||
register_webhook_events = Signal()
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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)
|
||||
@scopes_disabled()
|
||||
|
||||
@@ -141,7 +141,7 @@ class CheckinList(LoggedModel):
|
||||
return self.positions_query(ignore_status=False)
|
||||
|
||||
@scopes_disabled()
|
||||
def _filter_positions_inside(self, qs, at_time=None):
|
||||
def positions_inside_query(self, ignore_status=False, at_time=None):
|
||||
if at_time is None:
|
||||
c_q = []
|
||||
else:
|
||||
@@ -149,7 +149,7 @@ class CheckinList(LoggedModel):
|
||||
|
||||
if "postgresql" not in settings.DATABASES["default"]["ENGINE"]:
|
||||
# Use a simple approach that works on all databases
|
||||
qs = qs.annotate(
|
||||
qs = self.positions_query(ignore_status=ignore_status).annotate(
|
||||
last_entry=Subquery(
|
||||
Checkin.objects.filter(
|
||||
*c_q,
|
||||
@@ -202,7 +202,7 @@ class CheckinList(LoggedModel):
|
||||
.values("position_id", "type", "datetime", "cnt_exists_after")
|
||||
.query.sql_with_params()
|
||||
)
|
||||
return qs.filter(
|
||||
return self.positions_query(ignore_status=ignore_status).filter(
|
||||
pk__in=RawSQL(
|
||||
f"""
|
||||
SELECT "position_id"
|
||||
@@ -214,10 +214,6 @@ 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
|
||||
def positions_inside(self):
|
||||
return self.positions_inside_query(None)
|
||||
|
||||
@@ -28,7 +28,6 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
|
||||
from pretix.api.auth.devicesecurity import DEVICE_SECURITY_PROFILES
|
||||
from pretix.base.models import LoggedModel
|
||||
|
||||
|
||||
@@ -161,7 +160,6 @@ class Device(LoggedModel):
|
||||
)
|
||||
security_profile = models.CharField(
|
||||
max_length=190,
|
||||
choices=[(k, v.verbose_name) for k, v in DEVICE_SECURITY_PROFILES.items()],
|
||||
default='full',
|
||||
null=True,
|
||||
blank=False
|
||||
|
||||
@@ -1967,7 +1967,7 @@ class CheckinListAttendeeFilterForm(FilterForm):
|
||||
if s == '1':
|
||||
qs = qs.filter(last_entry__isnull=False)
|
||||
elif s == '2':
|
||||
qs = self.list._filter_positions_inside(qs)
|
||||
qs = qs.filter(pk__in=self.list.positions_inside.values_list('pk'))
|
||||
elif s == '3':
|
||||
qs = qs.filter(last_entry__isnull=False).filter(
|
||||
Q(last_exit__isnull=False) & Q(last_exit__gte=F('last_entry'))
|
||||
|
||||
@@ -54,6 +54,7 @@ from i18nfield.strings import LazyI18nString
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.api.auth.devicesecurity import get_all_security_profiles
|
||||
from pretix.api.models import WebHook
|
||||
from pretix.api.webhooks import get_all_webhook_events
|
||||
from pretix.base.customersso.oidc import oidc_validate_and_complete_config
|
||||
@@ -311,6 +312,11 @@ class DeviceForm(forms.ModelForm):
|
||||
'-has_subevents', '-date_from'
|
||||
)
|
||||
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):
|
||||
d = super().clean()
|
||||
@@ -344,6 +350,11 @@ class DeviceBulkEditForm(forms.ModelForm):
|
||||
'-has_subevents', '-date_from'
|
||||
)
|
||||
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):
|
||||
d = super().clean()
|
||||
|
||||
@@ -16,18 +16,8 @@
|
||||
{% bootstrap_field form.internal_name layout="control" %}
|
||||
</div>
|
||||
{% bootstrap_field form.description layout="control" %}
|
||||
{% bootstrap_field form.category_type layout="control" horizontal_field_class="big-radio-wrapper col-md-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.category_type layout="control" horizontal_field_class="big-radio-wrapper col-lg-9" %}
|
||||
{% bootstrap_field form.cross_selling_condition layout="control" horizontal_field_class="col-lg-9" %}
|
||||
{% bootstrap_field form.cross_selling_match_products layout="control" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-10-29 08:53+0000\n"
|
||||
"PO-Revision-Date: 2024-10-29 21:00+0000\n"
|
||||
"PO-Revision-Date: 2024-09-27 18:00+0000\n"
|
||||
"Last-Translator: Anarion Dunedain <anarion80@gmail.com>\n"
|
||||
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix/pl/"
|
||||
">\n"
|
||||
@@ -18,7 +18,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.8.1\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -4606,40 +4606,43 @@ msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:114 pretix/base/models/items.py:159
|
||||
#: pretix/control/forms/item.py:99
|
||||
#, fuzzy
|
||||
#| msgid "No category"
|
||||
msgid "Normal category"
|
||||
msgstr "Normalna kategoria"
|
||||
msgstr "Brak kategorii"
|
||||
|
||||
#: pretix/base/models/items.py:115 pretix/control/forms/item.py:112
|
||||
msgid "Normal + cross-selling category"
|
||||
msgstr "Normalna + sprzedaż krzyżowa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:116 pretix/control/forms/item.py:107
|
||||
#, fuzzy
|
||||
#| msgid "No category"
|
||||
msgid "Cross-selling category"
|
||||
msgstr "Kategoria sprzedaży krzyżowej"
|
||||
msgstr "Brak kategorii"
|
||||
|
||||
#: pretix/base/models/items.py:124
|
||||
msgid "Always show in cross-selling step"
|
||||
msgstr "Zawsze pokazuj w kroku sprzedaży krzyżowej"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:125
|
||||
msgid ""
|
||||
"Only show products that qualify for a discount according to discount rules"
|
||||
msgstr ""
|
||||
"Pokaż tylko produkty kwalifikujące się do zniżki zgodnie z zasadami "
|
||||
"rabatowymi"
|
||||
|
||||
#: pretix/base/models/items.py:126
|
||||
msgid "Only show if the cart contains one of the following products"
|
||||
msgstr ""
|
||||
"Pokaż tylko, jeśli w koszyku znajduje się jeden z następujących produktów"
|
||||
|
||||
#: pretix/base/models/items.py:129
|
||||
msgid "Cross-selling condition"
|
||||
msgstr "Warunek sprzedaży krzyżowej"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:137
|
||||
#, fuzzy
|
||||
#| msgid "Count add-on products"
|
||||
msgid "Cross-selling condition products"
|
||||
msgstr "Produkty warunkowej sprzedaży krzyżowej"
|
||||
msgstr "Policz produkty dodatkowe"
|
||||
|
||||
#: pretix/base/models/items.py:143
|
||||
#: pretix/control/templates/pretixcontrol/items/categories.html:3
|
||||
@@ -4651,11 +4654,13 @@ msgstr "Kategorie produktów"
|
||||
#: pretix/base/models/items.py:149
|
||||
#, python-brace-format
|
||||
msgid "{category} ({category_type})"
|
||||
msgstr "{category} ({category_type})"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/items.py:155
|
||||
#, fuzzy
|
||||
#| msgid "No category"
|
||||
msgid "Add-on category"
|
||||
msgstr "Kategoria dodatków"
|
||||
msgstr "Brak kategorii"
|
||||
|
||||
#: pretix/base/models/items.py:222 pretix/base/models/items.py:278
|
||||
msgid "Disable product for this date"
|
||||
@@ -6018,8 +6023,10 @@ msgid "Ticket"
|
||||
msgstr "Bilet"
|
||||
|
||||
#: pretix/base/models/orders.py:3405
|
||||
#, fuzzy
|
||||
#| msgid "Verification"
|
||||
msgid "Certificate"
|
||||
msgstr "Certyfikat"
|
||||
msgstr "Weryfikacja"
|
||||
|
||||
#: pretix/base/models/orders.py:3406 pretix/control/views/event.py:367
|
||||
#: pretix/control/views/event.py:372
|
||||
@@ -6185,13 +6192,11 @@ msgstr ""
|
||||
#, python-brace-format
|
||||
msgid "Seat with zone {zone}, row {row}, and number {number} has no seat ID."
|
||||
msgstr ""
|
||||
"Miejsce w strefie {zone}, rzędzie {row} i numerze {number} nie ma "
|
||||
"identyfikatora miejsca."
|
||||
|
||||
#: pretix/base/models/seating.py:71
|
||||
#, python-brace-format
|
||||
msgid "Multiple seats have the same ID: {id}"
|
||||
msgstr "Wiele miejsc ma ten sam identyfikator: {id}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/seating.py:199
|
||||
#, python-brace-format
|
||||
@@ -9039,8 +9044,10 @@ msgid "Ask for beneficiary"
|
||||
msgstr "Zapytaj o beneficjenta"
|
||||
|
||||
#: pretix/base/settings.py:574
|
||||
#, fuzzy
|
||||
#| msgid "Custom recipient field"
|
||||
msgid "Custom recipient field label"
|
||||
msgstr "Etykieta pola niestandardowego odbiorcy"
|
||||
msgstr "Niestandardowe pole odbiorcy"
|
||||
|
||||
#: pretix/base/settings.py:576
|
||||
msgid ""
|
||||
@@ -9057,8 +9064,10 @@ msgstr ""
|
||||
"ona wyświetlana na fakturze poniżej nagłówka. Pole nie będzie wymagane."
|
||||
|
||||
#: pretix/base/settings.py:589
|
||||
#, fuzzy
|
||||
#| msgid "Custom recipient field"
|
||||
msgid "Custom recipient field help text"
|
||||
msgstr "Tekst pomocy dotyczący pola odbiorcy niestandardowego"
|
||||
msgstr "Niestandardowe pole odbiorcy"
|
||||
|
||||
#: pretix/base/settings.py:591
|
||||
msgid ""
|
||||
@@ -9066,8 +9075,6 @@ msgid ""
|
||||
"will be displayed underneath the field. It will not be displayed on the "
|
||||
"invoice."
|
||||
msgstr ""
|
||||
"Jeśli używasz niestandardowego pola odbiorcy, możesz określić tekst pomocy, "
|
||||
"który będzie wyświetlany pod polem. Nie będzie on wyświetlany na fakturze."
|
||||
|
||||
#: pretix/base/settings.py:601
|
||||
msgid "Ask for VAT ID"
|
||||
@@ -12505,9 +12512,12 @@ msgstr ""
|
||||
"zakończenia przedsprzedaży"
|
||||
|
||||
#: pretix/base/timeline.py:106
|
||||
#, fuzzy
|
||||
#| msgctxt "timeline"
|
||||
#| msgid "Customers can no longer modify their orders"
|
||||
msgctxt "timeline"
|
||||
msgid "Customers can no longer modify their order information"
|
||||
msgstr "Klienci nie mogą już modyfikować swojego zamówienia"
|
||||
msgstr "Klienci nie mogą już modyfikować swoich zamówień"
|
||||
|
||||
#: pretix/base/timeline.py:119
|
||||
msgctxt "timeline"
|
||||
@@ -12530,6 +12540,9 @@ msgid "Customers can no longer cancel paid orders"
|
||||
msgstr "Klienci nie mogą już anulować opłaconych zamówień"
|
||||
|
||||
#: pretix/base/timeline.py:167
|
||||
#, fuzzy
|
||||
#| msgctxt "timeline"
|
||||
#| msgid "Customers can no longer modify their orders"
|
||||
msgctxt "timeline"
|
||||
msgid "Customers can no longer make changes to their orders"
|
||||
msgstr "Klienci nie mogą już modyfikować swoich zamówień"
|
||||
@@ -14075,30 +14088,31 @@ msgstr ""
|
||||
"wtyczki. Będzie on publicznie dostępny. Upewnij się, że jest on aktualny!"
|
||||
|
||||
#: pretix/control/forms/item.py:100
|
||||
#, fuzzy
|
||||
#| msgid "Products in this category are add-on products"
|
||||
msgid ""
|
||||
"Products in this category are regular products displayed on the front page."
|
||||
msgstr ""
|
||||
"Produkty w tej kategorii to zwykłe produkty wyświetlane na stronie głównej."
|
||||
msgstr "Produkty w tej kategorii są dodatkami"
|
||||
|
||||
#: pretix/control/forms/item.py:103
|
||||
#, fuzzy
|
||||
#| msgid "Product category"
|
||||
msgid "Add-on product category"
|
||||
msgstr "Kategoria produktu dodatkowego"
|
||||
msgstr "Kategoria produku"
|
||||
|
||||
#: pretix/control/forms/item.py:104
|
||||
#, fuzzy
|
||||
#| msgid "Products in this category are add-on products"
|
||||
msgid ""
|
||||
"Products in this category are add-on products and can only be bought as add-"
|
||||
"ons."
|
||||
msgstr ""
|
||||
"Produkty w tej kategorii są produktami dodatkowymi i można je kupić "
|
||||
"wyłącznie jako dodatki."
|
||||
msgstr "Produkty w tej kategorii są dodatkami"
|
||||
|
||||
#: pretix/control/forms/item.py:108
|
||||
msgid ""
|
||||
"Products in this category are regular products, but are only shown in the "
|
||||
"cross-selling step, according to the configuration below."
|
||||
msgstr ""
|
||||
"Produkty w tej kategorii to zwykłe produkty, ale są wyświetlane tylko na "
|
||||
"etapie sprzedaży krzyżowej, zgodnie z konfiguracją poniżej."
|
||||
|
||||
#: pretix/control/forms/item.py:113
|
||||
msgid ""
|
||||
@@ -14106,9 +14120,6 @@ msgid ""
|
||||
"but are additionally shown in the cross-selling step, according to the "
|
||||
"configuration below."
|
||||
msgstr ""
|
||||
"Produkty w tej kategorii to zwykłe produkty wyświetlane na stronie głównej, "
|
||||
"ale są dodatkowo pokazywane na etapie sprzedaży krzyżowej, zgodnie z "
|
||||
"poniższą konfiguracją."
|
||||
|
||||
#: pretix/control/forms/item.py:141 pretix/control/forms/item.py:211
|
||||
msgid "This field is required"
|
||||
@@ -16526,9 +16537,12 @@ msgid "The check-in of position #{posid} on list \"{list}\" has been reverted."
|
||||
msgstr "Zameldowanie pozycji #{posid} na liście \"{list}\" zostało cofnięte."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid ""
|
||||
#| "Position #{posid} has been checked in at {datetime} for list \"{list}\"."
|
||||
msgid "Position #{posid} has been printed at {datetime} with type \"{type}\"."
|
||||
msgstr "Pozycja #{posid} została wydrukowana o {datetime} z typem „{type}”."
|
||||
msgstr ""
|
||||
"Pozycja #{posid} została zameldowana w {datetime} dla listy \"{list}\"."
|
||||
|
||||
#: pretix/control/logdisplay.py:667
|
||||
#, python-brace-format
|
||||
@@ -17214,9 +17228,6 @@ msgid ""
|
||||
"check that you have completed all installation steps and your cronjob is "
|
||||
"executed correctly."
|
||||
msgstr ""
|
||||
"Komponent cronjob pretix nie został uruchomiony w ciągu ostatnich godzin. "
|
||||
"Sprawdź, czy ukończyłeś wszystkie kroki instalacji i czy twój cronjob został "
|
||||
"wykonany poprawnie."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/base.html:435
|
||||
msgid ""
|
||||
@@ -20068,8 +20079,10 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/base.html:24
|
||||
#: pretix/control/templates/pretixcontrol/item/include_variations.html:79
|
||||
#, fuzzy
|
||||
#| msgid "Manage questions"
|
||||
msgid "Manage quotas"
|
||||
msgstr "Zarządzaj pulami"
|
||||
msgstr "Zarządzaj pytaniami"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/base.html:27
|
||||
#: pretix/control/templates/pretixcontrol/item/include_variations.html:82
|
||||
@@ -20442,8 +20455,10 @@ msgid "Create a new category"
|
||||
msgstr "Utwórz nową kategorię"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/categories.html:34
|
||||
#, fuzzy
|
||||
#| msgid "Category name"
|
||||
msgid "Category type"
|
||||
msgstr "Typ kategorii"
|
||||
msgstr "Nazwa kategorii"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/categories.html:48
|
||||
#: pretix/control/templates/pretixcontrol/items/discounts.html:138
|
||||
@@ -25377,8 +25392,6 @@ msgid ""
|
||||
"According to your event settings, sold out products are hidden from "
|
||||
"customers. This way, customers will not be able to discover the waiting list."
|
||||
msgstr ""
|
||||
"Zgodnie z ustawieniami wydarzenia, wyprzedane produkty są ukryte przed "
|
||||
"klientami. W ten sposób klienci nie będą mogli znaleźć listy oczekujących."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:36
|
||||
msgid "Send vouchers"
|
||||
@@ -31806,25 +31819,28 @@ msgid ""
|
||||
"A product in your cart is only sold in combination with add-on products that "
|
||||
"are no longer available. Please contact the event organizer."
|
||||
msgstr ""
|
||||
"Produkt w Twoim koszyku jest sprzedawany tylko w połączeniu z produktami "
|
||||
"dodatkowymi, które nie są już dostępne. Skontaktuj się z organizatorem "
|
||||
"wydarzenia."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:20
|
||||
msgid "We're now trying to book these add-ons for you!"
|
||||
msgstr "Staramy się teraz zarezerwować te dodatki dla Ciebie!"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:28
|
||||
#, fuzzy
|
||||
#| msgid "Additional settings"
|
||||
msgid "Additional options for"
|
||||
msgstr "Dodatkowe opcje dla"
|
||||
msgstr "Dodatkowe ustawienia"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:64
|
||||
#, fuzzy
|
||||
#| msgid "Top recommendation"
|
||||
msgid "More recommendations"
|
||||
msgstr "Więcej rekomendacji"
|
||||
msgstr "Najlepsza rekomendacja"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:71
|
||||
#, fuzzy
|
||||
#| msgid "Top recommendation"
|
||||
msgid "Our recommendations"
|
||||
msgstr "Nasze rekomendacje"
|
||||
msgstr "Najlepsza rekomendacja"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:89
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:201
|
||||
@@ -32830,8 +32846,10 @@ msgid "Payment pending"
|
||||
msgstr "W toku płatności"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:19
|
||||
#, fuzzy
|
||||
#| msgid "Your orders for {event}"
|
||||
msgid "Your order qualifies for a discount"
|
||||
msgstr "Twoje zamówienia kwalifikuje się do zniżki"
|
||||
msgstr "Twoje zamówienia na {event}"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:28
|
||||
#: pretix/presale/templates/pretixpresale/event/voucher.html:78
|
||||
@@ -34372,8 +34390,6 @@ msgid ""
|
||||
"No ticket types are available for the waiting list, have a look at the "
|
||||
"ticket shop instead."
|
||||
msgstr ""
|
||||
"Nie ma dostępnych typów biletów dla listy oczekujących, zamiast tego należy "
|
||||
"udać się do sklepu z biletami."
|
||||
|
||||
#: pretix/presale/views/waiting.py:137 pretix/presale/views/waiting.py:161
|
||||
msgid "Waiting lists are disabled for this event."
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-10-29 08:53+0000\n"
|
||||
"PO-Revision-Date: 2024-10-30 19:00+0000\n"
|
||||
"PO-Revision-Date: 2024-09-18 14:02+0000\n"
|
||||
"Last-Translator: Tinna Sandström <tinna@coeo.events>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"sv/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.8.1\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -22131,7 +22131,7 @@ msgid ""
|
||||
"After starting this operation, depending on the size of your event, it might "
|
||||
"take a few minutes or longer until all orders are processed."
|
||||
msgstr ""
|
||||
"Efter att du har startat denna process kan det, beroende på storleken på "
|
||||
"Efter att du har startat denna operation kan det, beroende på storleken på "
|
||||
"ditt evenemang, ta några minuter eller längre tid innan alla bokningar är "
|
||||
"behandlade."
|
||||
|
||||
@@ -31866,7 +31866,7 @@ msgstr "Ändra kontaktinformation"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:167
|
||||
msgid "Confirmations"
|
||||
msgstr "Villkor"
|
||||
msgstr "Bekräftelser"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:185
|
||||
msgid ""
|
||||
|
||||
@@ -462,28 +462,14 @@ class BadgeExporter(BaseExporter):
|
||||
)),
|
||||
('date_from',
|
||||
forms.DateField(
|
||||
label=_('Start event date'),
|
||||
label=_('Start date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include tickets for dates on or after this date.')
|
||||
)),
|
||||
('date_to',
|
||||
forms.DateField(
|
||||
label=_('End event date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include tickets ordered on or before this date.')
|
||||
)),
|
||||
('order_date_from',
|
||||
forms.DateField(
|
||||
label=_('Start order date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include tickets ordered on or after this date.')
|
||||
)),
|
||||
('order_date_to',
|
||||
forms.DateField(
|
||||
label=_('End order date'),
|
||||
label=_('End date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include tickets for dates on or before this date.')
|
||||
@@ -495,7 +481,6 @@ class BadgeExporter(BaseExporter):
|
||||
('name', _('Attendee name')),
|
||||
('company', _('Attendee company')),
|
||||
('code', _('Order code')),
|
||||
('order_date', _('Order date')),
|
||||
('date', _('Event date')),
|
||||
] + ([
|
||||
('name:{}'.format(k), _('Attendee name: {part}').format(part=label))
|
||||
@@ -548,24 +533,6 @@ class BadgeExporter(BaseExporter):
|
||||
), self.event.timezone)
|
||||
qs = qs.filter(Q(subevent__date_from__lt=dt) | Q(subevent__isnull=True, order__event__date_from__lt=dt))
|
||||
|
||||
if form_data.get('order_date_from'):
|
||||
if not isinstance(form_data.get('order_date_from'), date):
|
||||
form_data['order_date_from'] = dateutil.parser.parse(form_data['order_date_from']).date()
|
||||
df = make_aware(datetime.combine(
|
||||
form_data['order_date_from'],
|
||||
time(hour=0, minute=0, second=0)
|
||||
), self.event.timezone)
|
||||
qs = qs.filter(order__datetime__gte=df)
|
||||
|
||||
if form_data.get('order_date_to'):
|
||||
if not isinstance(form_data.get('order_date_to'), date):
|
||||
form_data['order_date_to'] = dateutil.parser.parse(form_data['order_date_to']).date()
|
||||
dt = make_aware(datetime.combine(
|
||||
form_data['order_date_to'] + timedelta(days=1),
|
||||
time(hour=0, minute=0, second=0)
|
||||
), self.event.timezone)
|
||||
qs = qs.filter(order__datetime__lt=dt)
|
||||
|
||||
if form_data.get('order_by') == 'name':
|
||||
qs = qs.annotate(
|
||||
resolved_name=Case(
|
||||
@@ -586,8 +553,6 @@ class BadgeExporter(BaseExporter):
|
||||
).order_by('resolved_company', 'order__code')
|
||||
elif form_data.get('order_by') == 'code':
|
||||
qs = qs.order_by('order__code')
|
||||
elif form_data.get('order_by') == 'order_date':
|
||||
qs = qs.order_by('order__datetime')
|
||||
elif form_data.get('order_by') == 'date':
|
||||
qs = qs.annotate(ed=Coalesce('subevent__date_from', 'order__event__date_from')).order_by('ed', 'order__code')
|
||||
elif form_data.get('order_by', '').startswith('name:'):
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import pgettext
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.models import EventMetaValue, SubEventMetaValue
|
||||
@@ -67,7 +66,7 @@ def meta_filtersets(organizer, event=None):
|
||||
).values_list("value", flat=True).distinct())
|
||||
choices = [(k, k) for k in sorted(existing_values)]
|
||||
|
||||
choices.insert(0, ("", "-- %s --" % pgettext("filter_empty", "all")))
|
||||
choices.insert(0, ("", ""))
|
||||
if len(choices) > 1:
|
||||
fields[f"attr[{prop.name}]"] = {
|
||||
"label": str(prop.public_label) or prop.name,
|
||||
|
||||
@@ -40,7 +40,7 @@ def mocker_context():
|
||||
|
||||
def get_redis_connection(alias="default", write=True):
|
||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
||||
if worker_id and worker_id.startswith("gw"):
|
||||
if worker_id.startswith("gw"):
|
||||
redis_port = 1000 + int(worker_id.replace("gw", ""))
|
||||
else:
|
||||
redis_port = 1000
|
||||
|
||||
@@ -228,7 +228,6 @@ def checkin_list_env():
|
||||
# checkin
|
||||
Checkin.objects.create(position=op_a1_ticket, datetime=now() + timedelta(minutes=1), list=cl)
|
||||
Checkin.objects.create(position=op_a3_ticket, list=cl)
|
||||
Checkin.objects.create(position=op_a3_ticket, list=cl, type="exit")
|
||||
|
||||
return event, user, orga, [item_ticket, item_mascot], [order_pending, order_a1, order_a2, order_a3], \
|
||||
[op_pending_ticket, op_a1_ticket, op_a1_mascot, op_a2_ticket, op_a3_ticket], cl
|
||||
@@ -261,10 +260,8 @@ def test_checkins_list_ordering(client, checkin_list_env, order_key, expected):
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("query, expected", [
|
||||
('status=&item=&user=', ['A1Ticket', 'A1Mascot', 'A2Ticket', 'A3Ticket']),
|
||||
('status=0&item=&user=', ['A1Mascot', 'A2Ticket']),
|
||||
('status=1&item=&user=', ['A1Ticket', 'A3Ticket']),
|
||||
('status=2&item=&user=', ['A1Ticket']),
|
||||
('status=3&item=&user=', ['A3Ticket']),
|
||||
('status=0&item=&user=', ['A1Mascot', 'A2Ticket']),
|
||||
('status=&item=&user=a3dummy', ['A3Ticket']), # match order email
|
||||
('status=&item=&user=a3dummy', ['A3Ticket']), # match order email,
|
||||
('status=&item=&user=a4', ['A3Ticket']), # match attendee name
|
||||
|
||||
@@ -812,7 +812,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
data = json.loads(response.content.decode())
|
||||
assert data["meta_filter_fields"] == [
|
||||
{
|
||||
"choices": [["", "-- all --"], ["EN", "English"], ["DE", "German"]],
|
||||
"choices": [["", ""], ["EN", "English"], ["DE", "German"]],
|
||||
"key": "attr[Language]",
|
||||
"label": "Language"
|
||||
}
|
||||
@@ -838,7 +838,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
data = json.loads(response.content.decode())
|
||||
assert data["meta_filter_fields"] == [
|
||||
{
|
||||
"choices": [["", "-- all --"], ["DE", "DE"], ["EN", "EN"]],
|
||||
"choices": [["", ""], ["DE", "DE"], ["EN", "EN"]],
|
||||
"key": "attr[Language]",
|
||||
"label": "Language"
|
||||
}
|
||||
@@ -848,7 +848,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
data = json.loads(response.content.decode())
|
||||
assert data["meta_filter_fields"] == [
|
||||
{
|
||||
"choices": [["", "-- all --"], ["DE", "DE"]],
|
||||
"choices": [["", ""], ["DE", "DE"]],
|
||||
"key": "attr[Language]",
|
||||
"label": "Language"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user