From d1171b9cf0eb35801c3ba8f30f2f2530e2302b5b Mon Sep 17 00:00:00 2001 From: Phin Wolkwitz Date: Wed, 21 Jan 2026 16:54:24 +0100 Subject: [PATCH] [wip] Add location --- src/pretix/api/serializers/item.py | 2 +- .../0297_itemprogramtime_location.py | 19 ++++++++++++++++ src/pretix/base/models/items.py | 7 ++++++ src/pretix/control/forms/item.py | 5 +++++ .../item/include_program_times.html | 2 ++ src/pretix/presale/ical.py | 2 +- src/tests/api/test_items.py | 22 ++++++++++++++++++- 7 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 src/pretix/base/migrations/0297_itemprogramtime_location.py diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index a2c6258f5e..aa0d9f651c 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -222,7 +222,7 @@ class ItemBundleSerializer(serializers.ModelSerializer): class ItemProgramTimeSerializer(serializers.ModelSerializer): class Meta: model = ItemProgramTime - fields = ('id', 'start', 'end') + fields = ('id', 'start', 'end', 'location') def validate(self, data): data = super().validate(data) diff --git a/src/pretix/base/migrations/0297_itemprogramtime_location.py b/src/pretix/base/migrations/0297_itemprogramtime_location.py new file mode 100644 index 0000000000..d6eb1f2864 --- /dev/null +++ b/src/pretix/base/migrations/0297_itemprogramtime_location.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.27 on 2026-01-21 12:06 + +from django.db import migrations, models +import i18nfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("pretixbase", "0296_invoice_invoice_from_state"), + ] + + operations = [ + migrations.AddField( + model_name="itemprogramtime", + name="location", + field=i18nfield.fields.I18nTextField(max_length=200, null=True), + ) + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 339ad45014..feb7dec8a5 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -2306,10 +2306,17 @@ class ItemProgramTime(models.Model): :type start: datetime :param end: The date and time this program time ends :type end: datetime + :param location: venue + :type location: str """ item = models.ForeignKey('Item', related_name='program_times', on_delete=models.CASCADE) start = models.DateTimeField(verbose_name=_("Start")) end = models.DateTimeField(verbose_name=_("End")) + location = I18nTextField( + null=True, blank=True, + max_length=200, + verbose_name=_("Location"), + ) def clean(self): if hasattr(self, 'item') and self.item and self.item.event.has_subevents: diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 6d5b8f1797..93d5839fc1 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -1354,6 +1354,10 @@ class ItemProgramTimeForm(I18nModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['end'].widget.attrs['data-date-after'] = '#id_{prefix}-start_0'.format(prefix=self.prefix) + self.fields['location'].widget.attrs['rows'] = '1' + self.fields['location'].widget.attrs['placeholder'] = _( + 'Sample Conference Center, Heidelberg, Germany' + ) class Meta: model = ItemProgramTime @@ -1361,6 +1365,7 @@ class ItemProgramTimeForm(I18nModelForm): fields = [ 'start', 'end', + 'location' ] field_classes = { 'start': forms.SplitDateTimeField, diff --git a/src/pretix/control/templates/pretixcontrol/item/include_program_times.html b/src/pretix/control/templates/pretixcontrol/item/include_program_times.html index e223a0eecb..9939cbf4fd 100644 --- a/src/pretix/control/templates/pretixcontrol/item/include_program_times.html +++ b/src/pretix/control/templates/pretixcontrol/item/include_program_times.html @@ -34,6 +34,7 @@ {% bootstrap_form_errors form %} {% bootstrap_field form.start layout="control" %} {% bootstrap_field form.end layout="control" %} + {% bootstrap_field form.location layout="control" %} {% endfor %} @@ -59,6 +60,7 @@
{% bootstrap_field formset.empty_form.start layout="control" %} {% bootstrap_field formset.empty_form.end layout="control" %} + {% bootstrap_field formset.empty_form.location layout="control" %}
{% endescapescript %} diff --git a/src/pretix/presale/ical.py b/src/pretix/presale/ical.py index 14a1ec9ea8..ba0571f244 100644 --- a/src/pretix/presale/ical.py +++ b/src/pretix/presale/ical.py @@ -153,7 +153,7 @@ def get_private_icals(event, positions): # Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name)) description = '\n'.join(descr) - location = None + location = ", ".join(l.strip() for l in str(pt.location).splitlines() if l.strip()) dtstart = pt.start.astimezone(tz) dtend = pt.end.astimezone(tz) uid = 'pretix-{}-{}-{}-{}@{}'.format( diff --git a/src/tests/api/test_items.py b/src/tests/api/test_items.py index 25ea7cea88..aad8d89ee2 100644 --- a/src/tests/api/test_items.py +++ b/src/tests/api/test_items.py @@ -1972,6 +1972,13 @@ def program_time2(item, category): end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc)) +@pytest.fixture +def program_time3(item, category): + return item.program_times.create(start=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc), + end=datetime(2017, 12, 31, 0, 0, 0, tzinfo=timezone.utc), + location='Testlocation') + + TEST_PROGRAM_TIMES_RES = { 0: { "start": "2017-12-27T00:00:00Z", @@ -1980,24 +1987,37 @@ TEST_PROGRAM_TIMES_RES = { 1: { "start": "2017-12-29T00:00:00Z", "end": "2017-12-30T00:00:00Z", + }, + 2: { + "start": "2017-12-30T00:00:00Z", + "end": "2017-12-31T00:00:00Z", + "location": "Testlocation", } } @pytest.mark.django_db -def test_program_times_list(token_client, organizer, event, item, program_time, program_time2): +def test_program_times_list(token_client, organizer, event, item, program_time, program_time2, program_time3): res = dict(TEST_PROGRAM_TIMES_RES) res[0]["id"] = program_time.pk res[1]["id"] = program_time2.pk + res[2]["id"] = program_time3.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[0] == resp.data['results'][0] 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'] + # assert res[1] == resp.data['results'][1] + assert res[2]['start'] == resp.data['results'][2]['start'] + assert res[2]['end'] == resp.data['results'][2]['end'] + assert res[2]['location'] == resp.data['results'][2]['location'] + assert res[2]['id'] == resp.data['results'][2]['id'] + assert res[2] == resp.data['results'][2] @pytest.mark.django_db