Compare commits

...

29 Commits

Author SHA1 Message Date
Mira Weller
43b9049a12 Fix test cases for checkinlist export 2024-07-12 10:53:37 +02:00
Mira
7b16dfefbc New column and sort options for check-in-list export (#4300)
Allow check-in-list export to by sorted by order datetime (Z#23158159)
Add check-in text to export
2024-07-12 10:37:22 +02:00
Richard Schreiber
8a5b13dee9 Waitinglist: show time for subevent in overview 2024-07-09 15:37:51 +02:00
Richard Schreiber
d7dde8c23e Fix CSS-color in alert-danger icon 2024-07-09 10:17:20 +02:00
Richard Schreiber
1e2f93fbc5 Waitinglist: add time to subevent dropdown when sending vouchers (#4296) 2024-07-09 09:28:19 +02:00
Richard Schreiber
493fc03686 Fix PayPal CSP img-src 2024-07-09 09:26:46 +02:00
Raphael Michel
8d6d885f6e API: Add various filtering options
* API: Add various filtering options (#23158339)

* fix query-param description

* simplify payment provider qs-filter

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2024-07-08 21:31:54 +02:00
Martin Gross
2aa989c293 PPv2: Do not fail hard on refunds that are based on manually confirmed payments (Fixes: #PRETIXEU-AC1) 2024-07-04 15:12:53 +02:00
dependabot[bot]
06b226f40f Update pillow requirement from ==10.3.* to ==10.4.* (#4280)
Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.3.0...10.4.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-04 14:46:46 +02:00
Raphael Michel
73038b0d97 Fix enforcement of restricted plugins (#4286) 2024-07-03 17:14:03 +02:00
Erik Löfman
4513e31f0d Translations: Update Swedish
Currently translated at 63.0% (3585 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/sv/

powered by weblate
2024-07-03 17:05:45 +02:00
Raphael Michel
d34175114b Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5690 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2024-07-03 17:05:45 +02:00
Raphael Michel
b2c71b47ce Translations: Update German
Currently translated at 100.0% (5690 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2024-07-03 17:05:45 +02:00
Raphael Michel
a889abc52b Unify terminology choice Ticketshop (German) 2024-07-03 13:41:09 +02:00
Raphael Michel
8c01b2a469 API: Fix crash in creating variations 2024-07-03 12:37:41 +02:00
Raphael Michel
720c7fd7bb Fix crash in event cloning (PRETIXEU-ABX) 2024-07-03 11:52:57 +02:00
Raphael Michel
6ae6eba4de API: Add details of seats (#4282) 2024-07-03 09:48:59 +02:00
Raphael Michel
a173e347ea Optimize availability queries 2024-07-02 18:29:44 +02:00
Raphael Michel
94d13e4cdd API: Raise the right validation error (PRETIXEU-ABV) 2024-07-02 17:49:09 +02:00
Raphael Michel
e618441231 API: Fix crash expanding variations 2024-07-02 14:44:35 +02:00
Raphael Michel
cd57f1f024 API: Fix creation of embedded variations with explicit sales channels 2024-07-02 09:25:19 +02:00
Raphael Michel
075b9c187f Item list: Fix exclusive tax rules 2024-07-02 09:03:57 +02:00
Erik Löfman
d9f46cb817 Translations: Update Swedish
Currently translated at 59.2% (3374 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/sv/

powered by weblate
2024-07-01 18:24:27 +02:00
Anarion Dunedain
2892d16861 Translations: Update Polish
Currently translated at 100.0% (5690 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pl/

powered by weblate
2024-07-01 18:24:27 +02:00
Nikolai
9128624d68 Translations: Update Danish
Currently translated at 34.8% (1985 of 5690 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/da/

powered by weblate
2024-07-01 18:24:27 +02:00
Raphael Michel
d2cf8f801d Sales channels: Fix update view 2024-07-01 17:50:27 +02:00
Raphael Michel
682d0f886d Fix order change edge case (PRETIXEU-ABH) 2024-07-01 14:39:34 +02:00
Raphael Michel
d2cbd41a19 Fix ticket preview (PRETIXEU-ABF) 2024-07-01 14:38:25 +02:00
Raphael Michel
828f4e3168 Fix isort and docs test 2024-07-01 11:46:46 +02:00
55 changed files with 1390 additions and 1165 deletions

View File

@@ -40,6 +40,11 @@ answers list of objects Answers to user
seat objects The assigned seat (or ``null``)
├ id integer Internal ID of the seat instance
├ name string Human-readable seat name
├ zone_name string Name of the zone the seat is in
├ row_name string Name/number of the row the seat is in
├ row_label string Additional label of the row (or ``null``)
├ seat_number string Number of the seat within the row
├ seat_label string Additional label of the seat (or ``null``)
└ seat_guid string Identifier of the seat within the seating plan
===================================== ========================== =======================================================

View File

@@ -96,6 +96,8 @@ Endpoints
:query integer page: The page number in case of a multi-page result set, default is 1
:query string secret: Only show gift cards with the given secret.
:query string value: Only show gift cards with the given value.
:query boolean expired: Filter for gift cards that are (not) expired.
:query boolean testmode: Filter for gift cards that are (not) in test mode.
:query boolean include_accepted: Also show gift cards issued by other organizers that are accepted by this organizer.
:query string expand: If you pass ``"owner_ticket"``, the respective field will be shown as a nested value instead of just an ID.

View File

@@ -164,6 +164,7 @@ Endpoints
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string search: Filter the list by the value of the variation (substring search).
:query boolean active: If set to ``true`` or ``false``, only items with this value for the field ``active`` will be
returned.
:param organizer: The ``slug`` field of the organizer to fetch

View File

@@ -392,6 +392,7 @@ Endpoints
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query string search: Filter the list by internal name or name of the item (substring search).
:query boolean active: If set to ``true`` or ``false``, only items with this value for the field ``active`` will be
returned.
:query integer category: If set to the ID of a category, only items within that category will be returned.

View File

@@ -215,6 +215,11 @@ answers list of objects Answers to user
seat objects The assigned seat. Can be ``null``.
├ id integer Internal ID of the seat instance
├ name string Human-readable seat name
├ zone_name string Name of the zone the seat is in
├ row_name string Name/number of the row the seat is in
├ row_label string Additional label of the row (or ``null``)
├ seat_number string Number of the seat within the row
├ seat_label string Additional label of the seat (or ``null``)
└ seat_guid string Identifier of the seat within the seating plan
pdf_data object Data object required for ticket PDF generation. By default,
this field is missing. It will be added only if you add the
@@ -455,10 +460,13 @@ List of all orders
:query datetime modified_since: Only return orders that have changed since the given date. Be careful: We only
recommend using this in combination with ``testmode=false``, since test mode orders can vanish at any time and
you will not notice it using this method.
:query datetime created_since: Only return orders that have been created since the given date.
:query datetime created_since: Only return orders that have been created since the given date (inclusive).
:query datetime created_before: Only return orders that have been created before the given date (exclusive).
:query integer subevent: Only return orders with a position that contains this subevent ID. *Warning:* Result will also include orders if they contain mixed subevents, and it will even return orders where the subevent is only contained in a canceled position.
:query datetime subevent_after: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive after, and it considers the **end** of the subevent (or its start, if the end is not set).
:query datetime subevent_before: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive before, and it considers the **start** of the subevent.
:query string sales_channel: Only return orders with the given sales channel identifier (e.g. ``"web"``).
:query string payment_provider: Only return orders that contain a payment using the given payment provider. Note that this also searches for partial incomplete, or failed payments within the order and is not useful to get a sum of payment amounts without further processing.
:query string exclude: Exclude a field from the output, e.g. ``fees`` or ``positions.downloads``. Can be used as a performance optimization. Can be passed multiple times.
:query string include: Include only the given field in the output, e.g. ``fees`` or ``positions.downloads``. Can be used as a performance optimization. Can be passed multiple times. ``include`` is applied before ``exclude``, so ``exclude`` takes precedence.
:param organizer: The ``slug`` field of the organizer to fetch

View File

@@ -35,7 +35,7 @@ Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head
.. automodule:: pretix.presale.signals

View File

@@ -17,7 +17,7 @@ The project pretix is split into several components. The main components are:
create and manage their events, items, orders and tickets.
**presale**
This is the ticket-shop itself, containing all of the parts visible to the
This is the ticket shop itself, containing all of the parts visible to the
end user. Also called "frontend" in parts of this documentation.
**api**

View File

@@ -75,7 +75,7 @@ dependencies = [
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.8.*",
"phonenumberslite==8.13.*",
"Pillow==10.3.*",
"Pillow==10.4.*",
"pretix-plugin-build",
"protobuf==5.27.*",
"psycopg2-binary",

View File

@@ -21,8 +21,8 @@
#
import json
from django.core.exceptions import ValidationError
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class AsymmetricField(serializers.Field):

View File

@@ -281,13 +281,17 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
from pretix.base.plugins import get_all_plugins
plugins_available = {
p.module for p in get_all_plugins(self.instance)
p.module: p for p in get_all_plugins(self.instance)
if not p.name.startswith('.') and getattr(p, 'visible', True)
}
settings_holder = self.instance if self.instance and self.instance.pk else self.context['organizer']
for plugin in value.get('plugins'):
if plugin not in plugins_available:
raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin))
if getattr(plugins_available[plugin], 'restricted', False):
if plugin not in settings_holder.settings.allowed_restricted_plugins:
raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin))
return value

View File

@@ -76,7 +76,9 @@ class InlineItemVariationSerializer(SalesChannelMigrationMixin, I18nAwareModelSe
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['require_membership_types'].queryset = lazy(lambda: self.context['event'].organizer.membership_types.all(), QuerySet)
self.fields['limit_sales_channels'].child_relation.queryset = lazy(lambda: self.context['event'].organizer.sales_channels.all(), QuerySet)
self.fields['limit_sales_channels'].child_relation.queryset = (
self.context['event'].organizer.sales_channels.all() if 'event' in self.context else SalesChannel.objects.none()
)
def validate_meta_data(self, value):
for key in value['meta_data'].keys():
@@ -115,11 +117,15 @@ class ItemVariationSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializ
def create(self, validated_data):
meta_data = validated_data.pop('meta_data', None)
require_membership_types = validated_data.pop('require_membership_types', [])
limit_sales_channels = validated_data.pop('limit_sales_channels', [])
variation = ItemVariation.objects.create(**validated_data)
if require_membership_types:
variation.require_membership_types.add(*require_membership_types)
if limit_sales_channels:
variation.limit_sales_channels.add(*limit_sales_channels)
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
@@ -284,6 +290,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
self.fields['require_membership_types'].queryset = self.context['event'].organizer.membership_types.all()
self.fields['grant_membership_type'].queryset = self.context['event'].organizer.membership_types.all()
self.fields['limit_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
self.fields['variations'].child.fields['limit_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
def validate(self, data):
data = super().validate(data)
@@ -371,10 +378,13 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
for variation_data in variations_data:
require_membership_types = variation_data.pop('require_membership_types', [])
limit_sales_channels = variation_data.pop('limit_sales_channels', [])
var_meta_data = variation_data.pop('meta_data', {})
v = ItemVariation.objects.create(item=item, **variation_data)
if require_membership_types:
v.require_membership_types.add(*require_membership_types)
if limit_sales_channels:
v.limit_sales_channels.add(*limit_sales_channels)
if var_meta_data is not None:
for key, value in var_meta_data.items():

View File

@@ -165,7 +165,7 @@ class InlineSeatSerializer(I18nAwareModelSerializer):
class Meta:
model = Seat
fields = ('id', 'name', 'seat_guid')
fields = ('id', 'name', 'seat_guid', 'zone_name', 'row_name', 'row_label', 'seat_label', 'seat_number')
class AnswerSerializer(I18nAwareModelSerializer):
@@ -585,7 +585,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
self.fields['item'] = ItemSerializer(read_only=True, context=self.context)
if 'variation' in self.context['expand']:
self.fields['variation'] = InlineItemVariationSerializer(read_only=True)
self.fields['variation'] = InlineItemVariationSerializer(read_only=True, context=self.context)
if 'answers.question' in self.context['expand']:
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)

View File

@@ -406,7 +406,7 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
'item__variations').select_related('item__tax_rule')
if expand and 'variation' in expand:
qs = qs.prefetch_related('variation')
qs = qs.prefetch_related('variation', 'variation__meta_values')
return qs

View File

@@ -41,6 +41,7 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import serializers, views, viewsets
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from pretix.api.auth.permission import EventCRUDPermission
@@ -162,7 +163,13 @@ class EventViewSet(viewsets.ModelViewSet):
qs = filter_qs_by_attr(qs, self.request)
if 'with_availability_for' in self.request.GET:
qs = Event.annotated(qs, channel=self.request.GET.get('with_availability_for'))
qs = Event.annotated(
qs,
channel=get_object_or_404(
self.request.organizer.sales_channels,
identifier=self.request.GET.get('with_availability_for')
)
)
return qs.prefetch_related(
'organizer',
@@ -442,7 +449,13 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
qs = filter_qs_by_attr(qs, self.request)
if 'with_availability_for' in self.request.GET:
qs = SubEvent.annotated(qs, channel=self.request.GET.get('with_availability_for'))
qs = SubEvent.annotated(
qs,
channel=get_object_or_404(
self.request.organizer.sales_channels,
identifier=self.request.GET.get('with_availability_for')
)
)
return qs.prefetch_related(
'event',

View File

@@ -56,10 +56,17 @@ from pretix.base.models import (
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers.dicts import merge_dicts
from pretix.helpers.i18n import i18ncomp
with scopes_disabled():
class ItemFilter(FilterSet):
tax_rate = django_filters.CharFilter(method='tax_rate_qs')
search = django_filters.CharFilter(method='search_qs')
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(internal_name__icontains=value) | Q(name__icontains=i18ncomp(value))
)
def tax_rate_qs(self, queryset, name, value):
if value in ("0", "None", "0.00"):
@@ -71,6 +78,18 @@ with scopes_disabled():
model = Item
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
class ItemVariationFilter(FilterSet):
search = django_filters.CharFilter(method='search_qs')
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(value__icontains=i18ncomp(value))
)
class Meta:
model = ItemVariation
fields = ['active']
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemSerializer
@@ -140,6 +159,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
serializer_class = ItemVariationSerializer
queryset = ItemVariation.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
filterset_class = ItemVariationFilter
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = None

View File

@@ -108,6 +108,7 @@ with scopes_disabled():
status = django_filters.CharFilter(field_name='status', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte')
created_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
created_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
search = django_filters.CharFilter(method='search_qs')
@@ -115,6 +116,8 @@ with scopes_disabled():
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id', distinct=True)
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id', distinct=True)
customer = django_filters.CharFilter(field_name='customer__identifier')
sales_channel = django_filters.CharFilter(field_name='sales_channel__identifier')
payment_provider = django_filters.CharFilter(method='provider_qs')
class Meta:
model = Order
@@ -138,6 +141,11 @@ with scopes_disabled():
)
return qs
def provider_qs(self, qs, name, value):
return qs.filter(Exists(
OrderPayment.objects.filter(order=OuterRef('pk'), provider=value)
))
def subevent_before_qs(self, qs, name, value):
if getattr(self.request, 'event', None):
subevents = self.request.event.subevents

View File

@@ -24,10 +24,11 @@ from decimal import Decimal
import django_filters
from django.contrib.auth.hashers import make_password
from django.db import transaction
from django.db.models import OuterRef, Subquery, Sum
from django.db.models import OuterRef, Q, Subquery, Sum
from django.db.models.functions import Coalesce
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import mixins, serializers, status, views, viewsets
@@ -136,11 +137,19 @@ class SeatingPlanViewSet(viewsets.ModelViewSet):
with scopes_disabled():
class GiftCardFilter(FilterSet):
secret = django_filters.CharFilter(field_name='secret', lookup_expr='iexact')
expired = django_filters.BooleanFilter(method='expired_qs')
value = django_filters.NumberFilter(field_name='cached_value')
class Meta:
model = GiftCard
fields = ['secret', 'testmode']
def expired_qs(self, qs, name, value):
if value:
return qs.filter(expires__isnull=False, expires__lt=now())
else:
return qs.filter(Q(expires__isnull=True) | Q(expires__gte=now()))
class GiftCardViewSet(viewsets.ModelViewSet):
serializer_class = GiftCardSerializer

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.2.8 on 2024-07-01 09:26
from django.db import migrations, models
import pretix.base.models.orders

View File

@@ -304,10 +304,13 @@ class EventMixin:
return safe_string(json.dumps(eventdict))
@classmethod
def annotated(cls, qs, channel='web', voucher=None):
from pretix.base.models import Item, ItemVariation, Quota
def annotated(cls, qs, channel, voucher=None):
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
# makes the query SIGNIFICANTLY faster
from pretix.base.models import Item, ItemVariation, Quota, SalesChannel
assert isinstance(channel, (SalesChannel, str))
assert isinstance(channel, str)
sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher).filter(
Q(variations__isnull=True)
& Q(quotas__pk=OuterRef('pk'))
@@ -317,18 +320,23 @@ class EventMixin:
q_variation = (
Q(active=True)
& Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()))
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()))
& Q(item__active=True)
& Q(Q(item__available_from__isnull=True) | Q(item__available_from__lte=time_machine_now()))
& Q(Q(item__available_until__isnull=True) | Q(item__available_until__gte=time_machine_now()))
& Q(Q(item__category__isnull=True) | Q(item__category__is_addon=False))
& Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__identifier=channel))
& Q(item__require_bundling=False)
& Q(quotas__pk=OuterRef('pk'))
)
if isinstance(channel, str):
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__identifier=channel))
else:
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels=channel))
if voucher:
if voucher.variation_id:
q_variation &= Q(pk=voucher.variation_id)
@@ -962,7 +970,7 @@ class Event(EventMixin, LoggedModel):
):
c_items = list(d.condition_limit_products.all())
b_items = list(d.benefit_limit_products.all())
limit_sales_channels = list(v.limit_sales_channels.all())
limit_sales_channels = list(d.limit_sales_channels.all())
d.pk = None
d.event = self
d._prefetched_objects_cache = {}
@@ -1536,8 +1544,11 @@ class SubEvent(EventMixin, LoggedModel):
return qs_annotated
@classmethod
def annotated(cls, qs, channel='web', voucher=None):
def annotated(cls, qs, channel, voucher=None):
from .items import SubEventItem, SubEventItemVariation
from .organizer import SalesChannel
assert isinstance(channel, (str, SalesChannel))
qs = super().annotated(qs, channel, voucher=voucher)
qs = qs.annotate(

View File

@@ -271,16 +271,24 @@ class SubEventItemVariation(models.Model):
def filter_available(qs, channel='web', voucher=None, allow_addons=False):
assert isinstance(channel, str)
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
# makes the query SIGNIFICANTLY faster
from .organizer import SalesChannel
assert isinstance(channel, (SalesChannel, str))
q = (
# IMPORTANT: If this is updated, also update the ItemVariation query
# in models/event.py: EventMixin.annotated()
Q(active=True)
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()) | Q(available_from_mode='info'))
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()) | Q(available_until_mode='info'))
& Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
& Q(require_bundling=False)
)
if isinstance(channel, str):
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
else:
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
if not allow_addons:
q &= Q(Q(category__isnull=True) | Q(category__is_addon=False))

