New implementation of sales channels (#4111)

Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
Raphael Michel
2024-06-30 19:24:30 +02:00
committed by GitHub
parent 95511b0330
commit 4fb5c6bef0
174 changed files with 2902 additions and 616 deletions

View File

@@ -249,7 +249,7 @@ class CustomerStep(CartMixin, TemplateFlowStep):
icon = 'user'
def is_applicable(self, request):
return request.organizer.settings.customer_accounts and request.sales_channel.customer_accounts_supported
return request.organizer.settings.customer_accounts and request.sales_channel.type_instance.customer_accounts_supported
@cached_property
def login_form(self):
@@ -539,7 +539,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
self.request.event,
subevent=cartpos.subevent,
voucher=None,
channel=self.request.sales_channel.identifier,
channel=self.request.sales_channel,
base_qs=iao.addon_category.items,
allow_addons=True,
quota_cache=quota_cache,
@@ -935,7 +935,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
event=self.request.event,
cart_id=get_or_create_cart_id(request),
invoice_address=addr,
sales_channel=request.sales_channel.identifier,
sales_channel=request.sales_channel,
)
diff = cm.recompute_final_prices_and_taxes()
except TaxRule.SaleNotAllowed:

View File

@@ -51,7 +51,10 @@ class WaitingListForm(forms.ModelForm):
('', '')
]
items, display_add_to_cart = get_grouped_items(
self.event, self.instance.subevent, require_seat=None,
self.event,
subevent=self.instance.subevent,
require_seat=None,
channel=self.event.organizer.sales_channels.get(identifier="web"),
memberships=(
customer.usable_memberships(
for_event=self.instance.subevent or self.event,

View File

@@ -36,7 +36,6 @@ from django.template.response import TemplateResponse
from django.urls import resolve
from django_scopes import scope
from pretix.base.channels import WebshopSalesChannel
from pretix.base.timemachine import time_machine_now_assigned_from_request
from pretix.presale.signals import process_response
@@ -57,10 +56,6 @@ class EventMiddleware:
url = resolve(request.path_info)
request._namespace = url.namespace
if not hasattr(request, 'sales_channel'):
# The environ lookup is only relevant during unit testing
request.sales_channel = request.environ.get('PRETIX_SALES_CHANNEL', WebshopSalesChannel())
if url.namespace != 'presale':
return self.get_response(request)
@@ -71,6 +66,12 @@ class EventMiddleware:
with scope(organizer=getattr(request, 'organizer', None)), \
time_machine_now_assigned_from_request(request):
if not hasattr(request, 'sales_channel') and hasattr(request, 'organizer'):
# The environ lookup is only relevant during unit testing
request.sales_channel = request.organizer.sales_channels.get(
identifier=request.environ.get('PRETIX_SALES_CHANNEL', 'web')
)
response = self.get_response(request)
if hasattr(request, '_namespace') and request._namespace == 'presale' and hasattr(request, 'event'):

View File

@@ -120,7 +120,7 @@
<div class="clearfix"></div>
</div>
{% if request.event.testmode %}
{% if request.sales_channel.testmode_supported %}
{% if request.sales_channel.type_instance.testmode_supported %}
<div class="alert alert-warning">
<p><strong>
<span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>
@@ -194,7 +194,7 @@
</div>
{% endif %}
{% if request.event.testmode %}
{% if request.sales_channel.testmode_supported %}
{% if request.sales_channel.type_instance.testmode_supported %}
<div class="alert alert-testmode alert-warning">
<p><strong>
<span class="sr-only">{% trans "Warning" context "alert-messages" %}:</span>

View File

@@ -89,7 +89,7 @@
<div class="alert alert-info">
<p>{{ p.provider.test_mode_message }}</p>
</div>
{% if not request.sales_channel.testmode_supported %}
{% if not request.sales_channel.type_instance.testmode_supported %}
<div class="alert alert-danger">
<p>
{% trans "This sales channel does not provide support for test mode." %}

View File

@@ -415,6 +415,12 @@ def _event_view(function=None, require_live=True, require_plugin=None):
else:
with scope(organizer=getattr(request, 'organizer', None)), \
time_machine_now_assigned_from_request(request):
if not hasattr(request, 'sales_channel') and hasattr(request, 'organizer'):
# The environ lookup is only relevant during unit testing
request.sales_channel = request.organizer.sales_channels.get(
identifier=request.environ.get('PRETIX_SALES_CHANNEL', 'web')
)
response = func(request=request, *args, **kwargs)
if getattr(request, 'event', None):
for receiver, r in process_response.send(request.event, request=request, response=response):

View File

@@ -515,7 +515,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
return u
def post(self, request, *args, **kwargs):
if request.sales_channel.identifier not in request.event.sales_channels:
if not request.event.all_sales_channels and request.sales_channel.identifier not in (s.identifier for s in request.event.limit_sales_channels.all()):
raise Http404(_('Tickets for this event cannot be purchased on this sales channel.'))
cart_id = get_or_create_cart_id(self.request)
@@ -564,9 +564,9 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
# Fetch all items
items, display_add_to_cart = get_grouped_items(
self.request.event,
self.subevent,
subevent=self.subevent,
voucher=self.voucher,
channel=self.request.sales_channel.identifier,
channel=self.request.sales_channel,
memberships=(
self.request.customer.usable_memberships(
for_event=self.subevent or self.request.event,

View File

@@ -63,10 +63,9 @@ from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
from pretix.base.channels import get_all_sales_channels
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import (
ItemVariation, Quota, SeatCategoryMapping, Voucher,
ItemVariation, Quota, SalesChannel, SeatCategoryMapping, Voucher,
)
from pretix.base.models.event import Event, SubEvent
from pretix.base.models.items import (
@@ -110,7 +109,7 @@ def item_group_by_category(items):
)
def get_grouped_items(event, subevent=None, voucher=None, channel='web', require_seat=0, base_qs=None, allow_addons=False,
def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=None, require_seat=0, base_qs=None, allow_addons=False,
quota_cache=None, filter_items=None, filter_categories=None, memberships=None,
ignore_hide_sold_out_for_item_ids=None):
base_qs_set = base_qs is not None
@@ -152,8 +151,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
),
).filter(
variation_q,
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel.identifier),
active=True,
sales_channels__contains=channel,
quotas__isnull=False,
subevent_disabled=False
).prefetch_related(
@@ -192,7 +191,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
)
)
items = base_qs.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher, allow_addons=allow_addons).select_related(
items = base_qs.using(settings.DATABASE_REPLICA).filter_available(channel=channel.identifier, voucher=voucher, allow_addons=allow_addons).select_related(
'category', 'tax_rule', # for re-grouping
'hidden_if_available',
).prefetch_related(
@@ -244,7 +243,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
items = items.filter(category_id__in=[a for a in filter_categories if a.isdigit()])
display_add_to_cart = False
quota_cache_key = f'item_quota_cache:{subevent.id if subevent else 0}:{channel}:{bool(require_seat)}'
quota_cache_key = f'item_quota_cache:{subevent.id if subevent else 0}:{channel.identifier}:{bool(require_seat)}'
quota_cache = quota_cache or event.cache.get(quota_cache_key) or {}
quota_cache_existed = bool(quota_cache)
@@ -284,7 +283,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
item.available_variations = [v for v in item.available_variations
if v.pk == voucher.variation_id]
if get_all_sales_channels()[channel].unlimited_items_per_order:
if channel.type_instance.unlimited_items_per_order:
max_per_order = sys.maxsize
else:
max_per_order = item.max_per_order or int(event.settings.max_items_per_order)
@@ -527,7 +526,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
r._csp_ignore = True
return r
if request.sales_channel.identifier not in request.event.sales_channels:
if not request.event.all_sales_channels and request.sales_channel.identifier not in (s.identifier for s in request.event.limit_sales_channels.all()):
raise Http404(_('Tickets for this event cannot be purchased on this sales channel.'))
if request.event.has_subevents:
@@ -565,11 +564,12 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
if not self.request.event.has_subevents or self.subevent:
# Fetch all items
items, display_add_to_cart = get_grouped_items(
self.request.event, self.subevent,
self.request.event,
subevent=self.subevent,
filter_items=self.request.GET.getlist('item'),
filter_categories=self.request.GET.getlist('category'),
require_seat=None,
channel=self.request.sales_channel.identifier,
channel=self.request.sales_channel,
memberships=(
self.request.customer.usable_memberships(
for_event=self.subevent or self.request.event,

View File

@@ -705,7 +705,7 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
def can_generate_invoice(event, order, ignore_payments=False):
v = (
order.sales_channel in event.settings.get('invoice_generate_sales_channels')
order.sales_channel.identifier in event.settings.get('invoice_generate_sales_channels')
and (
event.settings.get('invoice_generate') in ('user', 'True')
or (

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(sales_channels__contains=self.request.sales_channel.identifier)
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier))
qs = qs.annotate(
min_from=Min('subevents__date_from'),
min_to=Min('subevents__date_to'),
@@ -724,13 +724,13 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) | Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
),
before,
after,
@@ -749,13 +749,14 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
settings.DATABASE_REPLICA
).filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
), 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),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
).prefetch_related(
Prefetch(
'event',
@@ -806,13 +807,14 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
),
before,
after,
@@ -843,13 +845,14 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
settings.DATABASE_REPLICA
).filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
), 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),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
).prefetch_related(
Prefetch(
'event',
@@ -943,13 +946,14 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
ctx['has_before'], ctx['has_after'] = has_before_after(
self.request.organizer.events.filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
),
SubEvent.objects.filter(
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
),
before,
after,
@@ -1193,13 +1197,14 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
settings.DATABASE_REPLICA
).filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
), 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),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
).prefetch_related(
Prefetch(
'event',
@@ -1224,10 +1229,10 @@ 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),
is_public=True,
live=True,
has_subevents=False,
sales_channels__contains=self.request.sales_channel.identifier,
),
request
).order_by(
@@ -1244,12 +1249,13 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
filter_qs_by_attr(
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),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
is_public=True,
active=True,
event__sales_channels__contains=self.request.sales_channel.identifier
),
request
).prefetch_related(

View File

@@ -274,7 +274,7 @@ class WidgetAPIProductList(EventListMixin, View):
self.request.event,
subevent=self.subevent,
voucher=self.voucher,
channel=self.request.sales_channel.identifier,
channel=self.request.sales_channel,
base_qs=qs,
require_seat=None,
memberships=(
@@ -372,7 +372,7 @@ class WidgetAPIProductList(EventListMixin, View):
'error': gettext('This ticket shop is currently disabled.')
})
if request.sales_channel.identifier not in request.event.sales_channels:
if not request.event.all_sales_channels and request.sales_channel.identifier not in (s.identifier for s in request.event.limit_sales_channels.all()):
return self.response({
'error': gettext('Tickets for this event cannot be purchased on this sales channel.')
})
@@ -546,7 +546,8 @@ class WidgetAPIProductList(EventListMixin, View):
add_subevents_for_days(
filter_qs_by_attr(
self.request.event.subevents_annotated('web').filter(
event__sales_channels__contains=self.request.sales_channel.identifier
Q(event__all_sales_channels=True) |
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
), self.request
),
limit_before, after, ebd, set(), self.request.event,
@@ -558,16 +559,17 @@ class WidgetAPIProductList(EventListMixin, View):
self.request,
filter_qs_by_attr(
Event.annotated(self.request.organizer.events, 'web').filter(
sales_channels__contains=self.request.sales_channel.identifier
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
), self.request
),
limit_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),
event__organizer=self.request.organizer,
event__is_public=True,
event__live=True,
event__sales_channels__contains=self.request.sales_channel.identifier
).prefetch_related(
'event___settings_objects', 'event__organizer___settings_objects'
)), self.request), limit_before, after, ebd, timezones)