Improve add-on products

This commit is contained in:
Raphael Michel
2017-03-19 14:33:45 +01:00
parent 5bcfb958f0
commit b52f2f5a9e
36 changed files with 802 additions and 131 deletions

View File

@@ -264,6 +264,7 @@ class ItemsTest(ItemFormTest):
require_voucher=True, allow_cancel=False)
self.var1 = ItemVariation.objects.create(item=self.item2, value="Silver")
self.var2 = ItemVariation.objects.create(item=self.item2, value="Gold")
self.addoncat = ItemCategory.objects.create(event=self.event1, name="Item category")
def test_move(self):
self.client.post('/control/event/%s/%s/items/%s/down' % (self.orga1.slug, self.event1.slug, self.item1.id),)
@@ -295,6 +296,49 @@ class ItemsTest(ItemFormTest):
self.item1.refresh_from_db()
assert self.item1.default_price == Decimal('23.00')
def test_manipulate_addons(self):
self.client.post('/control/event/%s/%s/items/%d/addons' % (self.orga1.slug, self.event1.slug, self.item2.id), {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '0',
'form-MIN_NUM_FORMS': '0',
'form-MAX_NUM_FORMS': '1000',
'form-0-id': '',
'form-0-addon_category': str(self.addoncat.pk),
'form-0-min_count': '1',
'form-0-max_count': '2',
})
assert self.item2.addons.exists()
assert self.item2.addons.first().addon_category == self.addoncat
self.client.post('/control/event/%s/%s/items/%d/addons' % (self.orga1.slug, self.event1.slug, self.item2.id), {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '1',
'form-MIN_NUM_FORMS': '0',
'form-MAX_NUM_FORMS': '1000',
'form-0-id': str(self.item2.addons.first().pk),
'form-0-addon_category': str(self.addoncat.pk),
'form-0-min_count': '1',
'form-0-max_count': '2',
'form-0-DELETE': 'on',
})
assert not self.item2.addons.exists()
# Do not allow duplicates
self.client.post('/control/event/%s/%s/items/%d/addons' % (self.orga1.slug, self.event1.slug, self.item2.id), {
'form-TOTAL_FORMS': '2',
'form-INITIAL_FORMS': '0',
'form-MIN_NUM_FORMS': '0',
'form-MAX_NUM_FORMS': '1000',
'form-0-id': '',
'form-0-addon_category': str(self.addoncat.pk),
'form-0-min_count': '1',
'form-0-max_count': '2',
'form-1-id': '',
'form-1-addon_category': str(self.addoncat.pk),
'form-1-min_count': '1',
'form-1-max_count': '2',
})
assert not self.item2.addons.exists()
def test_update_variations(self):
self.client.post('/control/event/%s/%s/items/%d/variations' % (self.orga1.slug, self.event1.slug, self.item2.id), {
'form-TOTAL_FORMS': '2',

View File

@@ -11,6 +11,8 @@ from pretix.base.models import (
CartPosition, Event, Item, ItemCategory, ItemVariation, Organizer,
Question, QuestionAnswer, Quota, Voucher,
)
from pretix.base.models.items import ItemAddOn
from pretix.base.services.cart import CartError, CartManager
class CartTestMixin:
@@ -513,12 +515,12 @@ class CartTest(CartTestMixin, TestCase):
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
CartPosition.objects.create(
cp = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'id': cp.pk
}, follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('less than', doc.select('.alert-danger')[0].text)
@@ -536,6 +538,17 @@ class CartTest(CartTestMixin, TestCase):
self.assertIn('empty', doc.select('.alert-success')[0].text)
self.assertFalse(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).exists())
def test_remove_invalid(self):
cp = CartPosition.objects.create(
event=self.event, cart_id='invalid', item=self.shirt, variation=self.shirt_red,
price=14, expires=now() + timedelta(minutes=10)
)
response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), {
'id': cp.pk
}, follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml")
assert doc.select('.alert-danger')
def test_remove_one_of_multiple(self):
cp = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -995,3 +1008,361 @@ class CartTest(CartTestMixin, TestCase):
}, follow=True)
positions = CartPosition.objects.filter(cart_id=self.session_key, event=self.event)
assert positions.count() == 1
class CartAddonTest(CartTestMixin, TestCase):
def setUp(self):
super().setUp()
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
self.workshopquota = Quota.objects.create(event=self.event, name='Workshop 1', size=5)
self.workshop1 = Item.objects.create(event=self.event, name='Workshop 1',
category=self.workshopcat, default_price=12)
self.workshop2 = Item.objects.create(event=self.event, name='Workshop 2',
category=self.workshopcat, default_price=12)
self.workshop3 = Item.objects.create(event=self.event, name='Workshop 3',
category=self.workshopcat, default_price=12)
self.workshop3a = ItemVariation.objects.create(item=self.workshop3, value='3a')
self.workshop3b = ItemVariation.objects.create(item=self.workshop3, value='3b')
self.workshopquota.items.add(self.workshop1)
self.workshopquota.items.add(self.workshop2)
self.workshopquota.items.add(self.workshop3)
self.workshopquota.variations.add(self.workshop3a)
self.workshopquota.variations.add(self.workshop3b)
self.addon1 = ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat)
self.cm = CartManager(event=self.event, cart_id=self.session_key)
def test_cart_set_simple_addon(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
self.cm.commit()
cp2 = cp1.addons.first()
assert cp2.item == self.workshop1
def test_wrong_category(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.workshop1.category = self.category
self.workshop1.save()
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
def test_invalid_parent(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id='other'
)
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
def test_no_quota_for_addon(self):
self.workshopquota.delete()
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
def test_unknown_addon_item(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': 99999,
'variation': None
}
])
def test_duplicate_items_for_other_cp(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
cp2 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
self.cm.set_addons([
{
'addon_to': cp2.pk,
'item': self.workshop1.pk,
'variation': None
}
])
self.cm.commit()
def test_no_duplicate_items_for_same_cp(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.addon1.max_count = 2
self.addon1.save()
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
},
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop3.pk,
'variation': self.workshop3a.pk
},
{
'addon_to': cp1.pk,
'item': self.workshop3.pk,
'variation': self.workshop3b.pk
}
])
def test_addon_max_count(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
},
{
'addon_to': cp1.pk,
'item': self.workshop2.pk,
'variation': None
}
])
self.addon1.max_count = 2
self.addon1.save()
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
},
{
'addon_to': cp1.pk,
'item': self.workshop2.pk,
'variation': None
}
])
def test_addon_min_count(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.addon1.min_count = 2
self.addon1.max_count = 9
self.addon1.save()
with self.assertRaises(CartError):
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop2.pk,
'variation': None
}
])
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
},
{
'addon_to': cp1.pk,
'item': self.workshop2.pk,
'variation': None
}
])
def test_remove_addons(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
cp2 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.cm.set_addons([])
self.cm.commit()
assert not CartPosition.objects.filter(pk=cp2.pk).exists()
def test_remove_addons_below_min(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
cp2 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.addon1.min_count = 1
self.addon1.save()
with self.assertRaises(CartError):
self.cm.set_addons([])
self.cm.commit()
assert CartPosition.objects.filter(pk=cp2.pk).exists()
def test_change_product(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop2.pk,
'variation': None
}
])
self.cm.commit()
cp1.refresh_from_db()
assert cp1.addons.count() == 1
assert cp1.addons.first().item == self.workshop2
def test_unchanged(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
assert not self.cm._operations
def test_exceed_max(self):
self.event.settings.max_items_per_order = 1
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
self.cm.commit()
def test_sold_out(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
self.workshopquota.size = 0
self.workshopquota.save()
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
with self.assertRaises(CartError):
self.cm.commit()
def test_sold_out_unchanged(self):
cp1 = CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.workshopquota.size = 0
self.workshopquota.save()
self.cm.set_addons([
{
'addon_to': cp1.pk,
'item': self.workshop1.pk,
'variation': None
}
])
self.cm.commit()
def test_expand_expired(self):
cp1 = CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
cp2 = CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.cm.extend_expired_positions()
self.cm.commit()
cp1.refresh_from_db()
cp2.refresh_from_db()
assert cp1.expires > now()
assert cp2.expires > now()
assert cp2.addon_to_id == cp1.pk

View File

@@ -11,6 +11,7 @@ from pretix.base.models import (
CartPosition, Event, Item, ItemCategory, Order, OrderPosition, Organizer,
Question, Quota, Voucher,
)
from pretix.base.models.items import ItemAddOn, ItemVariation
class CheckoutTestCase(TestCase):
@@ -35,6 +36,19 @@ class CheckoutTestCase(TestCase):
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
self._set_session('email', 'admin@localhost')
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
self.workshopquota = Quota.objects.create(event=self.event, name='Workshop 1', size=5)
self.workshop1 = Item.objects.create(event=self.event, name='Workshop 1',
category=self.workshopcat, default_price=12)
self.workshop2 = Item.objects.create(event=self.event, name='Workshop 2',
category=self.workshopcat, default_price=12)
self.workshop2a = ItemVariation.objects.create(item=self.workshop2, value='A')
self.workshop2b = ItemVariation.objects.create(item=self.workshop2, value='B')
self.workshopquota.items.add(self.workshop1)
self.workshopquota.items.add(self.workshop2)
self.workshopquota.variations.add(self.workshop2a)
self.workshopquota.variations.add(self.workshop2b)
def test_empty_cart(self):
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
@@ -766,3 +780,46 @@ class CheckoutTestCase(TestCase):
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertEqual(len(doc.select(".thank-you")), 1)
def test_addons_as_first_step(self):
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)
def test_set_addons_item_and_variation(self):
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat)
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
cp2 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
response = self.client.post('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug), {
'{}_{}-item_{}'.format(cp1.pk, self.workshopcat.pk, self.workshop1.pk): 'on',
'{}_{}-item_{}'.format(cp2.pk, self.workshopcat.pk, self.workshop2.pk): self.workshop2a.pk,
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug),
target_status_code=200)
assert cp1.addons.first().item == self.workshop1
assert cp2.addons.first().item == self.workshop2
assert cp2.addons.first().variation == self.workshop2a
def test_set_addons_required(self):
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat, min_count=1)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() - timedelta(minutes=10)
)
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug))
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)

View File

@@ -76,10 +76,11 @@ def test_plugin_in_order(event, mocker):
priority = 100
flow = with_mocked_step(mocker, MockingStep, event)
assert isinstance(flow[0], checkoutflow.QuestionsStep)
assert isinstance(flow[1], MockingStep)
assert isinstance(flow[2], checkoutflow.PaymentStep)
assert isinstance(flow[3], checkoutflow.ConfirmStep)
assert isinstance(flow[0], checkoutflow.AddOnsStep)
assert isinstance(flow[1], checkoutflow.QuestionsStep)
assert isinstance(flow[2], MockingStep)
assert isinstance(flow[3], checkoutflow.PaymentStep)
assert isinstance(flow[4], checkoutflow.ConfirmStep)
@pytest.mark.django_db
@@ -93,8 +94,8 @@ def test_step_ignored(event, mocker, req_with_session):
flow = with_mocked_step(mocker, MockingStep, event)
req_with_session.event = event
assert flow[0].get_next_applicable(req_with_session) is flow[2]
assert flow[0] is flow[2].get_prev_applicable(req_with_session)
assert flow[1].get_next_applicable(req_with_session) is flow[3]
assert flow[1] is flow[3].get_prev_applicable(req_with_session)
@pytest.mark.django_db