forked from CGM_Public/pretix_original
Add sub-events and relative date settings (#503)
* Data model * little crud * SubEventItemForm etc * Drop SubEventItem.active, quota editor * Fix failing tests * First frontend stuff * Addons form stuff * Quota calculation * net price display on EventIndex * Add tests, solve some bugs * Correct quota selection in more places, consolidate pricing logic * Fix failing quota tests * Fix TypeError * Add tests for checkout * Fixed a bug in QuotaForm * Prevent immutable cart if a quota was removed from an item * Add tests for pricing * Handle waiting list * Filter in check-in list * Fixed import lost in rebase * Fix waiting list widget * Voucher management * Voucher redemption * Fix broken tests * Add subevents to OrderChangeManager * Create a subevent during event creation * Fix bulk voucher creation * Introduce subevent.active * Copy from for subevents * Show active in list * ICal download for subevents * Check start and end of presale * Failing tests / show cart logic * Test * Rebase migrations * REST API integration of sub-events * Integrate quota calculation into the traditional quota form * Make subevent argument to add_position optional * Log-display foo * pretixdroid and subevents * Filter by subevent * Add more tests * Some mor tests * Rebase fixes * More tests * Relative dates * Restrict selection in relative datetime widgets * Filter subevent list * Re-label has_subevents * Rebase fixes, subevents in calendar view * Performance and caching issues * Refactor calendar templates * Permission tests * Calendar fixes and month selection * subevent selection * Rename subevents to dates * Add tests for calendar views
This commit is contained in:
@@ -2,11 +2,15 @@ import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
from django.utils.timezone import now
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from pytz import timezone
|
||||
from tests.base import SoupTest, extract_form_fields
|
||||
|
||||
from pretix.base.models import Event, Organizer, Team, User
|
||||
from pretix.base.models import (
|
||||
Event, Order, OrderPosition, Organizer, Team, User,
|
||||
)
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.testutils.mock import mocker_context
|
||||
|
||||
|
||||
@@ -164,7 +168,10 @@ class EventsTest(SoupTest):
|
||||
'payment_banktransfer__fee_abs': '12.23',
|
||||
'payment_banktransfer_bank_details_0': 'Test',
|
||||
'settings-payment_term_days': '2',
|
||||
'settings-payment_term_last': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'),
|
||||
'settings-payment_term_last_0': 'absolute',
|
||||
'settings-payment_term_last_1': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'),
|
||||
'settings-payment_term_last_2': '0',
|
||||
'settings-payment_term_last_3': 'date_from',
|
||||
'settings-tax_rate_default': '19.00',
|
||||
})
|
||||
assert doc.select('.alert-danger')
|
||||
@@ -327,6 +334,41 @@ class EventsTest(SoupTest):
|
||||
assert ev.presale_start == berlin_tz.localize(datetime.datetime(2016, 11, 1, 10, 0, 0)).astimezone(pytz.utc)
|
||||
assert ev.presale_end == berlin_tz.localize(datetime.datetime(2016, 11, 30, 18, 0, 0)).astimezone(pytz.utc)
|
||||
|
||||
def test_create_event_with_subevents_success(self):
|
||||
doc = self.get_doc('/control/events/add')
|
||||
tabletext = doc.select("form")[0].text
|
||||
self.assertIn("CCC", tabletext)
|
||||
self.assertNotIn("MRM", tabletext)
|
||||
|
||||
doc = self.post_doc('/control/events/add', {
|
||||
'event_wizard-current_step': 'foundation',
|
||||
'foundation-organizer': self.orga1.pk,
|
||||
'foundation-locales': ('en', 'de'),
|
||||
'foundation-has_subevents': 'on',
|
||||
})
|
||||
doc = self.post_doc('/control/events/add', {
|
||||
'event_wizard-current_step': 'basics',
|
||||
'basics-name_0': '33C3',
|
||||
'basics-name_1': '33C3',
|
||||
'basics-slug': '33c3',
|
||||
'basics-date_from': '2016-12-27 10:00:00',
|
||||
'basics-date_to': '2016-12-30 19:00:00',
|
||||
'basics-location_0': 'Hamburg',
|
||||
'basics-location_1': 'Hamburg',
|
||||
'basics-currency': 'EUR',
|
||||
'basics-locale': 'en',
|
||||
'basics-timezone': 'Europe/Berlin',
|
||||
'basics-presale_start': '2016-11-01 10:00:00',
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
self.post_doc('/control/events/add', {
|
||||
'event_wizard-current_step': 'copy',
|
||||
'copy-copy_from_event': ''
|
||||
})
|
||||
ev = Event.objects.get(slug='33c3')
|
||||
assert ev.has_subevents
|
||||
assert ev.subevents.count() == 1
|
||||
|
||||
def test_create_event_only_date_from(self):
|
||||
# date_to, presale_start & presale_end are optional fields
|
||||
self.post_doc('/control/events/add', {
|
||||
@@ -431,3 +473,132 @@ class EventsTest(SoupTest):
|
||||
'basics-presale_end': '2016-11-30 18:00:00',
|
||||
})
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
|
||||
class SubEventsTest(SoupTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
|
||||
self.event1 = Event.objects.create(
|
||||
organizer=self.orga1, name='30C3', slug='30c3',
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
plugins='pretix.plugins.banktransfer,tests.testdummy',
|
||||
has_subevents=True
|
||||
)
|
||||
|
||||
t = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
|
||||
can_change_items=True)
|
||||
t.members.add(self.user)
|
||||
t.limit_events.add(self.event1)
|
||||
self.ticket = self.event1.items.create(name='Early-bird ticket',
|
||||
category=None, default_price=23,
|
||||
admission=True)
|
||||
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
self.subevent1 = self.event1.subevents.create(name='SE1', date_from=now())
|
||||
|
||||
def test_list(self):
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/')
|
||||
tabletext = doc.select("#page-wrapper .table")[0].text
|
||||
self.assertIn("SE1", tabletext)
|
||||
|
||||
def test_create(self):
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/add')
|
||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/add', {
|
||||
'name_0': 'SE2',
|
||||
'active': 'on',
|
||||
'date_from': '2017-07-01 10:00:00',
|
||||
'date_to': '2017-07-01 12:00:00',
|
||||
'location_0': 'Hamburg',
|
||||
'presale_start': '2017-06-20 10:00:00',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'quotas-0-name': 'Q1',
|
||||
'quotas-0-size': '50',
|
||||
'quotas-0-itemvars': str(self.ticket.pk),
|
||||
'item-%d-price' % self.ticket.pk: '12'
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
se = self.event1.subevents.first()
|
||||
assert str(se.name) == "SE2"
|
||||
assert se.active
|
||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
||||
assert str(se.location) == "Hamburg"
|
||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
||||
assert not se.presale_end
|
||||
assert se.quotas.count() == 1
|
||||
q = se.quotas.last()
|
||||
assert q.name == "Q1"
|
||||
assert q.size == 50
|
||||
assert list(q.items.all()) == [self.ticket]
|
||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
||||
assert sei.price == 12
|
||||
|
||||
def test_modify(self):
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk)
|
||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk, {
|
||||
'name_0': 'SE2',
|
||||
'active': 'on',
|
||||
'date_from': '2017-07-01 10:00:00',
|
||||
'date_to': '2017-07-01 12:00:00',
|
||||
'location_0': 'Hamburg',
|
||||
'presale_start': '2017-06-20 10:00:00',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'quotas-0-name': 'Q1',
|
||||
'quotas-0-size': '50',
|
||||
'quotas-0-itemvars': str(self.ticket.pk),
|
||||
'item-%d-price' % self.ticket.pk: '12'
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
self.subevent1.refresh_from_db()
|
||||
se = self.subevent1
|
||||
assert str(se.name) == "SE2"
|
||||
assert se.active
|
||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
||||
assert str(se.location) == "Hamburg"
|
||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
||||
assert not se.presale_end
|
||||
assert se.quotas.count() == 1
|
||||
q = se.quotas.last()
|
||||
assert q.name == "Q1"
|
||||
assert q.size == 50
|
||||
assert list(q.items.all()) == [self.ticket]
|
||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
||||
assert sei.price == 12
|
||||
|
||||
def test_delete(self):
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk)
|
||||
assert doc.select("button")
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {})
|
||||
assert doc.select(".alert-success")
|
||||
assert not SubEventItem.objects.filter(pk=self.subevent1.pk).exists()
|
||||
|
||||
def test_delete_with_orders(self):
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=o,
|
||||
item=self.ticket,
|
||||
subevent=self.subevent1,
|
||||
price=Decimal("14"),
|
||||
)
|
||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, follow=True)
|
||||
assert doc.select(".alert-danger")
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {}, follow=True)
|
||||
assert doc.select(".alert-danger")
|
||||
assert self.event1.subevents.filter(pk=self.subevent1.pk).exists()
|
||||
|
||||
@@ -231,7 +231,7 @@ class QuotaTest(ItemFormTest):
|
||||
ItemVariation.objects.create(item=item2, value="Silver")
|
||||
ItemVariation.objects.create(item=item2, value="Gold")
|
||||
doc = self.get_doc('/control/event/%s/%s/quotas/%s/change' % (self.orga1.slug, self.event1.slug, c.id))
|
||||
doc.select('[name=item_%s]' % item1.id)[0]['checked'] = 'checked'
|
||||
[i for i in doc.select('[name=itemvars]') if i.get('value') == str(item1.id)][0]['checked'] = 'checked'
|
||||
form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
form_data['size'] = '350'
|
||||
doc = self.post_doc('/control/event/%s/%s/quotas/%s/change' % (self.orga1.slug, self.event1.slug, c.id),
|
||||
@@ -242,6 +242,19 @@ class QuotaTest(ItemFormTest):
|
||||
assert Quota.objects.get(id=c.id).size == 350
|
||||
assert item1 in Quota.objects.get(id=c.id).items.all()
|
||||
|
||||
def test_update_subevent(self):
|
||||
self.event1.has_subevents = True
|
||||
self.event1.save()
|
||||
se1 = self.event1.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event1.subevents.create(name="Bar", date_from=now())
|
||||
c = Quota.objects.create(event=self.event1, name="Full house", size=500, subevent=se1)
|
||||
doc = self.get_doc('/control/event/%s/%s/quotas/%s/change' % (self.orga1.slug, self.event1.slug, c.id))
|
||||
form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
form_data['subevent'] = se2.pk
|
||||
self.post_doc('/control/event/%s/%s/quotas/%s/change' % (self.orga1.slug, self.event1.slug, c.id),
|
||||
form_data)
|
||||
assert Quota.objects.get(id=c.id).subevent == se2
|
||||
|
||||
def test_delete(self):
|
||||
c = Quota.objects.create(event=self.event1, name="Full house", size=500)
|
||||
doc = self.get_doc('/control/event/%s/%s/quotas/%s/delete' % (self.orga1.slug, self.event1.slug, c.id))
|
||||
|
||||
@@ -476,6 +476,9 @@ class OrderChangeTests(SoupTest):
|
||||
order=self.order, item=self.ticket, variation=None,
|
||||
price=Decimal("23.00"), attendee_name="Dieter"
|
||||
)
|
||||
self.quota = self.event.quotas.create(name="All", size=100)
|
||||
self.quota.items.add(self.ticket)
|
||||
self.quota.items.add(self.shirt)
|
||||
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True)
|
||||
t.members.add(user)
|
||||
@@ -499,6 +502,38 @@ class OrderChangeTests(SoupTest):
|
||||
assert self.op1.tax_rate == self.shirt.tax_rate
|
||||
assert self.order.total == self.op1.price + self.op2.price
|
||||
|
||||
def test_change_subevent_success(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name='Foo', date_from=now())
|
||||
se2 = self.event.subevents.create(name='Bar', date_from=now())
|
||||
self.op1.subevent = se1
|
||||
self.op1.save()
|
||||
self.op2.subevent = se1
|
||||
self.op2.save()
|
||||
self.quota.subevent = se1
|
||||
self.quota.save()
|
||||
q2 = self.event.quotas.create(name='Q2', size=100, subevent=se2)
|
||||
q2.items.add(self.ticket)
|
||||
q2.items.add(self.shirt)
|
||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||
self.event.organizer.slug, self.event.slug, self.order.code
|
||||
), {
|
||||
'op-{}-operation'.format(self.op1.pk): 'subevent',
|
||||
'op-{}-subevent'.format(self.op1.pk): str(se2.pk),
|
||||
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
|
||||
'op-{}-operation'.format(self.op2.pk): '',
|
||||
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
||||
'op-{}-subevent'.format(self.op2.pk): str(se1.pk),
|
||||
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
||||
'add-subevent'.format(self.op2.pk): str(se1.pk),
|
||||
})
|
||||
self.op1.refresh_from_db()
|
||||
self.op2.refresh_from_db()
|
||||
self.order.refresh_from_db()
|
||||
assert self.op1.subevent == se2
|
||||
assert self.op2.subevent == se1
|
||||
|
||||
def test_change_price_success(self):
|
||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||
self.event.organizer.slug, self.event.slug, self.order.code
|
||||
|
||||
@@ -64,6 +64,10 @@ event_urls = [
|
||||
"vouchers/add",
|
||||
"vouchers/bulk_add",
|
||||
"vouchers/rng",
|
||||
"subevents/",
|
||||
"subevents/add",
|
||||
"subevents/2/delete",
|
||||
"subevents/2/",
|
||||
"quotas/",
|
||||
"quotas/2/delete",
|
||||
"quotas/2/change",
|
||||
@@ -182,6 +186,10 @@ event_permission_urls = [
|
||||
("can_change_items", "quotas/2/change", 404),
|
||||
("can_change_items", "quotas/2/delete", 404),
|
||||
("can_change_items", "quotas/add", 200),
|
||||
("can_change_event_settings", "subevents/", 200),
|
||||
("can_change_event_settings", "subevents/2/", 404),
|
||||
("can_change_event_settings", "subevents/2/delete", 404),
|
||||
("can_change_event_settings", "subevents/add", 200),
|
||||
("can_view_orders", "orders/overview/", 200),
|
||||
("can_view_orders", "orders/export/", 200),
|
||||
("can_view_orders", "orders/", 200),
|
||||
|
||||
@@ -425,3 +425,53 @@ class VoucherFormTest(SoupTest):
|
||||
doc = self.post_doc('/control/event/%s/%s/vouchers/%s/delete' % (self.orga.slug, self.event.slug, v.pk),
|
||||
{}, follow=True)
|
||||
assert doc.select(".alert-danger")
|
||||
|
||||
def test_subevent_optional(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk,
|
||||
})
|
||||
|
||||
def test_subevent_required_for_blocking(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk,
|
||||
'block_quota': 'on'
|
||||
}, expected_failure=True)
|
||||
|
||||
def test_subevent_blocking_quota_free(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
|
||||
self.quota_tickets.subevent = se1
|
||||
self.quota_tickets.save()
|
||||
q2 = Quota.objects.create(event=self.event, name='Tickets', size=0, subevent=se2)
|
||||
q2.items.add(self.ticket)
|
||||
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk,
|
||||
'block_quota': 'on',
|
||||
'subevent': se1.pk
|
||||
})
|
||||
|
||||
def test_subevent_blocking_quota_full(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||
|
||||
self.quota_tickets.subevent = se1
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
q2 = Quota.objects.create(event=self.event, name='Tickets', size=5, subevent=se2)
|
||||
q2.items.add(self.ticket)
|
||||
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk,
|
||||
'block_quota': 'on',
|
||||
'subevent': se1.pk
|
||||
}, expected_failure=True)
|
||||
|
||||
Reference in New Issue
Block a user