From c7215f4aee1017574068418e5d84605d52618bbd Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 5 May 2026 14:47:12 +0200 Subject: [PATCH] Add read-only details page for subevents --- src/pretix/control/signals.py | 10 + .../pretixcontrol/subevents/detail.html | 554 +++++++++--------- .../pretixcontrol/subevents/edit.html | 293 +++++++++ .../pretixcontrol/subevents/index.html | 2 +- src/pretix/control/urls.py | 3 +- src/pretix/control/views/subevents.py | 75 ++- src/tests/control/test_permissions.py | 6 +- src/tests/control/test_subevents.py | 4 +- 8 files changed, 666 insertions(+), 281 deletions(-) create mode 100644 src/pretix/control/templates/pretixcontrol/subevents/edit.html diff --git a/src/pretix/control/signals.py b/src/pretix/control/signals.py index 504aa2a82e..fcacb82d69 100644 --- a/src/pretix/control/signals.py +++ b/src/pretix/control/signals.py @@ -213,6 +213,16 @@ quota as argument in the ``quota`` keyword argument. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ +subevent_detail_html = EventPluginSignal() +""" +Arguments: 'subevent' + +This signal allows you to append HTML to a SubEvent's detail view. You receive the +subevent as argument in the ``subevent`` keyword argument. + +As with all event plugin signals, the ``sender`` keyword argument will contain the event. +""" + organizer_edit_tabs = DeprecatedSignal() """ Arguments: 'organizer', 'request' diff --git a/src/pretix/control/templates/pretixcontrol/subevents/detail.html b/src/pretix/control/templates/pretixcontrol/subevents/detail.html index 5bce7afbb1..a346730cde 100644 --- a/src/pretix/control/templates/pretixcontrol/subevents/detail.html +++ b/src/pretix/control/templates/pretixcontrol/subevents/detail.html @@ -4,290 +4,306 @@ {% load formset_tags %} {% load eventsignal %} {% load static %} -{% block title %}{% trans "Date" context "subevent" %}{% endblock %} +{% load money %} +{% load icon %} +{% block title %}{% blocktrans trimmed with name=subevent.name context "subevent" %}Date: {{ name }} +{% endblocktrans %}{% endblock %} {% block content %} - {% if not subevent.pk %} -

{% trans "Create date" context "subevent" %}

- {% else %} -

{% trans "Date" context "subevent" %}

- {% endif %} -
- {% csrf_token %} - {% bootstrap_form_errors form %} - {% for f in itemvar_forms %} - {% bootstrap_form_errors f %} - {% endfor %} -
-
-
- {% trans "General information" %} - {% bootstrap_field form.name layout="control" %} - {% bootstrap_field form.active layout="control" %} - {% bootstrap_field form.date_from layout="control" %} - {% bootstrap_field form.date_to layout="control" %} - {% include "pretixcontrol/event/fragment_geodata.html" %} - {% bootstrap_field form.date_admission layout="control" %} - {% bootstrap_field form.frontpage_text layout="control" %} - {% bootstrap_field form.is_public layout="control" %} - {% bootstrap_field form.comment layout="control" %} - {% if meta_forms %} - +

+ {% blocktrans trimmed with name=subevent.name context "subevent" %}Date: {{ name }}{% endblocktrans %} + {% if 'event.subevents:write' in request.eventpermset %} + + + {% trans "Edit" %} + + {% endif %} +

+
+
+
+ {% trans "General information" %} +
+
{% trans "Name" %}
+
{{ subevent.name }}
+
{% trans "ID" %}
+
#{{ subevent.pk }}
+
{% trans "Status" %}
+
+ {% if not subevent.active %} + {% trans "Disabled" %} + {% elif subevent.presale_has_ended %} + {% trans "Presale over" %} + {% elif not subevent.presale_is_running %} + {% trans "Presale not started" %} + {% else %} + {% trans "On sale" %} + {% endif %} +
+
{% trans "Event start time" %}
+
{{ subevent.date_from|date:"SHORT_DATETIME_FORMAT" }}
+
{% trans "Event end time" %}
+
{{ subevent.date_to|date:"SHORT_DATETIME_FORMAT" }}
+ {% if subevent.date_admission %} +
{% trans "Admission time" %}
+
{{ subevent.date_admission|date:"SHORT_DATETIME_FORMAT" }}
{% endif %} -
-
- {% trans "Timeline" %} - {% bootstrap_field form.presale_start layout="control" %} - {% bootstrap_field form.presale_end layout="control" %} -
-
- {% trans "Quotas" %} -
- {{ formset.management_form }} - {% bootstrap_formset_errors formset %} -
- {% for form in formset %} -
-
- {{ form.id }} - {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} -
-
-

-
-
- {% bootstrap_field form.name layout='inline' form_group_class="" %} -
-
- -
-
-

-
-
- {% bootstrap_form_errors form %} - {% bootstrap_field form.size layout="control" %} - {% bootstrap_field form.itemvars layout="control" %} - {% bootstrap_field form.release_after_exit layout="control" %} - {% bootstrap_field form.ignore_for_event_availability layout="control" %} -
-
- {% endfor %} -
- -

- -

-
-
- {% trans "Product settings" %} -

- {% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %} -

- {% for f in itemvar_forms %} -
- {% bootstrap_form_errors f %} -
- -
-
- {% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %} -
-
-
- {% bootstrap_field f.disabled layout="inline" form_group_class="" %} -
-
-
-
- - {% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}
- {% bootstrap_field f.available_from form_group_class="foo" layout="inline" %} -
-
- - {% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}
- {% bootstrap_field f.available_until form_group_class="" layout="inline" %} -
-
-
+ {% if subevent.presale_start %} +
{% trans "Start of presale" %}
+
{{ subevent.presale_start|date:"SHORT_DATETIME_FORMAT" }}
+ {% endif %} + {% if subevent.presale_end %} +
{% trans "End of presale" %}
+
{{ subevent.presale_end|date:"SHORT_DATETIME_FORMAT" }}
+ {% endif %} + {% if subevent.location %} +
{% trans "Location" %}
+
{{ subevent.location|linebreaksbr }}
+ {% endif %} +
{% trans "Show in lists" %}
+
{{ subevent.is_public|yesno }}
+ {% for k, v in subevent.meta_data.items %} +
{{ k }}
+
{{ v }}
{% endfor %} -
+ {% if subevent.comment %} +
{% trans "Internal comment" %}
+
{{ subevent.comment|linebreaksbr }}
+ {% endif %} + +
+
+ {% trans "Quotas" %} +
+ + + + + + + + + + + + {% for q in quotas %} + + + + + + + + {% endfor %} + +
{% trans "Quota name" %}{% trans "Products" %}{% trans "Total capacity" %}{% trans "Capacity left" %}
+ {{ q.name }} + {% if q.ignore_for_event_availability %} + + {% endif %} + +
    + {% for item in q.cached_items %} + {% if not item.has_variations %} +
  • + {{ item }} +
  • + {% endif %} + {% endfor %} + {% for v in q.variations.all %} +
  • + + {{ v.item }} – {{ v }}
  • + {% endfor %} +
+
{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %} + {% if 'event.items:write' in request.eventpermset %} + + {% endif %} +
+
+
+ {% if checkinlists %}
{% trans "Check-in lists" %} -

- {% blocktrans trimmed %} - You can choose to either add one or more check-in lists for every date in your series individually, - or use just one check-in list for all your dates and limit admission through check-in rules. Which - approach is better depends on multiple factors, such as the number of dates in your series. For a - series with one or less event date per day, individual lists are usually more helpful. If you - use dates to represent many time slots on the same day, or even overlapping time slots, working with - just one large check-in list will be easier. - {% endblocktrans %} -

-
- {{ cl_formset.management_form }} - {% bootstrap_formset_errors cl_formset %} -
- {% for form in cl_formset %} -
-
- {{ form.id }} - {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} -
-
-

-
-
- {% bootstrap_field form.name layout='inline' form_group_class="" %} +
+ + + + + {% if "event.orders:read" in request.eventpermset %} + + {% endif %} + + + + + + {% for cl in checkinlists %} + + + {% if "event.orders:read" in request.eventpermset %} + + {% endif %} + + + {% endfor %} - - -

- -

+ +
{% trans "Name" %}{% trans "Checked in" %}{% trans "Products" %}
+ {{ cl.name }} + +
+
+
+
-
- +
+ {{ cl.checkin_count|default_if_none:"0" }} / + {{ cl.position_count|default_if_none:"0" }}
- -
-
- {% bootstrap_form_errors form %} - {% bootstrap_field form.include_pending layout="control" %} - {% bootstrap_field form.all_products layout="control" %} - {% bootstrap_field form.limit_products layout="control" %} - {% bootstrap_field form.allow_entry_after_exit layout="control" %} - {% if form.gates %} - {% bootstrap_field form.gates layout="control" %} +
+ {% if cl.all_products %} + {% trans "All" %} + {% else %} +
    + {% for item in cl.limit_products.all %} +
  • + {{ item }} +
  • + {% endfor %} +
{% endif %} - - +
+ {% if "event.orders:read" in request.eventpermset %} + + {% endif %} + {% if "event.settings.general:write" in request.eventpermset %} + + + + {% endif %} +

- {% for f in plugin_forms %} - {% if f.title %} -
- {{ f.title }} - {% if f.template %} - {% include f.template with form=f %} - {% else %} - {% bootstrap_form f layout="control" %} - {% endif %} -
- {% endif %} - {% endfor %} + {% endif %} + {% eventsignal request.event "pretix.control.signals.subevent_detail_html" quota=quota %} +
+ {% if "event.orders:read" in request.eventpermset %} +
- {% trans "Additional settings" %} - {% for f in plugin_forms %} - {% if not f.title %} - {% if f.template %} - {% include f.template with form=f %} - {% else %} - {% bootstrap_form f layout="control" %} - {% endif %} + + {% trans "Orders" %} + + {{ order_count }} + + + {% if order_count %} +
+ + + + + + + + + {% for o in orders %} + + + + + {% endfor %} + +
{% trans "Order code" %}{% trans "Details" %}
+ + + {{ o.code }} + + +
+ {% if o.testmode %} + {% trans "TEST MODE" %} + {% endif %} + {% if o.status == "p" and o.pcnt == 0 %} + {# Everything related to this subevent is canceled #} + + + {% trans "partially canceled" %} + + {% else %} + {% include "pretixcontrol/orders/fragment_order_status.html" with order=o %} + {% endif %} +
+ {% if "." in o.sales_channel.icon %} + + {% else %} + + {% endif %} + {{ o.datetime|date:"SHORT_DATETIME_FORMAT" }} + {% if o.email %} +
{% icon "envelope-o fa-fw text-muted" %} + {{ o.email|default_if_none:"" }} + {% endif %} + {% if o.invoice_address.name %} +
{% icon "user fa-fw text-muted" %} {{ o.invoice_address.name }} + {% endif %} +
{% icon "ticket text-muted fa-fw" %} {{ o.pcnt }} + {% if o.comment %} +
+ + {{ o.comment|linebreaksbr }} + + {% endif %} + {% if o.custom_followup_due %} +
+ {% blocktrans trimmed with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %} + TODO {{ date }}{% endblocktrans %} + {% elif o.custom_followup_at %} +
+ {% blocktrans trimmed with date=o.custom_followup_at|date:"SHORT_DATE_FORMAT" context "followup" %} + TODO {{ date }}{% endblocktrans %} + {% endif %} +
+
+ {% if order_count > 10 %} +

+ + {% trans "View all" %} + +

{% endif %} - {% endfor %} + {% else %} +
+

+ {% blocktrans trimmed %} + No orders found. + {% endblocktrans %} +

+
+ {% endif %}
- {% if subevent.pk %} -
-
-
-

- {% trans "Date history" context "subevent" %} -

-
- {% include "pretixcontrol/includes/logs.html" with obj=subevent %} -
+ {% endif %} +
+
+
+

+ {% trans "Date history" context "subevent" %} +

- {% endif %} + {% include "pretixcontrol/includes/logs.html" with obj=subevent %} +
-
- -
- +
{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/subevents/edit.html b/src/pretix/control/templates/pretixcontrol/subevents/edit.html new file mode 100644 index 0000000000..5bce7afbb1 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/subevents/edit.html @@ -0,0 +1,293 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% load formset_tags %} +{% load eventsignal %} +{% load static %} +{% block title %}{% trans "Date" context "subevent" %}{% endblock %} +{% block content %} + {% if not subevent.pk %} +

{% trans "Create date" context "subevent" %}

+ {% else %} +

{% trans "Date" context "subevent" %}

+ {% endif %} +
+ {% csrf_token %} + {% bootstrap_form_errors form %} + {% for f in itemvar_forms %} + {% bootstrap_form_errors f %} + {% endfor %} +
+
+
+ {% trans "General information" %} + {% bootstrap_field form.name layout="control" %} + {% bootstrap_field form.active layout="control" %} + {% bootstrap_field form.date_from layout="control" %} + {% bootstrap_field form.date_to layout="control" %} + {% include "pretixcontrol/event/fragment_geodata.html" %} + {% bootstrap_field form.date_admission layout="control" %} + {% bootstrap_field form.frontpage_text layout="control" %} + {% bootstrap_field form.is_public layout="control" %} + {% bootstrap_field form.comment layout="control" %} + {% if meta_forms %} + + {% endif %} +
+
+ {% trans "Timeline" %} + {% bootstrap_field form.presale_start layout="control" %} + {% bootstrap_field form.presale_end layout="control" %} +
+
+ {% trans "Quotas" %} +
+ {{ formset.management_form }} + {% bootstrap_formset_errors formset %} +
+ {% for form in formset %} +
+
+ {{ form.id }} + {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} +
+
+

+
+
+ {% bootstrap_field form.name layout='inline' form_group_class="" %} +
+
+ +
+
+

+
+
+ {% bootstrap_form_errors form %} + {% bootstrap_field form.size layout="control" %} + {% bootstrap_field form.itemvars layout="control" %} + {% bootstrap_field form.release_after_exit layout="control" %} + {% bootstrap_field form.ignore_for_event_availability layout="control" %} +
+
+ {% endfor %} +
+ +

+ +

+
+
+ {% trans "Product settings" %} +

+ {% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %} +

+ {% for f in itemvar_forms %} +
+ {% bootstrap_form_errors f %} +
+ +
+
+ {% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %} +
+
+
+ {% bootstrap_field f.disabled layout="inline" form_group_class="" %} +
+
+
+
+ + {% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}
+ {% bootstrap_field f.available_from form_group_class="foo" layout="inline" %} +
+
+ + {% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}
+ {% bootstrap_field f.available_until form_group_class="" layout="inline" %} +
+
+
+ {% endfor %} +
+
+ {% trans "Check-in lists" %} +

+ {% blocktrans trimmed %} + You can choose to either add one or more check-in lists for every date in your series individually, + or use just one check-in list for all your dates and limit admission through check-in rules. Which + approach is better depends on multiple factors, such as the number of dates in your series. For a + series with one or less event date per day, individual lists are usually more helpful. If you + use dates to represent many time slots on the same day, or even overlapping time slots, working with + just one large check-in list will be easier. + {% endblocktrans %} +

+
+ {{ cl_formset.management_form }} + {% bootstrap_formset_errors cl_formset %} +
+ {% for form in cl_formset %} +
+
+ {{ form.id }} + {% bootstrap_field form.DELETE form_group_class="" layout="inline" %} +
+
+

+
+
+ {% bootstrap_field form.name layout='inline' form_group_class="" %} +
+
+ +
+
+

+
+
+ {% bootstrap_form_errors form %} + {% bootstrap_field form.include_pending layout="control" %} + {% bootstrap_field form.all_products layout="control" %} + {% bootstrap_field form.limit_products layout="control" %} + {% bootstrap_field form.allow_entry_after_exit layout="control" %} + {% if form.gates %} + {% bootstrap_field form.gates layout="control" %} + {% endif %} +
+
+ {% endfor %} +
+ +

+ +

+
+ {% for f in plugin_forms %} + {% if f.title %} +
+ {{ f.title }} + {% if f.template %} + {% include f.template with form=f %} + {% else %} + {% bootstrap_form f layout="control" %} + {% endif %} +
+ {% endif %} + {% endfor %} +
+ {% trans "Additional settings" %} + {% for f in plugin_forms %} + {% if not f.title %} + {% if f.template %} + {% include f.template with form=f %} + {% else %} + {% bootstrap_form f layout="control" %} + {% endif %} + {% endif %} + {% endfor %} +
+
+ {% if subevent.pk %} +
+
+
+

+ {% trans "Date history" context "subevent" %} +

+
+ {% include "pretixcontrol/includes/logs.html" with obj=subevent %} +
+
+ {% endif %} +
+
+ +
+
+{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/subevents/index.html b/src/pretix/control/templates/pretixcontrol/subevents/index.html index 52ac83fdf5..f73d44b927 100644 --- a/src/pretix/control/templates/pretixcontrol/subevents/index.html +++ b/src/pretix/control/templates/pretixcontrol/subevents/index.html @@ -182,7 +182,7 @@ {% endif %} {% if "event.subevents:write" in request.eventpermset %} - +