View File

@@ -2042,7 +2042,7 @@ class OrderChangeManager:
# This also prevents accidental removal through the UI because a hidden product will no longer
# be part of the input.
(a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent))
or (a.variation and self.order.sales_channel not in a.variation.sales_channels)
or (a.variation and not a.variation.all_sales_channels and not a.variation.limit_sales_channels.contains(self.order.sales_channel))
or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent)
or (
not item.all_sales_channels and

View File

@@ -105,6 +105,7 @@ def preview(event: int, provider: str):
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
email='sample@pretix.eu',
locale=event.settings.locale,
sales_channel=event.organizer.sales_channels.get(identifier="web"),
expires=now(), code="PREVIEW1234", total=119)
scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]

View File

@@ -136,9 +136,15 @@
{% if i.tax_rule and i.default_price %}
<br/>
<small class="text-muted">
{% blocktrans trimmed with rate=i.tax_rule.rate|floatformat:-2 taxname=i.tax_rule.name|default:s_taxes %}
incl. {{ rate }}% {{ taxname }}
{% endblocktrans %}
{% if not i.tax_rule.price_includes_tax %}
{% blocktrans trimmed with rate=i.tax_rule.rate|floatformat:-2 taxname=i.tax_rule.name %}
<strong>plus</strong> {{ rate }}% {{ taxname }}
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with rate=i.tax_rule.rate|floatformat:-2 taxname=i.tax_rule.name|default:s_taxes %}
incl. {{ rate }}% {{ taxname }}
{% endblocktrans %}
{% endif %}
</small>
{% endif %}
</td>

View File

@@ -57,7 +57,7 @@
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
{{ se }}
</option>
{% endfor %}
</select>
@@ -119,7 +119,7 @@
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
{{ se }}
</option>
{% endfor %}
</select>
@@ -195,7 +195,7 @@
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }}</td>
<td>{{ e.subevent }}</td>
{% endif %}
<td>
{{ e.created|date:"SHORT_DATETIME_FORMAT" }}

View File

@@ -400,7 +400,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat
if key.startswith("plugin:"):
module = key.split(":")[1]
if value == "enable" and module in plugins_available:
if getattr(plugins_available[module].app, 'restricted', False):
if getattr(plugins_available[module], 'restricted', False):
if module not in request.event.settings.allowed_restricted_plugins:
continue

View File

