Compare commits

...

4 Commits

Author SHA1 Message Date
Raphael Michel
8bbe605439 Fix voucher bulk form 2019-07-07 12:42:38 +02:00
Raphael Michel
8267da66e4 Add test 2019-07-07 12:25:08 +02:00
Raphael Michel
481db3cb88 Backwards-compatible implementation 2019-07-07 12:20:41 +02:00
Raphael Michel
435d90474a Changes in checks 2019-07-06 15:21:32 +02:00
13 changed files with 69 additions and 14 deletions

View File

@@ -41,6 +41,7 @@ quota integer An ID of a quot
tag string A string that is used for grouping vouchers tag string A string that is used for grouping vouchers
comment string An internal comment on the voucher comment string An internal comment on the voucher
subevent integer ID of the date inside an event series this voucher belongs to (or ``null``). subevent integer ID of the date inside an event series this voucher belongs to (or ``null``).
show_hidden_items boolean Only if set to ``true``, this voucher allows to buy products with the property ``hide_without_voucher``. Defaults to ``true``.
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
@@ -48,6 +49,10 @@ subevent integer ID of the date
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added. The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
.. versionchanged:: 3.0
The attribute ``show_hidden_items`` has been added.
Endpoints Endpoints
--------- ---------

View File

@@ -27,7 +27,7 @@ class VoucherSerializer(I18nAwareModelSerializer):
model = Voucher model = Voucher
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota', fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota', 'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
'tag', 'comment', 'subevent') 'tag', 'comment', 'subevent', 'show_hidden_items')
read_only_fields = ('id', 'redeemed') read_only_fields = ('id', 'redeemed')
list_serializer_class = VoucherListSerializer list_serializer_class = VoucherListSerializer

View File

@@ -0,0 +1,26 @@
# Generated by Django 2.2.1 on 2019-07-07 10:10
from django.db import migrations, models
def set_show_hidden_items(apps, schema_editor):
Voucher = apps.get_model('pretixbase', 'Voucher')
Voucher.objects.filter(quota__isnull=False).update(show_hidden_items=False)
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0124_seat_seat_guid'),
]
operations = [
migrations.AddField(
model_name='voucher',
name='show_hidden_items',
field=models.BooleanField(default=True),
),
migrations.RunPython(
set_show_hidden_items,
migrations.RunPython.noop,
)
]

View File

@@ -171,12 +171,11 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False):
qs = qs.filter(q) qs = qs.filter(q)
vouchq = Q(hide_without_voucher=False) vouchq = Q(hide_without_voucher=False)
if voucher: if voucher and voucher.show_hidden_items:
if voucher.item_id: if voucher.item_id:
vouchq |= Q(pk=voucher.item_id) vouchq = Q(pk=voucher.item_id)
qs = qs.filter(pk=voucher.item_id)
elif voucher.quota_id: elif voucher.quota_id:
qs = qs.filter(quotas__in=[voucher.quota_id]) vouchq = Q(quotas__in=[voucher.quota_id])
return qs.filter(vouchq) return qs.filter(vouchq)
@@ -343,7 +342,7 @@ class Item(LoggedModel):
verbose_name=_('This product will only be shown if a voucher matching the product is redeemed.'), verbose_name=_('This product will only be shown if a voucher matching the product is redeemed.'),
default=False, default=False,
help_text=_('This product will be hidden from the event page until the user enters a voucher ' help_text=_('This product will be hidden from the event page until the user enters a voucher '
'code that is specifically tied to this product (and not via a quota).') 'that unlocks this product.')
) )
require_bundling = models.BooleanField( require_bundling = models.BooleanField(
verbose_name=_('Only sell this product as part of a bundle'), verbose_name=_('Only sell this product as part of a bundle'),

View File

@@ -176,6 +176,10 @@ class Voucher(LoggedModel):
help_text=_("The text entered in this field will not be visible to the user and is available for your " help_text=_("The text entered in this field will not be visible to the user and is available for your "
"convenience.") "convenience.")
) )
show_hidden_items = models.BooleanField(
verbose_name=_("Shows hidden products that match this voucher"),
default=True
)
objects = ScopedManager(organizer='event__organizer') objects = ScopedManager(organizer='event__organizer')

View File

