mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Allow to disable self-choice seating
This commit is contained in:
@@ -6,7 +6,7 @@ from typing import List, Optional
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import DatabaseError, transaction
|
||||
from django.db.models import Count, Exists, OuterRef, Q
|
||||
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import gettext as _, pgettext_lazy
|
||||
@@ -147,6 +147,8 @@ class CartManager:
|
||||
).select_related('item', 'subevent')
|
||||
|
||||
def _is_seated(self, item, subevent):
|
||||
if not self.event.settings.seating_choice:
|
||||
return False
|
||||
if (item, subevent) not in self._seated_cache:
|
||||
self._seated_cache[item, subevent] = item.seat_category_mappings.filter(subevent=subevent).exists()
|
||||
return self._seated_cache[item, subevent]
|
||||
@@ -328,15 +330,18 @@ class CartManager:
|
||||
raise e
|
||||
|
||||
def extend_expired_positions(self):
|
||||
requires_seat = Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
Q(product=OuterRef('item'))
|
||||
& (Q(subevent=OuterRef('subevent')) if self.event.has_subevents else Q(subevent__isnull=True))
|
||||
)
|
||||
)
|
||||
if not self.event.settings.seating_choice:
|
||||
requires_seat = Value(0, output_field=IntegerField())
|
||||
expired = self.positions.filter(expires__lte=self.now_dt).select_related(
|
||||
'item', 'variation', 'voucher', 'addon_to', 'addon_to__item'
|
||||
).annotate(
|
||||
requires_seat=Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
Q(product=OuterRef('item'))
|
||||
& (Q(subevent=OuterRef('subevent')) if self.event.has_subevents else Q(subevent__isnull=True))
|
||||
)
|
||||
)
|
||||
requires_seat=requires_seat
|
||||
).prefetch_related(
|
||||
'item__quotas',
|
||||
'variation__quotas',
|
||||
@@ -349,7 +354,7 @@ class CartManager:
|
||||
if cp.pk in removed_positions or (cp.addon_to_id and cp.addon_to_id in removed_positions):
|
||||
continue
|
||||
|
||||
cp.item.requires_seat = cp.requires_seat
|
||||
cp.item.requires_seat = self.event.settings.seating_choice and cp.requires_seat
|
||||
|
||||
if cp.is_bundled:
|
||||
bundle = cp.addon_to.item.bundles.filter(bundled_item=cp.item, bundled_variation=cp.variation).first()
|
||||
|
||||
@@ -9,7 +9,9 @@ from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db import transaction
|
||||
from django.db.models import Exists, F, Max, Min, OuterRef, Q, Sum
|
||||
from django.db.models import (
|
||||
Exists, F, IntegerField, Max, Min, OuterRef, Q, Sum, Value,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.db.transaction import get_connection
|
||||
from django.dispatch import receiver
|
||||
@@ -890,13 +892,16 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
positions = CartPosition.objects.annotate(
|
||||
requires_seat=Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
Q(product=OuterRef('item'))
|
||||
& (Q(subevent=OuterRef('subevent')) if event.has_subevents else Q(subevent__isnull=True))
|
||||
)
|
||||
requires_seat = Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
Q(product=OuterRef('item'))
|
||||
& (Q(subevent=OuterRef('subevent')) if event.has_subevents else Q(subevent__isnull=True))
|
||||
)
|
||||
)
|
||||
if not event.settings.seating_choice:
|
||||
requires_seat = Value(0, output_field=IntegerField())
|
||||
positions = CartPosition.objects.annotate(
|
||||
requires_seat=requires_seat
|
||||
).filter(
|
||||
id__in=position_ids, event=event
|
||||
)
|
||||
@@ -1377,7 +1382,7 @@ class OrderChangeManager:
|
||||
raise OrderError(self.error_messages['subevent_required'])
|
||||
|
||||
seated = item.seat_category_mappings.filter(subevent=subevent).exists()
|
||||
if seated and not seat:
|
||||
if seated and not seat and self.event.settings.seating_choice:
|
||||
raise OrderError(self.error_messages['seat_required'])
|
||||
elif not seated and seat:
|
||||
raise OrderError(self.error_messages['seat_forbidden'])
|
||||
|
||||
@@ -1744,6 +1744,18 @@ Your {event} team"""))
|
||||
'default': settings.ENTROPY['giftcard_secret'],
|
||||
'type': int
|
||||
},
|
||||
'seating_choice': {
|
||||
'default': 'True',
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Customers can choose their own seats"),
|
||||
help_text=_("If disabled, you will need to manually assign seats in the backend. Note that this can mean "
|
||||
"people will not know their seat after their purchase and it might not be written on their "
|
||||
"ticket."),
|
||||
),
|
||||
'type': bool,
|
||||
},
|
||||
'seating_minimal_distance': {
|
||||
'default': '0',
|
||||
'type': float
|
||||
|
||||
@@ -399,7 +399,10 @@ class OrderPositionChangeForm(forms.Form):
|
||||
self.fields['tax_rule'].queryset = instance.event.tax_rules.all()
|
||||
self.fields['tax_rule'].label_from_instance = self.taxrule_label_from_instance
|
||||
|
||||
if not instance.seat:
|
||||
if not instance.seat and not (
|
||||
not instance.event.settings.seating_choice and
|
||||
instance.item.seat_category_mappings.filter(subevent=instance.subevent).exists()
|
||||
):
|
||||
del self.fields['seat']
|
||||
|
||||
choices = [
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if position.seat %}
|
||||
{% if position.form.seat %}
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Seat" %}</strong>
|
||||
|
||||
@@ -1514,7 +1514,7 @@ class OrderChange(OrderView):
|
||||
elif change_subevent is not None:
|
||||
ocm.change_subevent(p, *change_subevent)
|
||||
|
||||
if p.seat and p.form.cleaned_data['seat'] and p.form.cleaned_data['seat'] != p.seat.seat_guid:
|
||||
if p.form.cleaned_data.get('seat') and (not p.seat or p.form.cleaned_data['seat'] != p.seat.seat_guid):
|
||||
ocm.change_seat(p, p.form.cleaned_data['seat'])
|
||||
|
||||
if p.form.cleaned_data['price'] is not None and p.form.cleaned_data['price'] != p.price:
|
||||
|
||||
@@ -26,8 +26,8 @@ from django.views.generic import (
|
||||
from pretix.api.models import WebHook
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.models import (
|
||||
CachedFile, Device, GiftCard, OrderPayment, Organizer, Team, TeamInvite,
|
||||
User, LogEntry,
|
||||
CachedFile, Device, GiftCard, LogEntry, OrderPayment, Organizer, Team,
|
||||
TeamInvite, User,
|
||||
)
|
||||
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
|
||||
from pretix.base.models.giftcards import gen_giftcard_secret
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
action="{% eventurl request.event "presale:event.cart.add" cart_namespace=cart_namespace %}?next={{ cart_redirect|urlencode }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />
|
||||
{% if ev.seating_plan_id %}
|
||||
{% if ev.seating_plan_id and event.settings.seating_choice %}
|
||||
{% if event.has_subevents %}
|
||||
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent %}
|
||||
{% else %}
|
||||
|
||||
@@ -474,7 +474,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
|
||||
context['items_by_category'] = item_group_by_category(items)
|
||||
|
||||
context['subevent'] = self.subevent
|
||||
context['seating_available'] = self.voucher.seating_available(self.subevent)
|
||||
context['seating_available'] = self.request.event.settings.seating_choice and self.voucher.seating_available(self.subevent)
|
||||
|
||||
context['new_tab'] = (
|
||||
'require_cookie' in self.request.GET and
|
||||
|
||||
@@ -9,7 +9,9 @@ import isoweek
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Count, Exists, OuterRef, Prefetch
|
||||
from django.db.models import (
|
||||
Count, Exists, IntegerField, OuterRef, Prefetch, Value,
|
||||
)
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -61,6 +63,16 @@ 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,
|
||||
quota_cache=None, filter_items=None, filter_categories=None):
|
||||
base_qs = base_qs if base_qs is not None else event.items
|
||||
|
||||
requires_seat = Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
product_id=OuterRef('pk'),
|
||||
subevent=subevent
|
||||
)
|
||||
)
|
||||
if not event.settings.seating_choice:
|
||||
requires_seat = Value(0, output_field=IntegerField())
|
||||
|
||||
items = base_qs.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher, allow_addons=allow_addons).select_related(
|
||||
'category', 'tax_rule', # for re-grouping
|
||||
'hidden_if_available',
|
||||
@@ -111,12 +123,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
disabled=True,
|
||||
)
|
||||
),
|
||||
requires_seat=Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
product_id=OuterRef('pk'),
|
||||
subevent=subevent
|
||||
)
|
||||
),
|
||||
requires_seat=requires_seat,
|
||||
).filter(
|
||||
quotac__gt=0, subevent_disabled=False,
|
||||
).order_by('category__position', 'category_id', 'position', 'name')
|
||||
|
||||
Reference in New Issue
Block a user