@@ -103,7 +103,7 @@ class ItemList(ListView):
def get_queryset(self):
return Item.objects.filter(
event=self.request.event
).annotate(
).select_related("tax_rule").annotate(
var_count=Count('variations')
).prefetch_related("category", "limit_sales_channels").order_by(
F('category__position').asc(nulls_first=True),

View File

@@ -3161,7 +3161,7 @@ class ChannelUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
}
def form_valid(self, form):
if form.has_changed() or self.formset.has_changed():
if form.has_changed():
self.object.log_action('pretix.saleschannel.changed', user=self.request.user, data={
k: getattr(self.object, k)
for k in form.changed_data

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-30 18:55+0000\n"
"PO-Revision-Date: 2024-06-30 21:07+0000\n"
"PO-Revision-Date: 2024-07-02 19:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
">\n"
@@ -3536,7 +3536,7 @@ msgstr "Sie können keinen Ticketcode verwenden, der bereits existiert."
#: pretix/base/modelimport_orders.py:490
msgid "Please enter a valid language code."
msgstr "Bitte geben Sie einen gültigen Sprachcode ein."
msgstr "Bitte einen gültigen Sprachcode eingeben."
#: pretix/base/modelimport_orders.py:558 pretix/base/modelimport_orders.py:560
msgid "Please enter a valid sales channel."
@@ -9608,7 +9608,7 @@ msgstr ""
#: pretix/base/settings.py:1346
msgid "Ask search engines not to index the ticket shop"
msgstr "Der Ticket-Shop soll von Suchmaschinen nicht indiziert werden"
msgstr "Der Ticketshop soll von Suchmaschinen nicht indiziert werden"
#: pretix/base/settings.py:1355
msgid "Show variations of a product expanded by default"
@@ -10874,7 +10874,7 @@ msgstr ""
"Sie haben sich auf die Warteliste für {event} \n"
"für das Produkt {product} eingetragen.\n"
"\n"
"Wir haben nun ein Ticket für Sie! Sie können es in unserem Ticket-Shop "
"Wir haben nun ein Ticket für Sie! Sie können es in unserem Ticketshop "
"erwerben,\n"
"indem Sie in den nächsten {hours} Stunden den folgenden Gutscheincode "
"eingeben:\n"
@@ -11971,7 +11971,7 @@ msgid ""
"If you just configured this as a domain for your ticket shop, you now need "
"to set this up as a \"custom domain\" in your organizer account."
msgstr ""
"Wenn Sie gerade diese Domain für Ihren Ticket-Shop eingerichtet haben, "
"Wenn Sie gerade diese Domain für Ihren Ticketshop eingerichtet haben, "
"müssen Sie diese nun in Ihrem Veranstalterkonto als \"eigene Domain\" "
"eintragen."
@@ -18403,7 +18403,7 @@ msgid ""
"You can instead take your shop offline. This will hide it from everyone "
"except from the organizer teams you configured to have access to the event."
msgstr ""
"Sie können stattdessen den Ticket-Shop abschalten. Dies wird die "
"Sie können stattdessen den Ticketshop abschalten. Dies wird die "
"Veranstaltung vor allen außer den zugewiesenen Veranstalter-Teams verstecken."
#: pretix/control/templates/pretixcontrol/event/delete.html:66
@@ -18629,14 +18629,14 @@ msgid ""
"Your ticket shop is currently not live. It is thus only visible to you and "
"your team, not to any visitors."
msgstr ""
"Ihr Ticket-Shop ist zur Zeit offline und daher nur für Sie und Ihr Team "
"Ihr Ticketshop ist zur Zeit offline und daher nur für Sie und Ihr Team "
"verfügbar und nicht für Besucher."
#: pretix/control/templates/pretixcontrol/event/live.html:41
msgid ""
"To publish your ticket shop, you first need to resolve the following issues:"
msgstr ""
"Um Ihren Ticket-Shop zu veröffentlichen, müssen Sie zuerst die folgenden "
"Um Ihren Ticketshop zu veröffentlichen, müssen Sie zuerst die folgenden "
"Probleme beheben:"
#: pretix/control/templates/pretixcontrol/event/live.html:51
@@ -18646,7 +18646,7 @@ msgstr "Shop veröffentlichen"
#: pretix/control/templates/pretixcontrol/event/live.html:59
msgid "If you want to, you can publish your ticket shop now."
msgstr "Sie können Ihren Ticket-Shop jederzeit veröffentlichen."
msgstr "Sie können Ihren Ticketshop jederzeit veröffentlichen."
#: pretix/control/templates/pretixcontrol/event/live.html:83
msgid ""
@@ -19488,7 +19488,7 @@ msgid ""
"website. This way, your visitors can buy their ticket right away without "
"leaving your website."
msgstr ""
"Das pretix-Widget ist ein Weg, den Ticket-Shop in Ihre Event-Website "
"Das pretix-Widget ist ein Weg, den Ticketshop in Ihre Event-Website "
"einzubetten. Auf diese Weise können Besucher Ihrer Website ein Ticket "
"erwerben, ohne die Website verlassen zu müssen."
@@ -19515,7 +19515,7 @@ msgid ""
"JavaScript is disabled in your browser. To access our ticket shop without "
"JavaScript, please &lt;a %(a_attr)s&gt;click here&lt;/a&gt;."
msgstr ""
"JavaScript ist in Ihrem Browser deaktiviert. Um unseren Ticket-Shop ohne "
"JavaScript ist in Ihrem Browser deaktiviert. Um unseren Ticketshop ohne "
"JavaScript aufzurufen, klicken Sie bitte &lt;a %(a_attr)s&gt;hier&lt;/a&gt;."
#: pretix/control/templates/pretixcontrol/event/widget.html:64
@@ -19580,7 +19580,7 @@ msgid ""
"less than 10 characters that can be easily remembered, but you can also "
"choose to use a random value."
msgstr ""
"Dies ist die Adresse, unter der Ihr Ticket-Shop verfügbar sein wird. Sie "
"Dies ist die Adresse, unter der Ihr Ticketshop verfügbar sein wird. Sie "
"sollte kurz sein und darf nur Kleinbuchstaben, Zahlen, Punkte und "
"Bindestriche enthalten. Sie muss unter Ihren Veranstaltungen einmalig sein. "
"Wir empfehlen eine Abkürzung oder ein Datum mit weniger als 10 Zeichen, das "
@@ -22464,7 +22464,7 @@ msgstr "Veröffentlichen Sie Ihren Shop"
#: pretix/control/templates/pretixcontrol/orders/index.html:27
msgid "Go to the ticket shop"
msgstr "Zum Ticket-Shop"
msgstr "Zum Ticketshop"
#: pretix/control/templates/pretixcontrol/orders/index.html:35
msgid "Search query:"
@@ -23102,7 +23102,7 @@ msgid ""
"usage, the legal details in your specific jurisdiction, or the agreements "
"you have with third parties such as payment or tracking providers."
msgstr ""
"Trotzdem bleibt es Ihre Verantwortung, dass Ihr Ticket-Shop allen "
"Trotzdem bleibt es Ihre Verantwortung, dass Ihr Ticketshop allen "
"anwendbaren Gesetzen entspricht. Wir versuchen mit diesen Einstellungen zu "
"helfen, aber können keine Haftung übernehmen, da wir unter anderem die "
"genaue Konfiguration Ihres Ticketshops, die rechtlichen Details der "
@@ -25624,7 +25624,7 @@ msgstr "{quota} übrig"
#: pretix/control/views/dashboards.py:269
msgid "Your ticket shop is"
msgstr "Ihr Ticket-Shop ist"
msgstr "Ihr Ticketshop ist"
#: pretix/control/views/dashboards.py:269
msgid "Click here to change"
@@ -31490,7 +31490,7 @@ msgstr "Warnung"
#: pretix/presale/templates/pretixpresale/event/base.html:127
#: pretix/presale/templates/pretixpresale/event/base.html:201
msgid "This ticket shop is currently in test mode."
msgstr "Dieser Ticket-Shop ist momentan im Testmodus."
msgstr "Dieser Ticketshop ist momentan im Testmodus."
#: pretix/presale/templates/pretixpresale/event/base.html:130
#: pretix/presale/templates/pretixpresale/event/base.html:204
@@ -32857,7 +32857,7 @@ msgstr "Shop ausgeschaltet"
#: pretix/presale/templates/pretixpresale/event/offline.html:9
msgid "This ticket shop is currently turned off."
msgstr "Dieser Ticket-Shop ist im Moment nicht aktiv."
msgstr "Dieser Ticketshop ist im Moment nicht aktiv."
#: pretix/presale/templates/pretixpresale/event/offline.html:10
msgid "It is only accessible to authenticated team members."
@@ -33684,7 +33684,7 @@ msgid ""
"open source ticket sales software</a>."
msgstr ""
"Dies ist eine selbst-gehostete Installation von <a %(a_attr)s>pretix, dem "
"freundlichen Open Source Ticket-Shop</a>."
"freundlichen Open Source Ticketshop</a>."
#: pretix/presale/templates/pretixpresale/index.html:15
msgid ""
@@ -34215,7 +34215,7 @@ msgstr ""
#: pretix/presale/views/widget.py:372
msgid "This ticket shop is currently disabled."
msgstr "Dieser Ticket-Shop ist im Moment nicht verfügbar."
msgstr "Dieser Ticketshop ist im Moment nicht verfügbar."
#: pretix/presale/views/widget.py:386
msgid "The selected date does not exist in this event series."
@@ -34336,7 +34336,7 @@ msgstr "Kosovo"
#~ msgstr "Sie können nicht fortfahren."
#~ msgid "The selected ticket shop is currently not available."
#~ msgstr "Der ausgewählte Ticket-Shop ist im Moment nicht verfügbar."
#~ msgstr "Der ausgewählte Ticketshop ist im Moment nicht verfügbar."
#~ msgid "Needs to be enabled in your Stripe account first."
#~ msgstr "Muss erst im Stripe-Account aktiviert werden."

View File

@@ -871,12 +871,12 @@ msgstr "minimale Bestellmenge: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid "Close ticket shop"
msgstr "Ticket-Shop schließen"
msgstr "Ticketshop schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:40
msgctxt "widget"
msgid "The ticket shop could not be loaded."
msgstr "Der Ticket-Shop konnte nicht geladen werden."
msgstr "Der Ticketshop konnte nicht geladen werden."
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
@@ -890,7 +890,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Open ticket shop"
msgstr "Ticket-Shop öffnen"
msgstr "Ticketshop öffnen"
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-30 18:55+0000\n"
"PO-Revision-Date: 2024-06-30 21:07+0000\n"
"PO-Revision-Date: 2024-07-02 19:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
@@ -3536,7 +3536,7 @@ msgstr "Sie können keinen Ticketcode verwenden, der bereits existiert."
#: pretix/base/modelimport_orders.py:490
msgid "Please enter a valid language code."
msgstr "Bitte gib einen gültigen Sprachcode ein."
msgstr "Bitte einen gültigen Sprachcode eingeben."
#: pretix/base/modelimport_orders.py:558 pretix/base/modelimport_orders.py:560
msgid "Please enter a valid sales channel."
@@ -9594,7 +9594,7 @@ msgstr ""
#: pretix/base/settings.py:1346
msgid "Ask search engines not to index the ticket shop"
msgstr "Der Ticket-Shop soll von Suchmaschinen nicht indiziert werden"
msgstr "Der Ticketshop soll von Suchmaschinen nicht indiziert werden"
#: pretix/base/settings.py:1355
msgid "Show variations of a product expanded by default"
@@ -10856,7 +10856,7 @@ msgstr ""
"du hast dich auf die Warteliste für {event} \n"
"für das Produkt {product} eingetragen.\n"
"\n"
"Wir haben nun ein Ticket für dich! Du kannst es in unserem Ticket-Shop "
"Wir haben nun ein Ticket für dich! Du kannst es in unserem Ticketshop "
"erwerben,\n"
"indem du in den nächsten {hours} Stunden den folgenden Gutscheincode "
"eingibst:\n"
@@ -11950,7 +11950,7 @@ msgid ""
"If you just configured this as a domain for your ticket shop, you now need "
"to set this up as a \"custom domain\" in your organizer account."
msgstr ""
"Wenn du gerade diese Domain für deinen Ticket-Shop eingerichtet hast, musst "
"Wenn du gerade diese Domain für deinen Ticketshop eingerichtet hast, musst "
"du diese nun in deinem Veranstalterkonto als \"eigene Domain\" eintragen."
#: pretix/base/templates/403.html:4 pretix/base/templates/403.html:8
@@ -18371,7 +18371,7 @@ msgid ""
"You can instead take your shop offline. This will hide it from everyone "
"except from the organizer teams you configured to have access to the event."
msgstr ""
"Du kannst stattdessen den Ticket-Shop abschalten. Dies wird die "
"Du kannst stattdessen den Ticketshop abschalten. Dies wird die "
"Veranstaltung vor allen außer den zugewiesenen Veranstalter-Teams verstecken."
#: pretix/control/templates/pretixcontrol/event/delete.html:66
@@ -18596,14 +18596,14 @@ msgid ""
"Your ticket shop is currently not live. It is thus only visible to you and "
"your team, not to any visitors."
msgstr ""
"Dein Ticket-Shop ist zur Zeit offline und daher nur für dich und dein Team "
"Dein Ticketshop ist zur Zeit offline und daher nur für dich und dein Team "
"verfügbar und nicht für Besucher."
#: pretix/control/templates/pretixcontrol/event/live.html:41
msgid ""
"To publish your ticket shop, you first need to resolve the following issues:"
msgstr ""
"Um deinen Ticket-Shop zu veröffentlichen, musst du zuerst die folgenden "
"Um deinen Ticketshop zu veröffentlichen, musst du zuerst die folgenden "
"Probleme beheben:"
#: pretix/control/templates/pretixcontrol/event/live.html:51
@@ -18613,7 +18613,7 @@ msgstr "Shop veröffentlichen"
#: pretix/control/templates/pretixcontrol/event/live.html:59
msgid "If you want to, you can publish your ticket shop now."
msgstr "Du kannst deinen Ticket-Shop jederzeit veröffentlichen."
msgstr "Du kannst deinen Ticketshop jederzeit veröffentlichen."
#: pretix/control/templates/pretixcontrol/event/live.html:83
msgid ""
@@ -19454,7 +19454,7 @@ msgid ""
"website. This way, your visitors can buy their ticket right away without "
"leaving your website."
msgstr ""
"Das pretix-Widget ist ein Weg, den Ticket-Shop in deine Event-Website "
"Das pretix-Widget ist ein Weg, den Ticketshop in deine Event-Website "
"einzubetten. Auf diese Weise können Besucher deiner Website ein Ticket "
"erwerben, ohne die Website verlassen zu müssen."
@@ -19481,7 +19481,7 @@ msgid ""
"JavaScript is disabled in your browser. To access our ticket shop without "
"JavaScript, please &lt;a %(a_attr)s&gt;click here&lt;/a&gt;."
msgstr ""
"JavaScript ist in deinem Browser deaktiviert. Um unseren Ticket-Shop ohne "
"JavaScript ist in deinem Browser deaktiviert. Um unseren Ticketshop ohne "
"JavaScript aufzurufen, klicke bitte &lt;a %(a_attr)s&gt;hier&lt;/a&gt;."
#: pretix/control/templates/pretixcontrol/event/widget.html:64
@@ -19546,7 +19546,7 @@ msgid ""
"less than 10 characters that can be easily remembered, but you can also "
"choose to use a random value."
msgstr ""
"Dies ist die Adresse, unter der dein Ticket-Shop verfügbar sein wird. Sie "
"Dies ist die Adresse, unter der dein Ticketshop verfügbar sein wird. Sie "
"sollte kurz sein und darf nur Kleinbuchstaben, Zahlen, Punkte und "
"Bindestriche enthalten. Sie muss unter deinen Veranstaltungen einmalig sein. "
"Wir empfehlen eine Abkürzung oder ein Datum mit unter 10 Zeichen, das man "
@@ -22425,7 +22425,7 @@ msgstr "Veröffentliche deinen Shop"
#: pretix/control/templates/pretixcontrol/orders/index.html:27
msgid "Go to the ticket shop"
msgstr "Zum Ticket-Shop"
msgstr "Zum Ticketshop"
#: pretix/control/templates/pretixcontrol/orders/index.html:35
msgid "Search query:"
@@ -23063,7 +23063,7 @@ msgid ""
"usage, the legal details in your specific jurisdiction, or the agreements "
"you have with third parties such as payment or tracking providers."
msgstr ""
"Trotzdem bleibt es deine Verantwortung, dass dein Ticket-Shop allen "
"Trotzdem bleibt es deine Verantwortung, dass dein Ticketshop allen "
"anwendbaren Gesetzen entspricht. Wir versuchen mit diesen Einstellungen zu "
"helfen, aber können keine Haftung übernehmen, da wir unter anderem die "
"genaue Konfiguration deines Ticketshops, die rechtlichen Details der "
@@ -25579,7 +25579,7 @@ msgstr "{quota} übrig"
#: pretix/control/views/dashboards.py:269
msgid "Your ticket shop is"
msgstr "Dein Ticket-Shop ist"
msgstr "Dein Ticketshop ist"
#: pretix/control/views/dashboards.py:269
msgid "Click here to change"
@@ -31432,7 +31432,7 @@ msgstr "Warnung"
#: pretix/presale/templates/pretixpresale/event/base.html:127
#: pretix/presale/templates/pretixpresale/event/base.html:201
msgid "This ticket shop is currently in test mode."
msgstr "Dieser Ticket-Shop ist momentan im Testmodus."
msgstr "Dieser Ticketshop ist momentan im Testmodus."
#: pretix/presale/templates/pretixpresale/event/base.html:130
#: pretix/presale/templates/pretixpresale/event/base.html:204
@@ -32794,7 +32794,7 @@ msgstr "Shop ausgeschaltet"
#: pretix/presale/templates/pretixpresale/event/offline.html:9
msgid "This ticket shop is currently turned off."
msgstr "Dieser Ticket-Shop ist im Moment nicht aktiv."
msgstr "Dieser Ticketshop ist im Moment nicht aktiv."
#: pretix/presale/templates/pretixpresale/event/offline.html:10
msgid "It is only accessible to authenticated team members."
@@ -33622,7 +33622,7 @@ msgid ""
"open source ticket sales software</a>."
msgstr ""
"Dies ist eine selbst-gehostete Installation von <a %(a_attr)s>pretix, dem "
"freundlichen Open Source Ticket-Shop</a>."
"freundlichen Open Source Ticketshop</a>."
#: pretix/presale/templates/pretixpresale/index.html:15
msgid ""
@@ -34150,7 +34150,7 @@ msgstr ""
#: pretix/presale/views/widget.py:372
msgid "This ticket shop is currently disabled."
msgstr "Dieser Ticket-Shop ist im Moment nicht verfügbar."
msgstr "Dieser Ticketshop ist im Moment nicht verfügbar."
#: pretix/presale/views/widget.py:386
msgid "The selected date does not exist in this event series."
@@ -34271,7 +34271,7 @@ msgstr "Kosovo"
#~ msgstr "Du kannst daher nicht fortfahren."
#~ msgid "The selected ticket shop is currently not available."
#~ msgstr "Der ausgewählte Ticket-Shop ist im Moment nicht verfügbar."
#~ msgstr "Der ausgewählte Ticketshop ist im Moment nicht verfügbar."
#~ msgid "Needs to be enabled in your Stripe account first."
#~ msgstr "Muss erst im Stripe-Account aktiviert werden."

View File

@@ -870,12 +870,12 @@ msgstr "minimale Bestellmenge: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid "Close ticket shop"
msgstr "Ticket-Shop schließen"
msgstr "Ticketshop schließen"
#: pretix/static/pretixpresale/js/widget/widget.js:40
msgctxt "widget"
msgid "The ticket shop could not be loaded."
msgstr "Der Ticket-Shop konnte nicht geladen werden."
msgstr "Der Ticketshop konnte nicht geladen werden."
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
@@ -889,7 +889,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Open ticket shop"
msgstr "Ticket-Shop öffnen"
msgstr "Ticketshop öffnen"
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-30 18:55+0000\n"
"PO-Revision-Date: 2024-06-27 17:00+0000\n"
"PO-Revision-Date: 2024-07-01 16:00+0000\n"
"Last-Translator: Anarion Dunedain <anarion80@gmail.com>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix/pl/"
">\n"
@@ -122,7 +122,7 @@ msgstr "Słowacki"
#: pretix/_base_settings.py:103
msgid "Swedish"
msgstr ""
msgstr "Szwedzki"
#: pretix/_base_settings.py:104
msgid "Spanish"
@@ -640,13 +640,15 @@ msgstr "Sklep online"
#: pretix/base/channels.py:174
msgid "API"
msgstr ""
msgstr "API"
#: pretix/base/channels.py:175
msgid ""
"API sales channels come with no built-in functionality, but may be used for "
"custom integrations."
msgstr ""
"Kanały sprzedaży API nie mają wbudowanych funkcji, ale mogą być używane do "
"niestandardowych integracji."
#: pretix/base/context.py:45
#, python-brace-format
@@ -4041,10 +4043,8 @@ msgid "Position"
msgstr "Pozycja"
#: pretix/base/models/discount.py:68
#, fuzzy
#| msgid "Sales channels"
msgid "All supported sales channels"
msgstr "Kanały sprzedaży"
msgstr "Wszystkie obsługiwane kanały sprzedaży"
#: pretix/base/models/discount.py:89
msgid "Event series handling"
@@ -4257,10 +4257,8 @@ msgid "Seating plan"
msgstr "Plan miejsc"
#: pretix/base/models/event.py:639 pretix/base/models/items.py:618
#, fuzzy
#| msgid "Sales channels"
msgid "Sell on all sales channels"
msgstr "Kanały sprzedaży"
msgstr "Sprzedawaj we wszystkich kanałach sprzedaży"
#: pretix/base/models/event.py:644 pretix/base/models/items.py:623
#: pretix/base/models/items.py:1170 pretix/base/payment.py:417
@@ -4570,9 +4568,6 @@ msgid "{category} (Add-On products)"
msgstr "{category} (Produkty dodatkowe)"
#: pretix/base/models/items.py:128
#, fuzzy
#| msgctxt "checkoutflow"
#| msgid "Add-on products"
msgid "Add-On products"
msgstr "Produkty dodatkowe"
@@ -4859,11 +4854,8 @@ msgstr ""
"przeceny. Ustawienie kosmetyczne bez wzgędlu na faktyczną cenę sprzedaży."
#: pretix/base/models/items.py:624
#, fuzzy
#| msgid "Only sell tickets for this event on the following sales channels."
msgid "Only sell tickets for this product on the selected sales channels."
msgstr ""
"Sprzedaj bilety tylko na to wydarzenie na następujących kanałach sprzedaży."
msgstr "Sprzedaj bilety na ten produkt tylko w wybranych kanałach sprzedaży."
#: pretix/base/models/items.py:629
msgid ""
@@ -5114,6 +5106,8 @@ msgstr "Ten wariant nie będzie sprzedawany po podanej dacie."
#: pretix/base/models/items.py:1165
msgid "Sell on all sales channels the product is sold on"
msgstr ""
"Sprzedaj we wszystkich kanałach sprzedaży, w których sprzedawany jest ten "
"produkt."
#: pretix/base/models/items.py:1171
msgid ""
@@ -6053,9 +6047,6 @@ msgstr "Zaproszenie do zespołu '{team}' dla '{email}'"
#: pretix/base/models/organizer.py:538
#: pretix/control/templates/pretixcontrol/organizers/channels.html:23
#, fuzzy
#| msgctxt "reusable_medium"
#| msgid "Identifier"
msgid "Identifier"
msgstr "Identyfikator"
@@ -12050,6 +12041,8 @@ msgid ""
"This sales channel cannot be used properly since the respective plugin is "
"not active for this event."
msgstr ""
"Ten kanał sprzedaży nie może być używany prawidłowo, ponieważ odpowiednia "
"wtyczka nie jest aktywna dla tego wydarzenia."
#: pretix/base/templates/pretixbase/forms/widgets/portrait_image.html:10
msgid "Upload photo"
@@ -14061,6 +14054,8 @@ msgid ""
"You have selected dynamic validity but have not entered a time period. This "
"would render the tickets unusable."
msgstr ""
"Wybrałeś dynamiczną ważność, ale nie wprowadziłeś okresu czasu. Spowoduje "
"to, że bilety będą bezużyteczne."
#: pretix/control/forms/item.py:858
#, python-format
@@ -14807,10 +14802,8 @@ msgid "The selected organizer has already been invited."
msgstr "Wybrany organizator został już zaproszony."
#: pretix/control/forms/organizer.py:1128
#, fuzzy
#| msgid "A voucher with this code already exists."
msgid "A sales channel with the same identifier already exists."
msgstr "Voucher o tym kodzie już istnieje."
msgstr "Kanał sprzedaży o tym samym identyfikatorze już istnieje."
#: pretix/control/forms/renderers.py:56
#: pretix/control/templates/pretixcontrol/items/question_edit.html:139
@@ -15423,23 +15416,16 @@ msgid "The membership type has been deleted."
msgstr "Typ członkostwa został usunięty."
#: pretix/control/logdisplay.py:360 pretix/control/views/organizer.py:3119
#, fuzzy
#| msgctxt "subevent"
#| msgid "The new date has been created."
msgid "The sales channel has been created."
msgstr "Nowa data została utworzona."
msgstr "Kanał sprzedaży został utworzony."
#: pretix/control/logdisplay.py:361
#, fuzzy
#| msgid "The device has been changed."
msgid "The sales channel has been changed."
msgstr "Urządzenie zostało zmienione."
msgstr "Kanał sprzedaży został zmieniony."
#: pretix/control/logdisplay.py:362
#, fuzzy
#| msgid "The selected list has been deleted."
msgid "The sales channel has been deleted."
msgstr "Wybrana lista została usunięta."
msgstr "Kanał sprzedaży został usunięty."
#: pretix/control/logdisplay.py:363
msgid "The account has been created."
@@ -20190,14 +20176,14 @@ msgstr "Utwórz nową kategorię"
#: pretix/control/templates/pretixcontrol/items/index.html:146
#: pretix/control/templates/pretixcontrol/organizers/properties.html:54
msgid "Move up"
msgstr ""
msgstr "Przesuń w górę"
#: pretix/control/templates/pretixcontrol/items/categories.html:45
#: pretix/control/templates/pretixcontrol/items/discounts.html:142
#: pretix/control/templates/pretixcontrol/items/index.html:147
#: pretix/control/templates/pretixcontrol/organizers/properties.html:55
msgid "Move down"
msgstr ""
msgstr "Przesuń w dół"
#: pretix/control/templates/pretixcontrol/items/categories.html:46
#: pretix/control/templates/pretixcontrol/items/discounts.html:145
@@ -20207,6 +20193,8 @@ msgid ""
"Click and drag this button to reorder. Double click to show buttons for "
"reordering."
msgstr ""
"Kliknij i przeciągnij ten przycisk, aby zmienić kolejność. Kliknij "
"dwukrotnie, aby wyświetlić przyciski do zmiany kolejności."
#: pretix/control/templates/pretixcontrol/items/category.html:27
msgid "Category history"
@@ -20350,15 +20338,12 @@ msgstr ""
"produktu"
#: pretix/control/templates/pretixcontrol/items/discounts.html:111
#, fuzzy
#| msgctxt "discount"
#| msgid "Condition"
msgid "Condition:"
msgstr "Stan"
msgstr "Stan:"
#: pretix/control/templates/pretixcontrol/items/discounts.html:126
msgid "Applies to:"
msgstr ""
msgstr "Dotyczy:"
#: pretix/control/templates/pretixcontrol/items/fragment_quota_availability.html:3
msgid "Closed"
@@ -20398,12 +20383,6 @@ msgid "taxes"
msgstr "podatki"
#: pretix/control/templates/pretixcontrol/items/index.html:10
#, fuzzy
#| msgid ""
#| "Below, you find a list of all available products. You can click on a "
#| "product name to inspect and change product details. You can also use the "
#| "buttons on the right to change the order of products within a give "
#| "category."
msgid ""
"Below, you find a list of all available products. You can click on a product "
"name to inspect and change product details. You can also use the buttons on "
@@ -20412,8 +20391,8 @@ msgid ""
msgstr ""
"Poniżej znajduje się lista wszystkich dostępnych produktów. Możesz kliknąć "
"nazwę produktu, aby sprawdzić i zmienić jego szczegóły. Możesz także użyć "
"przycisków po prawej stronie, aby zmienić kolejność produktów w danej "
"kategorii."
"przycisków po prawej stronie, aby zmienić kolejność produktów lub przenieść "
"produkty do innej kategorii."
#: pretix/control/templates/pretixcontrol/items/index.html:19
msgid "You haven't created any products yet."
@@ -22479,48 +22458,34 @@ msgstr "Wyszukiwanie zamówień"
#: pretix/control/templates/pretixcontrol/organizers/channel_add.html:6
#: pretix/control/templates/pretixcontrol/organizers/channel_add_choice.html:6
#, fuzzy
#| msgid "Sales channel"
msgid "Add sales channel"
msgstr "Kanał sprzedaży"
msgstr "Dodaj kanał sprzedaży"
#: pretix/control/templates/pretixcontrol/organizers/channel_add.html:13
#: pretix/control/templates/pretixcontrol/organizers/channel_edit.html:13
#: pretix/control/templates/pretixcontrol/organizers/channels.html:24
#, fuzzy
#| msgid "Scan type"
msgid "Channel type"
msgstr "Typ skanowania"
msgstr "Typ kanału"
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html:5
#, fuzzy
#| msgid "Sales channel"
msgid "Delete sales channel:"
msgstr "Kanał sprzedaży"
msgstr "Usuń kanał sprzedaży:"
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html:10
#, fuzzy
#| msgid "Are you sure you want to delete the gate?"
msgid "Are you sure you want to delete this sales channel?"
msgstr "Czy na pewno chcesz usunąć bramkę?"
msgstr "Czy na pewno chcesz usunąć ten kanał sprzedaży?"
#: pretix/control/templates/pretixcontrol/organizers/channel_delete.html:15
#, fuzzy
#| msgid ""
#| "This membership cannot be deleted since it has been used in an order. "
#| "Change its end date to the past instead."
msgid ""
"This sales channel cannot be deleted since it has already been used to sell "
"orders or because it is a core element of the system."
msgstr ""
"Tego członkostwa nie można usunąć, ponieważ zostało użyte w zamówieniu. "
"Zamiast tego zmień jego datę końcową na przeszłą."
"Tego kanału sprzedaży nie można usunąć, ponieważ został już użyty do "
"sprzedaży zamówień lub jest podstawowym elementem systemu."
#: pretix/control/templates/pretixcontrol/organizers/channel_edit.html:6
#, fuzzy
#| msgid "Sales channel"
msgid "Sales channel:"
msgstr "Kanał sprzedaży"
msgstr "Kanał sprzedaży:"
#: pretix/control/templates/pretixcontrol/organizers/channels.html:8
msgid ""
@@ -22528,18 +22493,17 @@ msgid ""
"through. This is useful to unlock new revenue streams or to separate revenue "
"between different sources for reporting purchases."
msgstr ""
"Na tej stronie można zarządzać różnymi kanałami sprzedaży biletów. Jest to "
"przydatne do odblokowania nowych źródeł przychodów lub rozdzielenia "
"przychodów między różne źródła w celu raportowania zakupów."
#: pretix/control/templates/pretixcontrol/organizers/channels.html:15
#, fuzzy
#| msgid "Add a new value"
msgid "Add a new channel"
msgstr "Dodaj nową wartość"
msgstr "Dodaj nowy kanał"
#: pretix/control/templates/pretixcontrol/organizers/channels.html:22
#, fuzzy
#| msgid "Change"
msgid "Channel"
msgstr "Zmień"
msgstr "Kanał"
#: pretix/control/templates/pretixcontrol/organizers/customer.html:7
#: pretix/control/templates/pretixcontrol/organizers/customer.html:13
@@ -26776,34 +26740,24 @@ msgid "The customer account has been anonymized."
msgstr "Konto klienta zostało zanonimizowane."
#: pretix/control/views/organizer.py:3201
#, fuzzy
#| msgid "This organizer can not be deleted."
msgid "This channel can not be deleted."
msgstr "Tego organizatora nie można usunąć."
msgstr "Tego kanału nie można usunąć."
#: pretix/control/views/organizer.py:3206
#, fuzzy
#| msgid "The selected list has been deleted."
msgid "The selected sales channel has been deleted."
msgstr "Wybrana lista została usunięta."
msgstr "Wybrany kanał sprzedaży został usunięty."
#: pretix/control/views/organizer.py:3208
#, fuzzy
#| msgid ""
#| "The team could not be deleted as some constraints (e.g. data created by "
#| "plug-ins) do not allow it."
msgid ""
"The channel could not be deleted as some constraints (e.g. data created by "
"plug-ins) did not allow it."
msgstr ""
"Zespół nie mógł zostać usunięty, ponieważ niektóre ograniczenia (np. dane "
"utworzone przez wtyczki) na to nie pozwalają."
"Kanał nie mógł zostać usunięty, ponieważ niektóre ograniczenia (np. dane "
"utworzone przez wtyczki) na to nie pozwoliły."
#: pretix/control/views/organizer.py:3232
#, fuzzy
#| msgid "The order of discounts has been updated."
msgid "The order of sales channels has been updated."
msgstr "Kolejność rabatów została zaktualizowana."
msgstr "Kolejność kanałów sprzedaży została zaktualizowana."
#: pretix/control/views/pdf.py:83
msgid "The uploaded PDF file is too large."
@@ -28613,13 +28567,6 @@ msgid "Your PayPal account has been disconnected."
msgstr "Twoje konto PayPal zostało odłączone."
#: pretix/plugins/paypal2/apps.py:40
#, fuzzy
#| msgid ""
#| "Accept payments with your PayPal account. In addition to regular PayPal "
#| "payments, you can now also offer payments in a variety of local payment "
#| "methods such as giropay, SOFORT, iDEAL and many more to your customers - "
#| "they don't even need a PayPal account. PayPal is one of the most popular "
#| "payment methods world-wide."
msgid ""
"Accept payments with your PayPal account. In addition to regular PayPal "
"payments, you can now also offer payments in a variety of local payment "
@@ -28629,9 +28576,9 @@ msgid ""
msgstr ""
"Akceptuj płatności za pomocą konta PayPal. Oprócz zwykłych płatności PayPal, "
"możesz teraz oferować swoim klientom płatności za pomocą różnych lokalnych "
"metod płatności, takich jak giropay, SOFORT, iDEAL i wiele innych - nie "
"potrzebują oni nawet konta PayPal. PayPal jest jedną z najpopularniejszych "
"metod płatności na całym świecie."
"metod płatności, takich jak eps, iDEAL i wiele innych - nie potrzebują oni "
"nawet konta PayPal. PayPal jest jedną z najpopularniejszych metod płatności "
"na całym świecie."
#: pretix/plugins/paypal2/payment.py:99
msgid "PayPal Merchant ID"
@@ -28652,14 +28599,6 @@ msgid "Alternative Payment Methods"
msgstr "Alternatywne metody płatności"
#: pretix/plugins/paypal2/payment.py:151
#, fuzzy
#| msgid ""
#| "In addition to payments through a PayPal account, you can also offer your "
#| "customers the option to pay with credit cards and other, local payment "
#| "methods such as SOFORT, giropay, iDEAL, and many more - even when they do "
#| "not have a PayPal account. Eligible payment methods will be determined "
#| "based on the shoppers location. For German merchants, this is the direct "
#| "successor of PayPal Plus."
msgid ""
"In addition to payments through a PayPal account, you can also offer your "
"customers the option to pay with credit cards and other, local payment "
@@ -28670,10 +28609,10 @@ msgid ""
msgstr ""
"Oprócz płatności za pośrednictwem konta PayPal, możesz również zaoferować "
"swoim klientom możliwość płacenia kartami kredytowymi i innymi lokalnymi "
"metodami płatności, takimi jak SOFORT, giropay, iDEAL i wiele innych - nawet "
"jeśli nie mają konta PayPal. Kwalifikujące się metody płatności zostaną "
"określone na podstawie lokalizacji kupującego. Dla niemieckich sprzedawców "
"jest to bezpośredni następca PayPal Plus."
"metodami płatności, takimi jak eps, iDEAL i wiele innych - nawet jeśli nie "
"mają konta PayPal. Kwalifikujące się metody płatności zostaną określone na "
"podstawie lokalizacji kupującego. Dla niemieckich sprzedawców jest to "
"bezpośredni następca PayPal Plus."
#: pretix/plugins/paypal2/payment.py:166
msgid "Disable SEPA Direct Debit"
@@ -29880,11 +29819,6 @@ msgid "Stripe"
msgstr "Stripe"
#: pretix/plugins/stripe/apps.py:40
#, fuzzy
#| msgid ""
#| "Accept payments via Stripe, a globally popular payment service provider. "
#| "Stripe supports payments via credit cards as well as many local payment "
#| "methods such as giropay, iDEAL, Alipay,and many more."
msgid ""
"Accept payments via Stripe, a globally popular payment service provider. "
"Stripe supports payments via credit cards as well as many local payment "
@@ -33278,6 +33212,9 @@ msgid ""
"been added to the waiting list. We will only contact you once a spot opens "
"up."
msgstr ""
"Po dodaniu do listy oczekujących <strong>nie</strong> otrzymasz wiadomość e-"
"mail z potwierdzeniem. Skontaktujemy się z Tobą dopiero, gdy zwolni się "
"miejsce."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:44
msgid "Add me to the list"

File diff suppressed because it is too large Load Diff

View File

@@ -119,6 +119,7 @@ class CheckInListMixin(BaseExporter):
choices=[
('name', _('Attendee name')),
('code', _('Order code')),
('order_datetime', _('Order date')),
] + ([
('name:{}'.format(k), _('Attendee name: {part}').format(part=label))
for k, label, w in name_scheme['fields']
@@ -229,6 +230,8 @@ class CheckInListMixin(BaseExporter):
)
elif sort == 'code':
qs = qs.order_by(*o, 'order__code')
elif sort == 'order_datetime':
qs = qs.order_by(*o, '-order__datetime')
elif sort.startswith('name:'):
part = sort[5:]
qs = qs.annotate(
@@ -516,6 +519,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
headers.append(_('Order time'))
headers.append(_('Requires special attention'))
headers.append(_('Comment'))
headers.append(_('Check-in text'))
headers.append(_('Seat ID'))
headers.append(_('Seat name'))
headers.append(_('Seat zone'))
@@ -623,6 +627,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row.append(op.order.datetime.astimezone(self.event.timezone).strftime('%H:%M:%S'))
row.append(_('Yes') if op.require_checkin_attention else _('No'))
row.append(op.order.comment or "")
row.append("\n".join(text for text in [op.order.checkin_text, op.item.checkin_text] if text))
if op.seat:
row += [

View File

@@ -949,6 +949,9 @@ class PaypalMethod(BasePaymentProvider):
}
})
response = self.client.execute(req)
except KeyError:
raise PaymentException(_('Refunding the amount via PayPal failed: The original payment does not contain '
'the required information to issue an automated refund.'))
except IOError as e:
refund.order.log_action('pretix.event.order.refund.failed', {
'local_id': refund.local_id,

View File

@@ -163,7 +163,7 @@ def signal_process_response(sender, request: HttpRequest, response: HttpResponse
# 'frame-src': ['https://www.paypal.com', 'https://www.sandbox.paypal.com', "'nonce-{}'".format(_nonce(request))],
'frame-src': ['https:', "'nonce-{}'".format(_nonce(request))],
'connect-src': ['https://www.paypal.com', 'https://www.sandbox.paypal.com'], # Or not - seems to only affect PayPal logging...
'img-src': ['https://t.paypal.com'],
'img-src': ['https://t.paypal.com', 'https://www.paypalobjects.com'],
'style-src': ["'unsafe-inline'"] # PayPal does not comply with our nonce unfortunately, see Z#23113213
}

View File

@@ -84,6 +84,7 @@ seatingframe_html_head = EventPluginSignal()
"""
Arguments: ``request``
**Temporary workaround, might be removed again later.**
This signal allows you to put code inside the HTML ``<head>`` tag
of the seatingframe page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.

View File

@@ -151,7 +151,7 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
),
).filter(
variation_q,
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=channel),
active=True,
quotas__isnull=False,
subevent_disabled=False
@@ -685,7 +685,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
add_subevents_for_days(
filter_qs_by_attr(
self.request.event.subevents_annotated(
self.request.sales_channel.identifier,
self.request.sales_channel,
voucher,
).using(settings.DATABASE_REPLICA),
self.request
@@ -744,7 +744,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
add_subevents_for_days(
filter_qs_by_attr(
self.request.event.subevents_annotated(
self.request.sales_channel.identifier,
self.request.sales_channel,
voucher=voucher,
).using(settings.DATABASE_REPLICA),
self.request
@@ -793,7 +793,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['subevent_list'] = self.request.event.subevents_sorted(
filter_qs_by_attr(
self.request.event.subevents_annotated(
self.request.sales_channel.identifier,
self.request.sales_channel,
voucher=voucher,
).using(settings.DATABASE_REPLICA),
self.request

View File

@@ -185,7 +185,7 @@ class EventListMixin:
def _get_event_list_queryset(self):
query = Q(is_public=True) & Q(live=True)
qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query)
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier))
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel))
qs = qs.annotate(
min_from=Min('subevents__date_from'),
min_to=Min('subevents__date_to'),
@@ -213,7 +213,7 @@ class EventListMixin:
).order_by('order_from')
qs = Event.annotated(filter_qs_by_attr(
qs, self.request, match_subevents_with_conditions=Q(active=True) & Q(is_public=True) & date_q
))
), self.request.sales_channel)
return qs
def _set_month_to_next_subevent(self):
@@ -724,10 +724,10 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) | Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__all_sales_channels=True) | Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -746,14 +746,14 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
def _events_by_day(self, before, after):
ebd = defaultdict(list)
timezones = set()
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
settings.DATABASE_REPLICA
).filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
), before, after, ebd, timezones)
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -768,7 +768,7 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
)
)
)
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
self._multiple_timezones = len(timezones) > 1
return ebd
@@ -807,11 +807,11 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -842,14 +842,14 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
def _events_by_day(self, before, after):
ebd = defaultdict(list)
timezones = set()
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
settings.DATABASE_REPLICA
).filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
), before, after, ebd, timezones)
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -864,7 +864,7 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
)
)
)
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
self._multiple_timezones = len(timezones) > 1
return ebd
@@ -946,11 +946,11 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -1194,14 +1194,14 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
def _events_by_day(self, before, after):
ebd = defaultdict(list)
timezones = set()
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
settings.DATABASE_REPLICA
).filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
), before, after, ebd, timezones)
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
@@ -1216,7 +1216,7 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
)
)
)
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
self._multiple_timezones = len(timezones) > 1
return ebd
@@ -1229,7 +1229,7 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
filter_qs_by_attr(
self.request.organizer.events.filter(
Q(date_from__gt=cutoff) | Q(date_to__gt=cutoff),
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
is_public=True,
live=True,
has_subevents=False,
@@ -1250,7 +1250,7 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
SubEvent.objects.filter(
Q(date_from__gt=cutoff) | Q(date_to__gt=cutoff),
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,

View File

@@ -545,9 +545,9 @@ class WidgetAPIProductList(EventListMixin, View):
if hasattr(self.request, 'event'):
add_subevents_for_days(
filter_qs_by_attr(
self.request.event.subevents_annotated('web').filter(
self.request.event.subevents_annotated(self.request.sales_channel).filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
Q(event__limit_sales_channels=self.request.sales_channel),
), self.request
),
limit_before, after, ebd, set(), self.request.event,
@@ -558,8 +558,8 @@ class WidgetAPIProductList(EventListMixin, View):
add_events_for_days(
self.request,
filter_qs_by_attr(
Event.annotated(self.request.organizer.events, 'web').filter(
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
Event.annotated(self.request.organizer.events, self.request.sales_channel).filter(
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
), self.request
),
limit_before, after, ebd, timezones
@@ -572,7 +572,7 @@ class WidgetAPIProductList(EventListMixin, View):
event__live=True,
).prefetch_related(
'event___settings_objects', 'event__organizer___settings_objects'
)), self.request), limit_before, after, ebd, timezones)
), self.request.sales_channel), self.request), limit_before, after, ebd, timezones)
data['weeks'] = weeks_for_template(ebd, self.year, self.month)
for w in data['weeks']:
@@ -605,7 +605,7 @@ class WidgetAPIProductList(EventListMixin, View):
ebd = defaultdict(list)
if hasattr(self.request, 'event'):
add_subevents_for_days(
filter_qs_by_attr(self.request.event.subevents_annotated('web'), self.request),
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request),
limit_before, after, ebd, set(), self.request.event,
kwargs.get('cart_namespace')
)
@@ -613,7 +613,7 @@ class WidgetAPIProductList(EventListMixin, View):
timezones = set()
add_events_for_days(
self.request,
filter_qs_by_attr(Event.annotated(self.request.organizer.events, 'web'), self.request),
filter_qs_by_attr(Event.annotated(self.request.organizer.events, self.request.sales_channel), self.request),
limit_before, after, ebd, timezones
)
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
@@ -622,7 +622,7 @@ class WidgetAPIProductList(EventListMixin, View):
event__live=True,
).prefetch_related(
'event___settings_objects', 'event__organizer___settings_objects'
)), self.request), limit_before, after, ebd, timezones)
), self.request.sales_channel), self.request), limit_before, after, ebd, timezones)
data['days'] = days_for_template(ebd, week)
for d in data['days']:
@@ -632,7 +632,7 @@ class WidgetAPIProductList(EventListMixin, View):
limit = 50
if hasattr(self.request, 'event'):
evs = filter_qs_by_attr(
self.request.event.subevents_annotated(self.request.sales_channel.identifier),
self.request.event.subevents_annotated(self.request.sales_channel),
self.request,
match_subevents_with_conditions=(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now() - timedelta(hours=24)))

View File

@@ -195,7 +195,7 @@ input[type=number]::-webkit-outer-spin-button {
}
.alert-danger::before {
background-color: $state-danger-border;
background-image: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2036%2036'%20xmlns='http://www.w3.org/2000/svg'%20xml:space='preserve'%3E%3Cpath%20d='M12.14%204.62h11.64l8.24%208.24V23.4l-8.24%208.24H12.14L3.9%2023.39V12.86l8.24-8.24Z'%20fill='%23fff'/%3E%3Cpath%20d='M24.74%2022.6c0-.28-.11-.56-.31-.76l-3.27-3.27%203.27-3.27a1.08%201.08%200%200%200%200-1.52l-1.51-1.5a1.08%201.08%200%200%200-1.52%200l-3.27%203.26-3.27-3.27a1.08%201.08%200%200%200-1.52%200l-1.5%201.51a1.08%201.08%200%200%200%200%201.52l3.26%203.27-3.27%203.27a1.08%201.08%200%200%200%200%201.52l1.51%201.51a1.08%201.08%200%200%200%201.52%200l3.27-3.27%203.27%203.27a1.08%201.08%200%200%200%201.52%200l1.51-1.51c.2-.2.31-.48.31-.76Z'%20fill='#{url-friendly-colour($state-danger-text)}'/%3E%3C/svg%3E%0A");
background-image: url('data:image/svg+xml,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M12.14 4.62h11.64l8.24 8.24V23.4l-8.24 8.24H12.14L3.9 23.39V12.86l8.24-8.24Zm12.6 17.98c0-.28-.11-.56-.31-.76l-3.27-3.27 3.27-3.27a1.085 1.085 0 0 0 0-1.52l-1.51-1.5a1.085 1.085 0 0 0-1.52 0l-3.27 3.26-3.27-3.27a1.085 1.085 0 0 0-1.52 0l-1.5 1.51a1.085 1.085 0 0 0 0 1.52l3.26 3.27-3.27 3.27a1.085 1.085 0 0 0 0 1.52l1.51 1.51a1.085 1.085 0 0 0 1.52 0l3.27-3.27 3.27 3.27a1.085 1.085 0 0 0 1.52 0l1.51-1.51c.2-.2.31-.48.31-.76Z" style="fill:%23fff"/></svg>');
}
.alert-primary::before {
background: $brand-primary !important;

View File

@@ -1381,3 +1381,18 @@ def test_checkin_pdf_data_requires_permission(token_client, event, team, organiz
organizer.slug, event.slug, clist_all.pk
))
assert not resp.data['results'][0].get('pdf_data')
@pytest.mark.django_db
def test_expand(token_client, organizer, event, clist, clist_all, item, other_item, order, django_assert_max_num_queries):
with scopes_disabled():
op = order.positions.first()
var1 = item.variations.create(value="XS")
op.variation = var1
op.save()
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?search=z3fsn8jyu&expand=variation'.format(
organizer.slug, event.slug, clist_all.pk
))
assert resp.status_code == 200
assert 'value' in resp.data['results'][0]['variation']

View File

@@ -763,6 +763,50 @@ def test_event_update(token_client, organizer, event, item, meta_prop):
assert cnt == event.all_logentries().count()
@pytest.mark.django_db
def test_event_update_plugins_validation(token_client, organizer, event, item, meta_prop):
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["pretix.plugins.paypal2", "unknown"]
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {"plugins": ["Unknown plugin: 'unknown'."]}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["pretix.plugins.paypal2", "tests.testdummyhidden"]
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {"plugins": ["Unknown plugin: 'tests.testdummyhidden'."]}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["pretix.plugins.paypal2", "tests.testdummyrestricted"]
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {"plugins": ["Restricted plugin: 'tests.testdummyrestricted'."]}
organizer.settings.allowed_restricted_plugins = ["tests.testdummyrestricted"]
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["pretix.plugins.paypal2", "tests.testdummyrestricted"]
},
format='json'
)
assert resp.status_code == 200
@pytest.mark.django_db
def test_event_test_mode(token_client, organizer, event):
resp = token_client.patch(

View File

@@ -86,6 +86,18 @@ def test_giftcard_list(token_client, organizer, event, giftcard, other_giftcard)
assert resp.status_code == 200
assert 2 == len(resp.data['results'])
resp = token_client.get('/api/v1/organizers/{}/giftcards/?expired=false'.format(organizer.slug))
assert 1 == len(resp.data['results'])
resp = token_client.get('/api/v1/organizers/{}/giftcards/?expired=true'.format(organizer.slug))
assert 0 == len(resp.data['results'])
resp = token_client.get('/api/v1/organizers/{}/giftcards/?value=23.00'.format(organizer.slug))
assert 1 == len(resp.data['results'])
resp = token_client.get('/api/v1/organizers/{}/giftcards/?value=23'.format(organizer.slug))
assert 1 == len(resp.data['results'])
resp = token_client.get('/api/v1/organizers/{}/giftcards/?value=24'.format(organizer.slug))
assert 0 == len(resp.data['results'])
@pytest.mark.django_db
def test_giftcard_detail(token_client, organizer, event, giftcard):

View File

@@ -370,6 +370,13 @@ def test_item_list(token_client, organizer, event, team, item):
assert resp.status_code == 200
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?search=Budget'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?search=Free'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_item_detail(token_client, organizer, event, team, item):
@@ -590,7 +597,28 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
"meta_data": {
"day": "Wednesday",
},
}
},
{
"value": {
"de": "web",
"en": "web"
},
"active": True,
"require_approval": True,
"checkin_attention": False,
"checkin_text": None,
"require_membership": False,
"require_membership_hidden": False,
"require_membership_types": [],
"description": None,
"position": 0,
"default_price": None,
"sales_channels": ["web"],
"price": "23.00",
"meta_data": {
"day": "Wednesday",
},
},
]
},
format='json'
@@ -604,6 +632,8 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
assert new_item.variations.first().all_sales_channels is True
assert not new_item.variations.first().limit_sales_channels.exists()
assert new_item.variations.first().meta_data == {"day": "Wednesday"}
assert new_item.variations.last().all_sales_channels is False
assert new_item.variations.last().limit_sales_channels.exists()
@pytest.mark.django_db
@@ -1389,6 +1419,21 @@ def test_variations_list(token_client, organizer, event, item, variation):
assert res['position'] == resp.data['results'][0]['position']
assert res['price'] == resp.data['results'][0]['price']
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/variations/?active=true'.format(organizer.slug, event.slug, item.pk))
assert resp.status_code == 200
assert res['value'] == resp.data['results'][0]['value']
resp = token_client.get(
'/api/v1/organizers/{}/events/{}/items/{}/variations/?active=false'.format(organizer.slug, event.slug, item.pk))
assert resp.status_code == 200
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/variations/?search=Child'.format(organizer.slug, event.slug, item.pk))
assert resp.status_code == 200
assert res['value'] == resp.data['results'][0]['value']
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/variations/?search=Incorrect'.format(organizer.slug, event.slug, item.pk))
assert resp.status_code == 200
assert [] == resp.data['results']
@pytest.mark.django_db
def test_variations_detail(token_client, organizer, event, item, variation):

View File

@@ -414,6 +414,16 @@ def test_order_list(token_client, organizer, event, order, item, taxrule, questi
'/api/v1/organizers/{}/events/{}/orders/?email=foo@example.org'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?payment_provider=banktransfer'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?payment_provider=manual'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?sales_channel=web'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?sales_channel=bar'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?locale=en'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?locale=de'.format(organizer.slug, event.slug))
@@ -434,6 +444,36 @@ def test_order_list(token_client, organizer, event, order, item, taxrule, questi
))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_since={}'.format(
organizer.slug, event.slug,
(order.datetime - datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_since={}'.format(
organizer.slug, event.slug, order.datetime.isoformat().replace('+00:00', 'Z')
))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_since={}'.format(
organizer.slug, event.slug,
(order.datetime + datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_before={}'.format(
organizer.slug, event.slug,
(order.datetime - datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_before={}'.format(
organizer.slug, event.slug, order.datetime.isoformat().replace('+00:00', 'Z')
))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?created_before={}'.format(
organizer.slug, event.slug,
(order.datetime + datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?include_canceled_positions=false'.format(organizer.slug, event.slug))
assert resp.status_code == 200
assert len(resp.data['results'][0]['positions']) == 1

View File

@@ -2329,8 +2329,8 @@ class EventTest(TestCase):
item2 = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=False)
q.items.add(item)
q.items.add(item2)
assert Event.annotated(Event.objects).first().active_quotas == [q]
assert Event.annotated(Event.objects, 'foo').first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
assert Event.annotated(Event.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_product_inactive(self):
@@ -2341,7 +2341,7 @@ class EventTest(TestCase):
q = Quota.objects.create(event=event, name='Quota', size=2)
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=False)
q.items.add(item)
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_product_hidden_by_voucher(self):
@@ -2354,16 +2354,16 @@ class EventTest(TestCase):
q.items.add(item)
voucher = Voucher.objects.create(event=event, code='a', item=item, show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
voucher = Voucher.objects.create(event=event, code='b', item=item, show_hidden_items=False)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == []
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == []
voucher = Voucher.objects.create(event=event, code='c', show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
voucher = Voucher.objects.create(event=event, code='d', quota=q, show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
item2 = Item.objects.create(event=event, name='Early-bird ticket', default_price=0)
var = item2.variations.create(item=item2, value='Test', hide_without_voucher=True)
@@ -2373,13 +2373,13 @@ class EventTest(TestCase):
q.variations.add(var)
voucher = Voucher.objects.create(event=event, code='e', item=item2, variation=var, show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
voucher = Voucher.objects.create(event=event, code='f', item=item2, variation=var2, show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == []
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == []
voucher = Voucher.objects.create(event=event, code='g', quota=q, show_hidden_items=True)
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
@classscope(attr='organizer')
def test_active_quotas_annotation_product_addon(self):
@@ -2395,7 +2395,7 @@ class EventTest(TestCase):
item.category = cat
item.save()
q.items.add(item)
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_product_unavailable(self):
@@ -2407,7 +2407,7 @@ class EventTest(TestCase):
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=True,
available_until=now() - timedelta(days=1))
q.items.add(item)
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_variation_not_in_quota(self):
@@ -2419,7 +2419,7 @@ class EventTest(TestCase):
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=True)
item.variations.create(value="foo")
q.items.add(item)
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_variation(self):
@@ -2434,29 +2434,29 @@ class EventTest(TestCase):
v.limit_sales_channels.add(self.organizer.sales_channels.get(identifier="web"))
q.items.add(item)
q.variations.add(v)
assert Event.annotated(Event.objects).first().active_quotas == [q]
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
item.available_until = now() - timedelta(days=1)
item.save()
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
item.available_until = None
item.available_from = now() + timedelta(days=1)
item.save()
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
item.available_until = None
item.available_from = None
item.active = False
item.save()
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
item.active = True
item.save()
assert Event.annotated(Event.objects).first().active_quotas == [q]
assert Event.annotated(Event.objects, 'foo').first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
assert Event.annotated(Event.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
v.active = False
v.save()
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
item.hide_without_voucher = True
item.save()
assert Event.annotated(Event.objects).first().active_quotas == []
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
class SubEventTest(TestCase):
@@ -2502,8 +2502,10 @@ class SubEventTest(TestCase):
all_sales_channels=False)
item.limit_sales_channels.add(self.organizer.sales_channels.get(identifier="web"))
q.items.add(item)
assert SubEvent.annotated(SubEvent.objects).first().active_quotas == [q]
assert SubEvent.annotated(SubEvent.objects, 'foo').first().active_quotas == []
assert SubEvent.annotated(SubEvent.objects, 'web').first().active_quotas == [q]
assert SubEvent.annotated(SubEvent.objects, 'bar').first().active_quotas == []
assert SubEvent.annotated(SubEvent.objects, self.organizer.sales_channels.get(identifier="web")).first().active_quotas == [q]
assert SubEvent.annotated(SubEvent.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
@classscope(attr='organizer')
def test_active_quotas_annotation_no_interference(self):
@@ -2514,8 +2516,8 @@ class SubEventTest(TestCase):
subevent=se2)
item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=0, active=True)
q.items.add(item)
assert SubEvent.annotated(SubEvent.objects).filter(pk=self.se.pk).first().active_quotas == []
assert SubEvent.annotated(SubEvent.objects).filter(pk=se2.pk).first().active_quotas == [q]
assert SubEvent.annotated(SubEvent.objects, 'web').filter(pk=self.se.pk).first().active_quotas == []
assert SubEvent.annotated(SubEvent.objects, 'web').filter(pk=se2.pk).first().active_quotas == [q]
@classscope(attr='organizer')
def test_best_availability(self):
@@ -2540,7 +2542,7 @@ class SubEventTest(TestCase):
q = Quota.objects.create(event=self.event, name='Quota', size=1,
subevent=self.se)
q.items.add(item)
obj = SubEvent.annotated(SubEvent.objects).first()
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
assert len(obj.active_quotas) == 1
assert obj.best_availability == (Quota.AVAILABILITY_GONE, 0, 1)
@@ -2548,14 +2550,14 @@ class SubEventTest(TestCase):
q2 = Quota.objects.create(event=self.event, name='Quota 2', size=2,
subevent=self.se)
q2.items.add(item)
obj = SubEvent.annotated(SubEvent.objects).first()
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
assert len(obj.active_quotas) == 2
assert obj.best_availability == (Quota.AVAILABILITY_GONE, 0, 1)
# 2 quotas - 2 items. Higher quota wins since second item is only connected to second quota.
item2 = Item.objects.create(event=self.event, name='Regular ticket', default_price=10, active=True)
q2.items.add(item2)
obj = SubEvent.annotated(SubEvent.objects).first()
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
assert len(obj.active_quotas) == 2
assert obj.best_availability == (Quota.AVAILABILITY_OK, 1, 2)
assert obj.best_availability_is_low
@@ -2564,7 +2566,7 @@ class SubEventTest(TestCase):
q.size = 10
q.save()
q2.delete()
obj = SubEvent.annotated(SubEvent.objects).first()
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
assert len(obj.active_quotas) == 1
assert obj.best_availability == (Quota.AVAILABILITY_OK, 9, 10)
assert not obj.best_availability_is_low
@@ -2572,7 +2574,7 @@ class SubEventTest(TestCase):
# Unlimited quota
q.size = None
q.save()
obj = SubEvent.annotated(SubEvent.objects).first()
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
assert obj.best_availability == (Quota.AVAILABILITY_OK, None, None)
assert not obj.best_availability_is_low

View File

@@ -299,6 +299,8 @@ class EventsTest(SoupTest):
doc = self.get_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug))
self.assertIn("Stripe", doc.select(".form-plugins")[0].text)
self.assertIn("Enable", doc.select("[name=\"plugin:pretix.plugins.stripe\"]")[0].text)
assert not doc.select("[name=\"plugin:tests.testdummyrestricted\"]")
assert not doc.select("[name=\"plugin:tests.testdummyhidden\"]")
doc = self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug),
{'plugin:pretix.plugins.stripe': 'enable'})
@@ -308,6 +310,23 @@ class EventsTest(SoupTest):
{'plugin:pretix.plugins.stripe': 'disable'})
self.assertIn("Enable", doc.select("[name=\"plugin:pretix.plugins.stripe\"]")[0].text)
self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug),
{'plugin:tests.testdummyhidden': 'enable'})
self.event1.refresh_from_db()
assert "testdummyhidden" not in self.event1.plugins
self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug),
{'plugin:tests.testdummyrestricted': 'enable'})
self.event1.refresh_from_db()
assert "testdummyrestricted" not in self.event1.plugins
self.orga1.settings.allowed_restricted_plugins = ["tests.testdummyrestricted"]
self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug),
{'plugin:tests.testdummyrestricted': 'enable'})
self.event1.refresh_from_db()
assert "testdummyrestricted" in self.event1.plugins
def test_testmode_enable(self):
self.event1.testmode = False
self.event1.save()

View File

@@ -91,12 +91,12 @@ def test_csv_simple(event):
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title","Attendee name:
First name","Attendee name: Middle name","Attendee name: Family name","Product","Price","Checked in","Checked out","Automatically
checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special attention",
"Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until","Address","ZIP code",
"Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until","Address","ZIP code",
"City","Country","State"
"FOO","Mr Peter A Jones","Mr","Peter","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"FOO","Mrs Andrea J Zulu","Mrs","Andrea","J","Zulu","Ticket","13.00","","","No","ggsngqtnmhx74jswjngw3fk8pfwz2a7k",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
@@ -113,12 +113,12 @@ def test_csv_order_by_name_parts(event): # noqa
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title",
"Attendee name: First name","Attendee name: Middle name","Attendee name: Family name","Product","Price",
"Checked in","Checked out","Automatically checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special
attention","Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
attention","Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
"Address","ZIP code","City","Country","State"
"FOO","Mrs Andrea J Zulu","Mrs","Andrea","J","Zulu","Ticket","13.00","","","No","ggsngqtnmhx74jswjngw3fk8pfwz2a7k",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"FOO","Mr Peter A Jones","Mr","Peter","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
c = CSVCheckinList(event, organizer=event.organizer)
_, _, content = c.render({
@@ -131,12 +131,12 @@ def test_csv_order_by_name_parts(event): # noqa
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title",
"Attendee name: First name","Attendee name: Middle name","Attendee name: Family name","Product","Price",
"Checked in","Checked out","Automatically checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special
attention","Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
attention","Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
"Address","ZIP code","City","Country","State"
"FOO","Mr Peter A Jones","Mr","Peter","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"FOO","Mrs Andrea J Zulu","Mrs","Andrea","J","Zulu","Ticket","13.00","","","No","ggsngqtnmhx74jswjngw3fk8pfwz2a7k",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
@@ -184,12 +184,12 @@ def test_csv_order_by_inherited_name_parts(event): # noqa
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title",
"Attendee name: First name","Attendee name: Middle name","Attendee name: Family name","Product","Price",
"Checked in","Checked out","Automatically checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special
attention","Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
attention","Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
"Address","ZIP code","City","Country","State"
"BAR","Mr Albert J Zulu","Mr","Albert","J","Zulu","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytyy",
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"FOO","Mr Paul A Jones","Mr","Paul","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
c = CSVCheckinList(event, organizer=event.organizer)
_, _, content = c.render({
@@ -202,12 +202,12 @@ def test_csv_order_by_inherited_name_parts(event): # noqa
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title",
"Attendee name: First name","Attendee name: Middle name","Attendee name: Family name","Product","Price",
"Checked in","Checked out","Automatically checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special
attention","Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
attention","Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
"Address","ZIP code","City","Country","State"
"BAR","Mr Albert J Zulu","Mr","Albert","J","Zulu","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytyy",
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"FOO","Mr Paul A Jones","Mr","Paul","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
c = CSVCheckinList(event, organizer=event.organizer)
_, _, content = c.render({
@@ -220,10 +220,56 @@ def test_csv_order_by_inherited_name_parts(event): # noqa
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title",
"Attendee name: First name","Attendee name: Middle name","Attendee name: Family name","Product","Price",
"Checked in","Checked out","Automatically checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special
attention","Comment","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
attention","Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until",
"Address","ZIP code","City","Country","State"
"FOO","Mr Paul A Jones","Mr","Paul","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","FOOCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
"BAR","Mr Albert J Zulu","Mr","Albert","J","Zulu","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytyy",
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","",""
"dummy@dummy.test","'+498912345678","BARCORP","","2019-02-22","14:00:00","No","","","","","","","","","","","","","","",""
""")
@pytest.mark.django_db
def test_csv_order_by_orderdatetime(event):
order1 = event.orders.first()
order1.checkin_text = 'meow'
order1.save()
order2 = Order.objects.create(
code='FOO2', event=event, email='dummy@dummy.test', phone="+498912345678",
status=Order.STATUS_PAID,
datetime=datetime.datetime(2019, 2, 22, 22, 0, 0, tzinfo=datetime.timezone.utc),
expires=now() + datetime.timedelta(days=10),
total=33, locale='en', checkin_text='beep',
sales_channel=event.organizer.sales_channels.get(identifier="web"),
)
item_ticket = Item.objects.create(event=event, name="Ticket2", default_price=23, admission=True, checkin_text='boop')
OrderPosition.objects.create(
order=order2,
item=item_ticket,
variation=None,
price=Decimal("23"),
attendee_name_parts={"title": "Mx", "given_name": "Alex", "middle_name": "F", "family_name": "Nord"},
secret='asdfasdfasdfasdfasdfasdfasfdasdf'
)
c = CSVCheckinList(event, organizer=event.organizer)
_, _, content = c.render({
'list': event.checkin_lists.first().pk,
'secrets': True,
'sort': 'order_datetime',
'_format': 'default',
'questions': []
})
assert clean(content.decode()) == clean(""""Order code","Attendee name","Attendee name: Title","Attendee name:
First name","Attendee name: Middle name","Attendee name: Family name","Product","Price","Checked in","Checked out","Automatically
checked in","Secret","E-mail","Phone number","Company","Voucher code","Order date","Order time","Requires special attention",
"Comment","Check-in text","Seat ID","Seat name","Seat zone","Seat row","Seat number","Blocked","Valid from","Valid until","Address","ZIP code",
"City","Country","State"
"FOO2","Mx Alex F Nord","Mx","Alex","F","Nord","Ticket2","23.00","","","No","asdfasdfasdfasdfasdfasdfasfdasdf",
"dummy@dummy.test","'+498912345678","","","2019-02-22","22:00:00","No","","beep\nboop","","","","","","","","","","","","",""
"FOO","Mr Peter A Jones","Mr","Peter","A","Jones","Ticket","23.00","","","No","hutjztuxhkbtwnesv2suqv26k6ttytxx",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","meow","","","","","","","","","","","","",""
"FOO","Mrs Andrea J Zulu","Mrs","Andrea","J","Zulu","Ticket","13.00","","","No","ggsngqtnmhx74jswjngw3fk8pfwz2a7k",
"dummy@dummy.test","'+498912345678","","","2019-02-22","14:00:00","No","","meow","","","","","","","","","","","","",""
""")

View File

@@ -28,6 +28,8 @@ TEST_DIR = os.path.dirname(__file__)
TEMPLATES[0]['DIRS'].append(os.path.join(TEST_DIR, 'templates')) # NOQA
INSTALLED_APPS.append('tests.testdummy') # NOQA
INSTALLED_APPS.append('tests.testdummyrestricted') # NOQA
INSTALLED_APPS.append('tests.testdummyhidden') # NOQA
PRETIX_AUTH_BACKENDS = [
'pretix.base.auth.NativeAuthBackend',

View File

@@ -0,0 +1,21 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#

View File

@@ -0,0 +1,35 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.apps import AppConfig
class TestDummyHiddenApp(AppConfig):
name = 'tests.testdummyhidden'
verbose_name = 'testdummyhidden'
class PretixPluginMeta:
name = 'testdummyhidden'
version = '1.0.0'
restricted = True
def is_available(self, event):
return False

View File

@@ -0,0 +1,21 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#

View File

@@ -0,0 +1,32 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.apps import AppConfig
class TestDummyRestrictedApp(AppConfig):
name = 'tests.testdummyrestricted'
verbose_name = 'testdummyrestricted'
class PretixPluginMeta:
name = 'testdummyrestricted'
version = '1.0.0'
restricted = True