Event calendar: Allow to show a message if no events are found

This commit is contained in:
Raphael Michel
2026-08-07 14:10:30 +02:00
parent 421a2e37d0
commit 73d03aa037
16 changed files with 107 additions and 4 deletions
+17
View File
@@ -933,6 +933,23 @@ DEFAULTS = {
"is over. You can use it to describe other options to get a ticket, such as a box office.")
)
},
'event_list_empty_text': {
'default': LazyI18nString.from_gettext(
gettext_noop('No dates are currently available for sale that match your criteria.'),
),
'type': LazyI18nString,
'form_class': I18nFormField,
'serializer_class': I18nField,
'form_kwargs': dict(
label=pgettext_lazy("subevents", "Text for empty date results"),
widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': {'rows': '2'}},
help_text=pgettext_lazy("subevents", "This text will be shown if the calendar or list of dates is empty, "
"e.g. because a month does not contain any dates or a filter chosen by the user does "
"find any results. You can use this to advertise ways to get in touch with you to "
"arrange further dates. We do not recommend more than one or two sentences.")
)
},
'payment_explanation': {
'default': '',
'type': LazyI18nString,
+2
View File
@@ -600,6 +600,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
'imprint_url',
'checkout_email_helptext',
'presale_has_ended_text',
'event_list_empty_text',
'voucher_explanation_text',
'checkout_success_text',
'show_dates_on_frontpage',
@@ -734,6 +735,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
del self.fields['event_list_available_only']
del self.fields['event_list_filters']
del self.fields['event_calendar_future_only']
del self.fields['event_list_empty_text']
self.fields['primary_font'].choices = [('Open Sans', 'Open Sans')] + sorted([
(a, FontSelect.FontOption(title=a, data=v)) for a, v in get_fonts(self.event, pdf_support_required=False).items()
], key=lambda a: a[0])
@@ -275,6 +275,9 @@
{% bootstrap_field sform.event_calendar_future_only layout="control" %}
{% endif %}
{% bootstrap_field sform.low_availability_percentage layout="control" addon_after="%" %}
{% if sform.event_list_empty_text %}
{% bootstrap_field sform.event_list_empty_text layout="control" addon_after="%" %}
{% endif %}
<h4>{% trans "Order details" %}</h4>
@@ -2,6 +2,7 @@
{% load eventurl %}
{% load icon %}
{% load urlreplace %}
{% load rich_text %}
<nav aria-label="{% trans "calendar navigation" %}">
<ul class="row calendar-nav">
<li class="text-left flip">
@@ -50,4 +51,12 @@
</li>
</ul>
</nav>
{% include "pretixpresale/fragment_calendar.html" with show_avail=event.settings.event_list_availability weeks=subevent_list.weeks show_names=subevent_list.show_names %}
<div class="calendar-container">
{% include "pretixpresale/fragment_calendar.html" with show_avail=event.settings.event_list_availability weeks=subevent_list.weeks show_names=subevent_list.show_names %}
{% if subevent_list.any_events is False %}
<div class="calendar-empty">
<span class="fa fa-calendar-times-o fa-4x text-muted"></span>
{{ event.settings.event_list_empty_text|rich_text }}
</div>
{% endif %}
</div>
@@ -2,6 +2,7 @@
{% load eventurl %}
{% load icon %}
{% load urlreplace %}
{% load rich_text %}
<nav aria-label="{% trans "calendar navigation" %}">
<ul class="row calendar-nav">
<li class="text-left flip">
@@ -59,7 +60,15 @@
</li>
</ul>
</nav>
{% include "pretixpresale/fragment_week_calendar.html" with show_avail=event.settings.event_list_availability days=subevent_list.days show_names=subevent_list.show_names %}
<div class="calendar-container">
{% include "pretixpresale/fragment_week_calendar.html" with show_avail=event.settings.event_list_availability days=subevent_list.days show_names=subevent_list.show_names %}
{% if subevent_list.any_events is False %}
<div class="calendar-empty">
<span class="fa fa-calendar-times-o fa-4x text-muted"></span>
{{ event.settings.event_list_empty_text|rich_text }}
</div>
{% endif %}
</div>
<div class="row visible-xs">
<div class="col-xs-6 text-left flip">
<a href="?{% url_replace request "date" subevent_list.before|date:"o-\WW" %}"
@@ -2,6 +2,7 @@
{% load icon %}
{% load eventurl %}
{% load date_fast %}
{% load rich_text %}
<div class="event-list full-width-list alternating-rows">
{% for subev in subevent_list.subevent_list %}
@@ -43,5 +44,10 @@
</a>
</p>
</article>
{% empty %}
<div class="calendar-empty">
<span class="fa fa-calendar-times-o fa-4x text-muted"></span>
{{ event.settings.event_list_empty_text|rich_text }}
</div>
{% endfor %}
</div>
+2
View File
@@ -356,6 +356,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
after,
future_only=self.request.event.settings.event_calendar_future_only
)
context['any_events'] = any(ebd)
elif context['list_type'] == "week":
self._set_week_year()
tz = self.request.event.timezone
@@ -430,6 +431,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
after,
future_only=self.request.event.settings.event_calendar_future_only
)
context['any_events'] = any(ebd)
else:
subevents = self.request.event.subevents_sorted(
filter_qs_by_attr(
+6
View File
@@ -642,6 +642,8 @@ class WidgetAPIProductList(EventListMixin, View):
sales_channel=self.request.sales_channel,
)
if not any(ebd):
data['empty_text'] = str(rich_text(request.event.settings.event_list_empty_text, safelinks=False))
data['weeks'] = weeks_for_template(ebd, self.year, self.month)
for w in data['weeks']:
for d in w:
@@ -700,6 +702,8 @@ class WidgetAPIProductList(EventListMixin, View):
sales_channel=self.request.sales_channel,
)
if not any(ebd):
data['empty_text'] = str(rich_text(request.event.settings.event_list_empty_text, safelinks=False))
data['days'] = days_for_template(ebd, week)
for d in data['days']:
d['events'] = self._serialize_events(d['events'] or [])
@@ -770,6 +774,8 @@ class WidgetAPIProductList(EventListMixin, View):
'event_url': eventreverse_absolute(event, 'presale:event.index'),
})
if not data['events']:
data['empty_text'] = str(rich_text(request.event.settings.event_list_empty_text, safelinks=False))
cache.set(cache_key, data, 30)
# These pages are cached for a really short duration this should make them pretty accurate, while still
# providing some protection against burst traffic.
@@ -1,3 +1,35 @@
.calendar-container:has(.calendar-empty), .event-list:has(.calendar-empty) {
position: relative;
overflow: hidden;
.table-responsive, .week-calendar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
}
.calendar-empty {
position: relative;
z-index: 100;
background: rgba(255, 255, 255, 0.9);
text-align: center;
font-size: $font-size-large;
padding: 30px 0;
.fa {
margin-block: 12px;
}
*:last-child {
margin-bottom: 0;
}
}
}
.table-calendar, .week-calendar, .day-calendar {
td, th {
width: 14.29%;
@@ -513,7 +513,7 @@ h2 .label {
}
.event-list.full-width-list {
h3, h4, p {
h3, h4, article p {
margin-top: 0;
margin-bottom: 0;
}
@@ -748,6 +748,13 @@ $table-bg-accent: rgba(128, 128, 128, 0.05);
display: block;
width: 100%;
}
.pretix-widget-event-list-empty,
.pretix-widget-event-week-empty,
.pretix-widget-event-calendar-empty {
padding: 10px 0;
text-align: center;
}
}
@@ -38,6 +38,7 @@ export interface ProductListResponse {
events?: EventEntry[]
has_more_events?: boolean
meta_filter_fields?: MetaFilterField[]
empty_text?: string
weeks?: DayEntry[][]
date?: string
days?: DayEntry[]
@@ -92,7 +92,9 @@ function nextmonth () {
| {{ STRINGS.next_month }} &raquo;
//- Calendar
.pretix-widget-event-calendar-empty(v-if="!!store.emptyText", v-html="store.emptyText")
table.pretix-widget-event-calendar-table(
v-else
:id="id",
tabindex="0",
:aria-labelledby="ariaLabelledby"
@@ -62,6 +62,8 @@ console.log(store)
:event="event"
)
.pretix-widget-event-list-empty(v-if="!store.events.length && store.emptyText", v-html="store.emptyText")
p.pretix-widget-event-list-load-more(v-if="store.hasMoreEvents")
button(@click.prevent.stop="loadMore") {{ STRINGS.load_more }}
</template>
@@ -88,7 +88,8 @@ function nextweek () {
| {{ STRINGS.next_week }} &raquo;
//- Actual calendar
.pretix-widget-event-week-table(:id="id", tabindex="0", :aria-label="weekname")
.pretix-widget-event-week-empty(v-if="store.emptyText", v-html="store.emptyText")
.pretix-widget-event-week-table(v-else, :id="id", tabindex="0", :aria-label="weekname")
.pretix-widget-event-week-col(v-for="d in store.days", :key="d?.date || ''")
EventWeekCell(:day="d", :mobile="mobile")
</template>
@@ -95,6 +95,7 @@ export function createWidgetStore (config: {
offset: 0,
appendEvents: false,
metaFilterFields: [] as MetaFilterField[],
emptyText: null as string | null,
// UI state
mobile: false,
@@ -270,6 +271,7 @@ export function createWidgetStore (config: {
this.name = data.name ?? null
this.frontpageText = data.frontpage_text ?? null
this.metaFilterFields = data.meta_filter_fields ?? []
this.emptyText = data.empty_text ?? null
} else if (data.days !== undefined) {
this.days = data.days
this.date = null
@@ -279,6 +281,7 @@ export function createWidgetStore (config: {
this.name = data.name ?? null
this.frontpageText = data.frontpage_text ?? null
this.metaFilterFields = data.meta_filter_fields ?? []
this.emptyText = data.empty_text ?? null
} else if (data.events !== undefined) {
this.events = this.appendEvents && this.events
? this.events.concat(data.events)
@@ -290,6 +293,7 @@ export function createWidgetStore (config: {
this.frontpageText = data.frontpage_text ?? null
this.hasMoreEvents = data.has_more_events ?? false
this.metaFilterFields = data.meta_filter_fields ?? []
this.emptyText = data.empty_text ?? null
} else {
this.view = 'event'
this.targetUrl = data.target_url ?? this.targetUrl