forked from CGM_Public/pretix_original
Improve UI to configure unavailable items handling (Z#23131828) (#3739)
* start impl of unavailability modes ui * add db migration * use new widget for more fields * improve contrast * use new widget for hide_without_voucher field * improved wording * rebase migration * undo changes to require_membership_hidden * code formatting * move unavail_reason logic around * enforce consistent state of hide_without_voucher / require_voucher * annotate unavailability info in get_grouped_items * remove MSIE6 compat * add unavailability reasons to widget * remove test output * Apply suggestions from code review text improvements Co-authored-by: Richard Schreiber <schreiber@rami.io> * add css fix for jumping items due to tooltip * dynamically retrieve unavailability reason message * widget: simplify logic conditions * add available_{from,until}_mode to api and api docs * rebase migration * rebase migration * add unavailable_*_mode to ItemVariation * add available_*_mode to API docs for items * fix wrong reference * fix test cases * add available_*_mode to item variation form * apply unavailability modes to subevents and variations (presale) * /o\ * apply unavailability modes to subevents and variations (widget) * display unavailability mode in subevent product settings * fix widget test * fix api item tests * copy available_*_mode when copying an item * Apply suggestions from code review Co-authored-by: Raphael Michel <michel@rami.io> * Add unavail mode indicator to bulk create and edit forms --------- Co-authored-by: Richard Schreiber <schreiber@rami.io> Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -1,6 +1,19 @@
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% if avail <= 10 %}
|
||||
|
||||
{% if item.current_unavailability_reason == 'require_voucher' %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this product." %}</a></small></p>
|
||||
</div>
|
||||
{% elif item.current_unavailability_reason == 'available_from' or var.current_unavailability_reason == 'available_from' %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||
<p><small>{% trans "Not available yet." %}</small></p>
|
||||
</div>
|
||||
{% elif item.current_unavailability_reason == 'available_until' or var.current_unavailability_reason == 'available_until' %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||
<p><small>{% trans "Not available any more." %}</small></p>
|
||||
</div>
|
||||
{% elif avail <= 10 %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box gone">
|
||||
{% if price != None and not price %}
|
||||
<strong>{% trans "FULLY BOOKED" %}</strong>
|
||||
|
||||
@@ -185,11 +185,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if item.require_voucher %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this ticket." %}</a></small></p>
|
||||
</div>
|
||||
{% elif var.cached_availability.0 == 100 %}
|
||||
{% if var.cached_availability.0 == 100 and not item.current_unavailability_reason and not var.current_unavailability_reason %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if var.order_max == 1 %}
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
@@ -338,11 +334,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if item.require_voucher %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this ticket." %}</a></small></p>
|
||||
</div>
|
||||
{% elif item.cached_availability.0 == 100 %}
|
||||
{% if item.cached_availability.0 == 100 and not item.current_unavailability_reason %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if item.order_max == 1 %}
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if var.cached_availability.0 == 100 %}
|
||||
{% if var.cached_availability.0 == 100 and not item.current_unavailability_reason %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
|
||||
{% if max_times > 1 %}
|
||||
{% if var.order_max == 1 %}
|
||||
@@ -251,7 +251,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "pretixpresale/event/fragment_availability.html" with price=var.display_price.gross avail=var.cached_availability.0 %}
|
||||
{% include "pretixpresale/event/fragment_availability.html" with price=var.display_price.gross avail=var.cached_availability.0 item=item %}
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</article>
|
||||
@@ -363,7 +363,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if item.cached_availability.0 == 100 %}
|
||||
{% if item.cached_availability.0 == 100 and not item.current_unavailability_reason %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
|
||||
{% if max_times > 1 %}
|
||||
{% if item.order_max == 1 %}
|
||||
@@ -404,7 +404,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "pretixpresale/event/fragment_availability.html" with price=item.display_price.gross avail=item.cached_availability.0 %}
|
||||
{% include "pretixpresale/event/fragment_availability.html" with price=item.display_price.gross avail=item.cached_availability.0 item=item %}
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</article>
|
||||
|
||||
@@ -48,6 +48,7 @@ from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import (
|
||||
Count, Exists, IntegerField, OuterRef, Prefetch, Q, Value,
|
||||
)
|
||||
from django.db.models.lookups import Exact
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -118,8 +119,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
requires_seat = Value(0, output_field=IntegerField())
|
||||
|
||||
variation_q = (
|
||||
Q(Q(available_from__isnull=True) | Q(available_from__lte=now())) &
|
||||
Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
|
||||
Q(Q(available_from__isnull=True) | Q(available_from__lte=now()) | Q(available_from_mode='info')) &
|
||||
Q(Q(available_until__isnull=True) | Q(available_until__gte=now()) | Q(available_until_mode='info'))
|
||||
)
|
||||
if not voucher or not voucher.show_hidden_items:
|
||||
variation_q &= Q(hide_without_voucher=False)
|
||||
@@ -135,7 +136,9 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
queryset=ItemVariation.objects.using(settings.DATABASE_REPLICA).annotate(
|
||||
subevent_disabled=Exists(
|
||||
SubEventItemVariation.objects.filter(
|
||||
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
|
||||
Q(disabled=True)
|
||||
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now()))
|
||||
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now())),
|
||||
variation_id=OuterRef('pk'),
|
||||
subevent=subevent,
|
||||
)
|
||||
@@ -205,7 +208,9 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
has_variations=Count('variations'),
|
||||
subevent_disabled=Exists(
|
||||
SubEventItem.objects.filter(
|
||||
Q(disabled=True) | Q(available_from__gt=now()) | Q(available_until__lt=now()),
|
||||
Q(disabled=True)
|
||||
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now()))
|
||||
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now())),
|
||||
item_id=OuterRef('pk'),
|
||||
subevent=subevent,
|
||||
)
|
||||
@@ -301,6 +306,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
item._remove = True
|
||||
continue
|
||||
|
||||
item.current_unavailability_reason = item.unavailability_reason(has_voucher=voucher, subevent=subevent)
|
||||
|
||||
item.description = str(item.description)
|
||||
for recv, resp in item_description.send(sender=event, item=item, variation=None, subevent=subevent):
|
||||
if resp:
|
||||
@@ -415,6 +422,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
if not display_add_to_cart:
|
||||
display_add_to_cart = not item.requires_seat and var.order_max > 0
|
||||
|
||||
var.current_unavailability_reason = var.unavailability_reason(has_voucher=voucher, subevent=subevent)
|
||||
|
||||
item.original_price = (
|
||||
item.tax(item.original_price, currency=event.currency, include_bundled=True,
|
||||
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
|
||||
|
||||
@@ -272,7 +272,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'picture_fullsize': get_picture(self.request.event, item.picture) if item.picture else None,
|
||||
'description': str(rich_text(item.description, safelinks=False)) if item.description else None,
|
||||
'has_variations': item.has_variations,
|
||||
'require_voucher': item.require_voucher,
|
||||
'current_unavailability_reason': item.current_unavailability_reason,
|
||||
'order_min': item.min_per_order,
|
||||
'order_max': item.order_max if not item.has_variations else None,
|
||||
'price': price_dict(item, item.display_price) if not item.has_variations else None,
|
||||
@@ -317,6 +317,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
var.cached_availability[0],
|
||||
var.cached_availability[1] if item.do_show_quota_left else None
|
||||
],
|
||||
'current_unavailability_reason': var.current_unavailability_reason,
|
||||
} for var in item.available_variations if (not variation_filter or var.id in variation_filter)
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user