@@ -225,10 +225,7 @@ class CartManager:
def _check_item_constraints(self, op): def _check_item_constraints(self, op):
if isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation): if isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation):
if op.item.require_voucher and op.voucher is None: if (op.item.require_voucher or op.item.hide_without_voucher) and (op.voucher is None or not op.voucher.show_hidden_items):
raise CartError(error_messages['voucher_required'])
if op.item.hide_without_voucher and (op.voucher is None or op.voucher.item is None or op.voucher.item.pk != op.item.pk):
raise CartError(error_messages['voucher_required']) raise CartError(error_messages['voucher_required'])
if not op.item.is_available() or (op.variation and not op.variation.active): if not op.item.is_available() or (op.variation and not op.variation.active):

View File

@@ -505,9 +505,9 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
err = err or error_messages['voucher_required'] err = err or error_messages['voucher_required']
break break
if cp.item.hide_without_voucher and (cp.voucher is None or cp.voucher.item is None if cp.item.hide_without_voucher and (cp.voucher is None or not cp.voucher.show_hidden_items or not cp.voucher.applies_to(cp.item.pk, cp.variation.pk)):
or cp.voucher.item.pk != cp.item.pk):
delete(cp) delete(cp)
cp.delete()
err = error_messages['voucher_required'] err = error_messages['voucher_required']
break break

View File

@@ -32,7 +32,7 @@ class VoucherForm(I18nModelForm):
localized_fields = '__all__' localized_fields = '__all__'
fields = [ fields = [
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
'comment', 'max_usages', 'price_mode', 'subevent' 'comment', 'max_usages', 'price_mode', 'subevent', 'show_hidden_items'
] ]
field_classes = { field_classes = {
'valid_until': SplitDateTimeField, 'valid_until': SplitDateTimeField,
@@ -197,7 +197,7 @@ class VoucherBulkForm(VoucherForm):
localized_fields = '__all__' localized_fields = '__all__'
fields = [ fields = [
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
'max_usages', 'price_mode', 'subevent' 'max_usages', 'price_mode', 'subevent', 'show_hidden_items'
] ]
field_classes = { field_classes = {
'valid_until': SplitDateTimeField, 'valid_until': SplitDateTimeField,

View File

@@ -72,6 +72,7 @@
{% bootstrap_field form.allow_ignore_quota layout="control" %} {% bootstrap_field form.allow_ignore_quota layout="control" %}
{% bootstrap_field form.tag layout="control" %} {% bootstrap_field form.tag layout="control" %}
{% bootstrap_field form.comment layout="control" %} {% bootstrap_field form.comment layout="control" %}
{% bootstrap_field form.show_hidden_items layout="control" %}
</fieldset> </fieldset>
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %} {% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
<div class="form-group submit-group"> <div class="form-group submit-group">

View File

@@ -74,6 +74,7 @@
{% bootstrap_field form.allow_ignore_quota layout="control" %} {% bootstrap_field form.allow_ignore_quota layout="control" %}
{% bootstrap_field form.tag layout="control" %} {% bootstrap_field form.tag layout="control" %}
{% bootstrap_field form.comment layout="control" %} {% bootstrap_field form.comment layout="control" %}
{% bootstrap_field form.show_hidden_items layout="control" %}
</fieldset> </fieldset>
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %} {% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
</div> </div>

View File

@@ -43,6 +43,7 @@ TEST_VOUCHER_RES = {
'quota': None, 'quota': None,
'tag': 'Foo', 'tag': 'Foo',
'comment': '', 'comment': '',
'show_hidden_items': True,
'subevent': None 'subevent': None
} }

View File

@@ -1529,6 +1529,19 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].item, self.shirt) self.assertEqual(objs[0].item, self.shirt)
self.assertEqual(objs[0].variation, self.shirt_red) self.assertEqual(objs[0].variation, self.shirt_red)
def test_hide_without_voucher_failed_because_of_voucher(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.shirt, event=self.event, show_hidden_items=False)
self.shirt.hide_without_voucher = True
self.shirt.save()
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1',
'_voucher_code': v.code
}, follow=True)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 0)
def test_hide_without_voucher_failed(self): def test_hide_without_voucher_failed(self):
self.shirt.hide_without_voucher = True self.shirt.hide_without_voucher = True
self.shirt.save() self.shirt.save()

View File

@@ -435,6 +435,14 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
assert "Early-bird" in html.rendered_content assert "Early-bird" in html.rendered_content
def test_hide_wo_voucher_quota(self): def test_hide_wo_voucher_quota(self):
self.item.hide_without_voucher = True
self.item.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "Early-bird" in html.rendered_content
def test_hide_wo_voucher_quota_dont_show(self):
self.v.show_hidden_items = False
self.v.save()
self.item.hide_without_voucher = True self.item.hide_without_voucher = True
self.item.save() self.item.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code)) html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))