mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Add program times for items (Z#23178639)
* Add program times for items * Fix frontend date validation * Add ical data for program times [wip] * Improve ical data for program times * Remove duplicate code and add comments * Adjust migration * Remove program times form for event series * Add pdf placeholder [wip] * Improve explanation text with suggestion Co-authored-by: Raphael Michel <michel@pretix.eu> * Fix import sorting * Improve ical generation * Improve ical entry description * Fix migration * Add copyability for program times fot items and events * Update migration * Add API endpoints/functions, fix isort * Improve variable name Co-authored-by: Richard Schreiber <schreiber@rami.io> * Remove todo comment * Add documentation, Change endpoint name * Change related name * Remove unnecessary code block * Add program times to item API * Fix imports * Add log text * Use daterange helper * Add and update API tests * Add another API test * Add program times to cloning tests * Update query count because of program times query * Invalidate cached tickets on program time changes * Reduce invalidation calls * Update migration after rebase * Apply improvements to invalidation from review Co-authored-by: Richard Schreiber <schreiber@rami.io> * remove unneccessary attr=item param * remove unnecessary kwargs for formset_factory * fix local var name being overwritten in for-loop * fix empty formset being saved * Use subevent if available * make code less verbose * remove double event-label in ical desc * fix unnecessary var re-assign * fix ev vs p.subevent --------- Co-authored-by: Raphael Michel <michel@pretix.eu> Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -46,7 +46,8 @@ from tests.const import SAMPLE_PNG
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, InvoiceAddress, Item, ItemAddOn, ItemBundle, ItemCategory,
|
||||
ItemVariation, Order, OrderPosition, Question, QuestionOption, Quota,
|
||||
ItemProgramTime, ItemVariation, Order, OrderPosition, Question,
|
||||
QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.models.orders import OrderFee
|
||||
|
||||
@@ -331,6 +332,7 @@ TEST_ITEM_RES = {
|
||||
"variations": [],
|
||||
"addons": [],
|
||||
"bundles": [],
|
||||
"program_times": [],
|
||||
"show_quota_left": None,
|
||||
"original_price": None,
|
||||
"free_price_suggestion": None,
|
||||
@@ -509,6 +511,24 @@ def test_item_detail_bundles(token_client, organizer, event, team, item, categor
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_detail_program_times(token_client, organizer, event, team, item, category):
|
||||
with scopes_disabled():
|
||||
item.program_times.create(item=item, start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
|
||||
res = dict(TEST_ITEM_RES)
|
||||
|
||||
res["id"] = item.pk
|
||||
res["program_times"] = [{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
}]
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_create(token_client, organizer, event, item, category, taxrule, membership_type):
|
||||
resp = token_client.post(
|
||||
@@ -1072,6 +1092,57 @@ def test_item_create_with_bundle(token_client, organizer, event, item, category,
|
||||
assert resp.content.decode() == '{"bundles":["The chosen variation does not belong to this item."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_create_with_product_time(token_client, organizer, event, item, category, taxrule):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"issue_giftcard": False,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"checkin_text": None,
|
||||
"has_variations": False,
|
||||
"program_times": [
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
},
|
||||
{
|
||||
"start": "2017-12-29T00:00:00Z",
|
||||
"end": "2017-12-30T00:00:00Z",
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
new_item = Item.objects.get(pk=resp.data['id'])
|
||||
assert new_item.program_times.first().start == datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.first().end == datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.last().start == datetime(2017, 12, 29, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert new_item.program_times.last().end == datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
def test_item_update(token_client, organizer, event, item, category, item2, category2, taxrule2):
|
||||
resp = token_client.patch(
|
||||
@@ -1147,8 +1218,8 @@ def test_item_update(token_client, organizer, event, item, category, item2, cate
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
@@ -1165,8 +1236,8 @@ def test_item_update(token_client, organizer, event, item, category, item2, cate
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
item.personalized = True
|
||||
item.admission = True
|
||||
@@ -1322,8 +1393,8 @@ def test_item_update_with_variation(token_client, organizer, event, item):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1345,8 +1416,8 @@ def test_item_update_with_addon(token_client, organizer, event, item, category):
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
assert resp.content.decode() == '{"non_field_errors":["Updating add-ons, bundles, program times or variations via ' \
|
||||
'PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1881,6 +1952,123 @@ def test_addons_delete(token_client, organizer, event, item, addon):
|
||||
assert not item.addons.filter(pk=addon.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def program_time(item, category):
|
||||
return item.program_times.create(start=datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def program_time2(item, category):
|
||||
return item.program_times.create(start=datetime(2017, 12, 29, 0, 0, 0, tzinfo=timezone.utc),
|
||||
end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc))
|
||||
|
||||
|
||||
TEST_PROGRAM_TIMES_RES = {
|
||||
0: {
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z",
|
||||
},
|
||||
1: {
|
||||
"start": "2017-12-29T00:00:00Z",
|
||||
"end": "2017-12-30T00:00:00Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2):
|
||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||
res[0]["id"] = program_time.pk
|
||||
res[1]["id"] = program_time2.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res[0]['start'] == resp.data['results'][0]['start']
|
||||
assert res[0]['end'] == resp.data['results'][0]['end']
|
||||
assert res[0]['id'] == resp.data['results'][0]['id']
|
||||
assert res[1]['start'] == resp.data['results'][1]['start']
|
||||
assert res[1]['end'] == resp.data['results'][1]['end']
|
||||
assert res[1]['id'] == resp.data['results'][1]['id']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_detail(token_client, organizer, event, item, program_time):
|
||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||
res[0]["id"] = program_time.pk
|
||||
resp = token_client.get(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, program_time.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res[0] == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_create(token_client, organizer, event, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-27T00:00:00Z",
|
||||
"end": "2017-12-28T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert datetime(2017, 12, 27, 0, 0, 0, tzinfo=timezone.utc) == program_time.start
|
||||
assert datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc) == program_time.end
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"start": "2017-12-28T00:00:00Z",
|
||||
"end": "2017-12-27T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The program end must not be before the program start."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_update(token_client, organizer, event, item, program_time):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug, item.pk,
|
||||
program_time.pk),
|
||||
{
|
||||
"start": "2017-12-26T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
with scopes_disabled():
|
||||
program_time = ItemProgramTime.objects.get(pk=resp.data['id'])
|
||||
assert datetime(2017, 12, 26, 0, 0, 0, tzinfo=timezone.utc) == program_time.start
|
||||
assert datetime(2017, 12, 28, 0, 0, 0, tzinfo=timezone.utc) == program_time.end
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug, item.pk,
|
||||
program_time.pk),
|
||||
{
|
||||
"start": "2017-12-30T00:00:00Z"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The program end must not be before the program start."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_program_times_delete(token_client, organizer, event, item, program_time):
|
||||
resp = token_client.delete(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/program_times/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, program_time.pk))
|
||||
assert resp.status_code == 204
|
||||
with scopes_disabled():
|
||||
assert not item.program_times.filter(pk=program_time.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def quota(event, item):
|
||||
q = event.quotas.create(name="Budget Quota", size=200)
|
||||
|
||||
@@ -1996,7 +1996,7 @@ def test_pdf_data(token_client, organizer, event, order, django_assert_max_num_q
|
||||
assert not resp.data['positions'][0].get('pdf_data')
|
||||
|
||||
# order list
|
||||
with django_assert_max_num_queries(32):
|
||||
with django_assert_max_num_queries(33):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
|
||||
organizer.slug, event.slug
|
||||
))
|
||||
|
||||
@@ -39,7 +39,9 @@ import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Organizer, Question, SeatingPlan
|
||||
from pretix.base.models import (
|
||||
Event, ItemProgramTime, Organizer, Question, SeatingPlan,
|
||||
)
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
|
||||
|
||||
@@ -78,6 +80,10 @@ def test_full_clone_same_organizer():
|
||||
# todo: test that item pictures are copied, not linked
|
||||
ItemMetaValue.objects.create(item=item1, property=item_meta, value="Foo")
|
||||
assert item1.meta_data
|
||||
ItemProgramTime.objects.create(item=item1,
|
||||
start=datetime.datetime(2017, 12, 27, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0, tzinfo=datetime.timezone.utc))
|
||||
assert item1.program_times
|
||||
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15,
|
||||
hidden_if_item_available=item1)
|
||||
item2v = item2.variations.create(value="red", default_price=15, all_sales_channels=False)
|
||||
@@ -161,6 +167,8 @@ def test_full_clone_same_organizer():
|
||||
assert copied_item1.category == copied_event.categories.get(name='Tickets')
|
||||
assert copied_item1.limit_sales_channels.get() == sc
|
||||
assert copied_item1.meta_data == item1.meta_data
|
||||
assert copied_item1.program_times.first().start == item1.program_times.first().start
|
||||
assert copied_item1.program_times.first().end == item1.program_times.first().end
|
||||
assert copied_item2.variations.get().meta_data == item2v.meta_data
|
||||
assert copied_item1.hidden_if_available == copied_q2
|
||||
assert copied_item1.grant_membership_type == membership_type
|
||||
|
||||
@@ -681,6 +681,10 @@ class ItemsTest(ItemFormTest):
|
||||
self.var2.save()
|
||||
prop = self.event1.item_meta_properties.create(name="Foo")
|
||||
self.item2.meta_values.create(property=prop, value="Bar")
|
||||
self.item2.program_times.create(start=datetime.datetime(2017, 12, 27, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc),
|
||||
end=datetime.datetime(2017, 12, 28, 0, 0, 0,
|
||||
tzinfo=datetime.timezone.utc))
|
||||
|
||||
doc = self.get_doc('/control/event/%s/%s/items/add?copy_from=%d' % (self.orga1.slug, self.event1.slug, self.item2.pk))
|
||||
data = extract_form_fields(doc.select("form")[0])
|
||||
@@ -709,6 +713,8 @@ class ItemsTest(ItemFormTest):
|
||||
assert i_new.meta_data == i_old.meta_data == {"Foo": "Bar"}
|
||||
assert set(i_new.questions.all()) == set(i_old.questions.all())
|
||||
assert set([str(v.value) for v in i_new.variations.all()]) == set([str(v.value) for v in i_old.variations.all()])
|
||||
assert i_old.program_times.first().start == i_new.program_times.first().start
|
||||
assert i_old.program_times.first().end == i_new.program_times.first().end
|
||||
|
||||
def test_add_to_existing_quota(self):
|
||||
with scopes_disabled():
|
||||
|
||||
Reference in New Issue
Block a user