mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Allow to hide products that require membership (#2240)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -59,7 +59,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||
model = ItemVariation
|
||||
fields = ('id', 'value', 'active', 'description',
|
||||
'position', 'default_price', 'price', 'original_price',
|
||||
'require_membership', 'require_membership_types', 'available_from', 'available_until',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -75,7 +75,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
model = ItemVariation
|
||||
fields = ('id', 'value', 'active', 'description',
|
||||
'position', 'default_price', 'price', 'original_price',
|
||||
'require_membership', 'require_membership_types', 'available_from', 'available_until',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -175,7 +175,7 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
|
||||
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
||||
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
|
||||
'require_membership', 'require_membership_types', 'grant_membership_type',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
||||
'grant_membership_duration_like_event', 'grant_membership_duration_days',
|
||||
'grant_membership_duration_months')
|
||||
read_only_fields = ('has_variations',)
|
||||
|
||||
23
src/pretix/base/migrations/0199_auto_20211005_1050.py
Normal file
23
src/pretix/base/migrations/0199_auto_20211005_1050.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.4 on 2021-10-05 10:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0198_invoice_sent_to_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='require_membership_hidden',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='itemvariation',
|
||||
name='require_membership_hidden',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -25,6 +25,7 @@ from django.contrib.auth.hashers import (
|
||||
check_password, is_password_usable, make_password,
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models import F, Q
|
||||
from django.utils.crypto import get_random_string, salted_hmac
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
@@ -183,6 +184,12 @@ class Customer(LoggedModel):
|
||||
def stored_addresses(self):
|
||||
return self.invoice_addresses(manager='profiles')
|
||||
|
||||
def usable_memberships(self, for_event, testmode=False):
|
||||
return self.memberships.active(for_event).with_usages().filter(
|
||||
Q(membership_type__max_usages__isnull=True) | Q(usages__lt=F('membership_type__max_usages')),
|
||||
testmode=testmode,
|
||||
)
|
||||
|
||||
|
||||
class AttendeeProfile(models.Model):
|
||||
customer = models.ForeignKey(
|
||||
|
||||
@@ -523,6 +523,12 @@ class Item(LoggedModel):
|
||||
verbose_name=_('Allowed membership types'),
|
||||
blank=True,
|
||||
)
|
||||
require_membership_hidden = models.BooleanField(
|
||||
verbose_name=_('Hide without a valid membership'),
|
||||
help_text=_('Do not show this unless the customer is logged in and has a valid membership. Be aware that '
|
||||
'this means it will never be visible in the widget.'),
|
||||
default=False,
|
||||
)
|
||||
grant_membership_type = models.ForeignKey(
|
||||
'MembershipType',
|
||||
null=True, blank=True,
|
||||
@@ -802,6 +808,12 @@ class ItemVariation(models.Model):
|
||||
verbose_name=_('Membership types'),
|
||||
blank=True,
|
||||
)
|
||||
require_membership_hidden = models.BooleanField(
|
||||
verbose_name=_('Hide without a valid membership'),
|
||||
help_text=_('Do not show this unless the customer is logged in and has a valid membership. Be aware that '
|
||||
'this means it will never be visible in the widget.'),
|
||||
default=False,
|
||||
)
|
||||
available_from = models.DateTimeField(
|
||||
verbose_name=_("Available from"),
|
||||
null=True, blank=True,
|
||||
|
||||
@@ -95,6 +95,7 @@ class MembershipQuerySet(models.QuerySet):
|
||||
|
||||
def active(self, ev):
|
||||
return self.filter(
|
||||
canceled=False,
|
||||
date_start__lte=ev.date_from,
|
||||
date_end__gte=ev.date_from
|
||||
)
|
||||
@@ -175,7 +176,7 @@ class Membership(models.Model):
|
||||
else:
|
||||
dt = now()
|
||||
|
||||
return dt >= self.date_start and dt <= self.date_end
|
||||
return not self.canceled and dt >= self.date_start and dt <= self.date_end
|
||||
|
||||
def allow_delete(self):
|
||||
return self.testmode and not self.orderposition_set.exists()
|
||||
|
||||
@@ -607,6 +607,7 @@ class ItemUpdateForm(I18nModelForm):
|
||||
'issue_giftcard',
|
||||
'require_membership',
|
||||
'require_membership_types',
|
||||
'require_membership_hidden',
|
||||
'grant_membership_type',
|
||||
'grant_membership_duration_like_event',
|
||||
'grant_membership_duration_days',
|
||||
@@ -713,6 +714,7 @@ class ItemVariationForm(I18nModelForm):
|
||||
'original_price',
|
||||
'description',
|
||||
'require_membership',
|
||||
'require_membership_hidden',
|
||||
'require_membership_types',
|
||||
'available_from',
|
||||
'available_until',
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
{% bootstrap_field form.require_membership layout="control" %}
|
||||
<div data-display-dependency="#{{ form.require_membership.id_for_label }}">
|
||||
{% bootstrap_field form.require_membership_types layout="control" %}
|
||||
{% bootstrap_field form.require_membership_hidden layout="control" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -147,6 +148,7 @@
|
||||
{% bootstrap_field formset.empty_form.require_membership layout="control" %}
|
||||
<div data-display-dependency="#{{ formset.empty_form.require_membership.id_for_label }}">
|
||||
{% bootstrap_field formset.empty_form.require_membership_types layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.require_membership_hidden layout="control" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
{% bootstrap_field form.require_membership layout="control" %}
|
||||
<div data-display-dependency="#{{ form.require_membership.id_for_label }}">
|
||||
{% bootstrap_field form.require_membership_types layout="control" %}
|
||||
{% bootstrap_field form.require_membership_hidden layout="control" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field form.allow_cancel layout="control" %}
|
||||
|
||||
@@ -499,7 +499,14 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
channel=self.request.sales_channel.identifier,
|
||||
base_qs=iao.addon_category.items,
|
||||
allow_addons=True,
|
||||
quota_cache=quota_cache
|
||||
quota_cache=quota_cache,
|
||||
memberships=(
|
||||
self.request.customer.usable_memberships(
|
||||
for_event=cartpos.subevent or self.request.event,
|
||||
testmode=self.request.event.testmode
|
||||
)
|
||||
if getattr(self.request, 'customer', None) else None
|
||||
),
|
||||
)
|
||||
item_cache[ckey] = items
|
||||
else:
|
||||
|
||||
@@ -43,13 +43,21 @@ class WaitingListForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
self.channel = kwargs.pop('channel')
|
||||
customer = kwargs.pop('customer')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
choices = [
|
||||
('', '')
|
||||
]
|
||||
items, display_add_to_cart = get_grouped_items(
|
||||
self.event, self.instance.subevent, require_seat=None
|
||||
self.event, self.instance.subevent, require_seat=None,
|
||||
memberships=(
|
||||
self.request.customer.usable_memberships(
|
||||
for_event=self.instance.subevent or self.event,
|
||||
testmode=self.request.event.testmode
|
||||
)
|
||||
if customer else None
|
||||
),
|
||||
)
|
||||
for i in items:
|
||||
if not i.allow_waitinglist:
|
||||
|
||||
@@ -523,8 +523,18 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
|
||||
context['max_times'] = self.voucher.max_usages - self.voucher.redeemed
|
||||
|
||||
# Fetch all items
|
||||
items, display_add_to_cart = get_grouped_items(self.request.event, self.subevent,
|
||||
voucher=self.voucher, channel=self.request.sales_channel.identifier)
|
||||
items, display_add_to_cart = get_grouped_items(
|
||||
self.request.event,
|
||||
self.subevent,
|
||||
voucher=self.voucher,
|
||||
channel=self.request.sales_channel.identifier,
|
||||
memberships=(
|
||||
self.request.customer.usable_memberships(
|
||||
for_event=self.subevent or self.request.event,
|
||||
testmode=self.request.event.testmode
|
||||
) if getattr(self.request, 'customer', None) else None
|
||||
),
|
||||
)
|
||||
|
||||
# Calculate how many options the user still has. If there is only one option, we can
|
||||
# check the box right away ;)
|
||||
|
||||
@@ -100,7 +100,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,
|
||||
quota_cache=None, filter_items=None, filter_categories=None):
|
||||
quota_cache=None, filter_items=None, filter_categories=None, memberships=None):
|
||||
base_qs_set = base_qs is not None
|
||||
base_qs = base_qs if base_qs is not None else event.items
|
||||
|
||||
@@ -120,10 +120,16 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
if not voucher or not voucher.show_hidden_items:
|
||||
variation_q &= Q(hide_without_voucher=False)
|
||||
|
||||
if memberships is not None:
|
||||
prefetch_membership_types = ['require_membership_types']
|
||||
else:
|
||||
prefetch_membership_types = []
|
||||
|
||||
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',
|
||||
).prefetch_related(
|
||||
*prefetch_membership_types,
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
@@ -160,6 +166,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
quotas__isnull=False,
|
||||
subevent_disabled=False
|
||||
).prefetch_related(
|
||||
*prefetch_membership_types,
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent))
|
||||
@@ -240,6 +247,11 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
item._remove = True
|
||||
continue
|
||||
|
||||
if item.require_membership and item.require_membership_hidden:
|
||||
if not memberships or not any([m.membership_type in item.require_membership_types.all() for m in memberships]):
|
||||
item._remove = True
|
||||
continue
|
||||
|
||||
item.description = str(item.description)
|
||||
for recv, resp in item_description.send(sender=event, item=item, variation=None):
|
||||
if resp:
|
||||
@@ -290,6 +302,11 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
|
||||
display_add_to_cart = display_add_to_cart or item.order_max > 0
|
||||
else:
|
||||
for var in item.available_variations:
|
||||
if var.require_membership and var.require_membership_hidden:
|
||||
if not memberships or not any([m.membership_type in var.require_membership_types.all() for m in memberships]):
|
||||
var._remove = True
|
||||
continue
|
||||
|
||||
var.description = str(var.description)
|
||||
for recv, resp in item_description.send(sender=event, item=item, variation=var):
|
||||
if resp:
|
||||
@@ -338,7 +355,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._subevent_quotas and (
|
||||
not voucher or not voucher.quota_id or v in restrict_vars
|
||||
)
|
||||
) and not getattr(v, '_remove', False)
|
||||
]
|
||||
|
||||
if event.settings.hide_sold_out:
|
||||
@@ -439,7 +456,13 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
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.identifier,
|
||||
memberships=(
|
||||
self.request.customer.usable_memberships(
|
||||
for_event=self.subevent or self.request.event,
|
||||
testmode=self.request.event.testmode
|
||||
) if getattr(self.request, 'customer', None) else None
|
||||
),
|
||||
)
|
||||
|
||||
context['waitinglist_seated'] = False
|
||||
|
||||
@@ -54,6 +54,7 @@ class WaitingView(EventViewMixin, FormView):
|
||||
subevent=self.subevent
|
||||
)
|
||||
kwargs['channel'] = self.request.sales_channel.identifier
|
||||
kwargs['customer'] = getattr(self.request, 'customer', None)
|
||||
kwargs.setdefault('initial', {})
|
||||
if 'var' in self.request.GET:
|
||||
kwargs['initial']['itemvar'] = f'{self.request.GET.get("item")}-{self.request.GET.get("var")}'
|
||||
|
||||
@@ -226,8 +226,17 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
qs = qs.filter(category__pk__in=self.request.GET.get('categories').split(","))
|
||||
|
||||
items, display_add_to_cart = get_grouped_items(
|
||||
self.request.event, subevent=self.subevent, voucher=self.voucher, channel=self.request.sales_channel.identifier,
|
||||
base_qs=qs
|
||||
self.request.event,
|
||||
subevent=self.subevent,
|
||||
voucher=self.voucher,
|
||||
channel=self.request.sales_channel.identifier,
|
||||
base_qs=qs,
|
||||
memberships=(
|
||||
self.request.customer.usable_memberships(
|
||||
for_event=self.subevent or self.request.event,
|
||||
testmode=self.request.event.testmode
|
||||
) if getattr(self.request, 'customer', None) else None
|
||||
),
|
||||
)
|
||||
|
||||
grps = []
|
||||
|
||||
Reference in New Issue
Block a user