Enhance event card layouts, timeline styles, and add template tests

main
Arne Schauf 3 weeks ago
parent 1c4cfb25b1
commit 207bdbc6ce
  1. 4
      core/static/css/theme/base.css
  2. 91
      core/static/css/timeline.css
  3. 45
      core/templates/core/event_history_page.html
  4. 37
      core/templates/core/event_index_page.html
  5. 65
      core/tests.py

@ -66,6 +66,10 @@ a:hover::after {
width: 100%;
}
a.btn::after {
display: none;
}
/* Form controls */
.form-control:focus {
border-color: var(--bs-primary);

@ -1,7 +1,7 @@
/* Timeline styling */
.timeline {
position: relative;
padding: 0;
padding: 20px 0;
list-style: none;
}
@ -12,17 +12,18 @@
bottom: 0;
left: 40px;
width: 4px;
background-color: var(--bs-primary);
background: linear-gradient(to bottom, var(--bs-primary) 0%, rgba(var(--bs-primary-rgb), 0.2) 100%);
border-radius: 2px;
}
.timeline-item {
position: relative;
margin-bottom: 50px;
margin-bottom: 60px;
}
.timeline-badge {
position: absolute;
top: 0;
top: 10px;
left: 40px;
width: 60px;
height: 60px;
@ -36,61 +37,111 @@
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 5px;
border: 4px solid #fff;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.timeline-item:hover .timeline-badge {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
.timeline-badge-day {
font-size: 1.25rem;
font-size: 1.3rem;
font-weight: bold;
line-height: 1;
}
.timeline-badge-month {
font-size: 0.8rem;
font-size: 0.85rem;
text-transform: uppercase;
line-height: 1;
font-weight: 600;
}
.timeline-month-header {
position: relative;
margin-bottom: 30px;
margin-bottom: 40px;
list-style: none;
}
.timeline-month-header h3 {
position: relative;
padding-left: 60px;
border-bottom: 2px solid var(--bs-primary);
padding-bottom: 10px;
padding-left: 85px;
border-bottom: 2px solid rgba(var(--bs-primary-rgb), 0.2);
padding-bottom: 12px;
font-weight: 700;
color: var(--bs-primary);
display: inline-block;
}
.timeline-panel {
position: relative;
width: calc(100% - 90px);
width: calc(100% - 100px);
float: right;
border-radius: 8px;
border-radius: 12px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 1px solid rgba(0,0,0,0.05) !important;
}
.timeline-panel:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1) !important;
}
.timeline-panel:before {
content: '';
position: absolute;
top: 26px;
left: -15px;
border-top: 15px solid transparent;
border-right: 15px solid #fff;
border-bottom: 15px solid transparent;
top: 24px;
left: -12px;
border-top: 12px solid transparent;
border-right: 12px solid #fff;
border-bottom: 12px solid transparent;
filter: drop-shadow(-2px 0 2px rgba(0,0,0,0.03));
}
.timeline-date {
display: block;
margin-bottom: 10px;
font-weight: bold;
font-weight: 600;
color: var(--bs-primary);
}
.timeline-panel .card-title a {
transition: color 0.2s ease;
}
.timeline-panel .card-title a:hover {
color: var(--bs-primary) !important;
}
@media (max-width: 767px) {
.timeline:before {
left: 30px;
}
.timeline-badge {
margin-left: 0;
left: 30px;
width: 50px;
height: 50px;
margin-left: -25px;
}
.timeline-badge-day {
font-size: 1.1rem;
}
.timeline-badge-month {
font-size: 0.75rem;
}
.timeline-panel {
width: calc(100% - 70px);
}
.timeline-month-header h3 {
padding-left: 65px;
}
}

@ -20,24 +20,24 @@
<span class="timeline-badge-month">{{ event.value.start_date|date:"b" }}</span>
</div>
<div class="timeline-panel card border-0 shadow-sm">
<div class="card-header bg-light">
<div class="card-header bg-light border-0 rounded-top">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="fs-5">{{ event.value.start_date|date:"SHORT_DATE_FORMAT" }}{% if event.value.end_date %} - {{ event.value.end_date|date:"SHORT_DATE_FORMAT" }}{% endif %}</span>
<span class="fs-5 fw-bold text-primary"><i class="bi bi-calendar-event me-2"></i>{{ event.value.start_date|date:"SHORT_DATE_FORMAT" }}{% if event.value.end_date %} - {{ event.value.end_date|date:"SHORT_DATE_FORMAT" }}{% endif %}</span>
</div>
<div>
<span class="text-muted">{{ event.value.start_date|date:"F Y" }}</span>
</div>
</div>
</div>
<div class="card-body">
<h3 class="card-title mb-3">
<div class="card-body px-4 py-4">
<h3 class="card-title mb-3 fw-bold">
{% if event.type == 'page' %}
<a href="{% pageurl event.page %}">{{ event.value.title }}</a>
<a href="{% pageurl event.page %}" class="text-decoration-none text-dark">{{ event.value.title }}</a>
{% else %}
{{ event.value.title }}
{% endif %}
{% if event.value.subtitle %}<br><small class="text-muted">{{ event.value.subtitle }}</small>{% endif %}
{% if event.value.subtitle %}<br><small class="text-muted fw-normal fs-5">{{ event.value.subtitle }}</small>{% endif %}
</h3>
<div class="row">
<div class="col-md-4 mb-3 mb-md-0">
@ -45,38 +45,27 @@
{% image event.value.image max-250x250 class="img-fluid rounded" %}
{% endif %}
</div>
<div class="col-md-4 mb-3 mb-md-0">
<h5 class="mb-3">
{{ event.value.start_date }}{% if event.value.end_date %} - {{ event.value.end_date }}{% endif %}
</h5>
<p>
{% if event.value.location_name %}<strong>{{ event.value.location_name }}</strong><br>{% endif %}
{% if event.value.location_city %}{{ event.value.location_city }}{% endif %}
<div class="col-md-4 mb-3 mb-md-0 d-flex flex-column justify-content-center">
{% if event.value.location_name or event.value.location_city %}
<p class="mb-0 text-muted">
{% if event.value.location_name %}<strong><i class="bi bi-geo-alt-fill me-2"></i>{{ event.value.location_name }}</strong><br>{% endif %}
{% if event.value.location_city %}<span class="ms-4">{{ event.value.location_city }}</span>{% endif %}
</p>
{% endif %}
</div>
<div class="col-md-4">
<div class="col-md-4 d-flex flex-column justify-content-center">
{% if event.type == 'manual' %}
{% if event.value.flyer.url %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.value.flyer.url }}" target="_blank"><i class="bi bi-cloud-download"></i> Programm herunterladen</a>
</p>
<a class="btn btn-outline-primary w-100 mb-2 rounded-pill shadow-sm" href="{{ event.value.flyer.url }}" target="_blank"><i class="bi bi-cloud-download me-1"></i> Programm herunterladen</a>
{% endif %}
{% if event.value.additional_info_target.url %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.value.additional_info_target.url }}"><i class="bi bi-info-circle"></i>
{{ event.value.additional_info_btn_label }}</a>
</p>
<a class="btn btn-outline-secondary w-100 rounded-pill shadow-sm" href="{{ event.value.additional_info_target.url }}"><i class="bi bi-info-circle me-1"></i> {{ event.value.additional_info_btn_label }}</a>
{% endif %}
{% elif event.type == 'page' %}
{% if event.value.flyer %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.value.flyer.url }}" target="_blank"><i class="bi bi-cloud-download"></i> Programm herunterladen</a>
</p>
<a class="btn btn-outline-primary w-100 mb-2 rounded-pill shadow-sm" href="{{ event.value.flyer.url }}" target="_blank"><i class="bi bi-cloud-download me-1"></i> Programm herunterladen</a>
{% endif %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{% pageurl event.page %}"><i class="bi bi-info-circle"></i> Details anzeigen</a>
</p>
<a class="btn btn-outline-secondary w-100 rounded-pill shadow-sm" href="{% pageurl event.page %}"><i class="bi bi-search me-1"></i> Details anzeigen</a>
{% endif %}
</div>
</div>

@ -23,50 +23,41 @@
{% if event.is_partner_event %}
<div class="partner-event-ribbon">Partner Event</div>
{% endif %}
<div class="card-header bg-light">
<div class="card-header bg-light border-0 rounded-top">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="fs-5">{{ event.start_date|date:"SHORT_DATE_FORMAT" }}{% if event.end_date %} - {{ event.end_date|date:"SHORT_DATE_FORMAT" }}{% endif %}</span>
<span class="fs-5 fw-bold text-primary"><i class="bi bi-calendar-event me-2"></i>{{ event.start_date|date:"SHORT_DATE_FORMAT" }}{% if event.end_date %} - {{ event.end_date|date:"SHORT_DATE_FORMAT" }}{% endif %}</span>
</div>
<div>
</div>
</div>
</div>
<div class="card-body">
<h3 class="card-title mb-3"><a href="{{ event.url }}" class="text-decoration-none">{{ event.title }}</a>
{% if event.subtitle %}<br><small class="text-muted">{{ event.subtitle }}</small>{% endif %}</h3>
<div class="card-body px-4 py-4">
<h3 class="card-title mb-3 fw-bold"><a href="{{ event.url }}" class="text-decoration-none text-dark">{{ event.title }}</a>
{% if event.subtitle %}<br><small class="text-muted fw-normal fs-5">{{ event.subtitle }}</small>{% endif %}</h3>
<div class="row">
<div class="col-md-4 mb-3 mb-md-0">
{% if event.img1_img %}
{% image event.img1_img max-250x250 class="img-fluid rounded" %}
{% endif %}
</div>
<div class="col-md-4 mb-3 mb-md-0">
<h5 class="mb-3">
{{ event.start_date }}{% if event.end_date %} - {{ event.end_date }}{% endif %}
</h5>
<div class="col-md-4 mb-3 mb-md-0 d-flex flex-column justify-content-center">
{% if event.location_name %}
<p>
<strong>{{ event.location_name }}</strong><br>
{% if event.location_street %}{{ event.location_street }}<br>{% endif %}
{% if event.location_city %}{{ event.location_city }}{% endif %}
<p class="mb-0 text-muted">
<strong><i class="bi bi-geo-alt-fill me-2"></i>{{ event.location_name }}</strong><br>
{% if event.location_street %}<span class="ms-4">{{ event.location_street }}</span><br>{% endif %}
{% if event.location_city %}<span class="ms-4">{{ event.location_city }}</span>{% endif %}
</p>
{% endif %}
</div>
<div class="col-md-4">
<div class="col-md-4 d-flex flex-column justify-content-center">
{% if event.is_registration_active %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.url }}"><i class="bi bi-people"></i> Zur Anmeldung</a>
</p>
<a class="btn btn-primary w-100 mb-2 rounded-pill shadow-sm" href="{{ event.url }}"><i class="bi bi-people me-1"></i> Zur Anmeldung</a>
{% endif %}
{% if event.flyer.url %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.flyer.url }}" target="_blank"><i class="bi bi-cloud-download"></i> Programm herunterladen</a>
</p>
<a class="btn btn-outline-primary w-100 mb-2 rounded-pill shadow-sm" href="{{ event.flyer.url }}" target="_blank"><i class="bi bi-cloud-download me-1"></i> Programm herunterladen</a>
{% endif %}
<p>
<a class="btn btn-outline-primary w-100 mb-2" href="{{ event.url }}"><i class="bi bi-search"></i> Mehr Informationen</a>
</p>
<a class="btn btn-outline-secondary w-100 rounded-pill shadow-sm" href="{{ event.url }}"><i class="bi bi-search me-1"></i> Mehr Informationen</a>
</div>
</div>
</div>

@ -0,0 +1,65 @@
from datetime import date, timedelta
from django.test import TestCase
from wagtail.models import Page, Site
from core.models import EventIndexPage, EventPage, HomePage
class EventIndexPageTemplateTests(TestCase):
@classmethod
def setUpTestData(cls):
default_site = Site.objects.filter(is_default_site=True).first()
if default_site:
cls.home_page = default_site.root_page.specific
default_site.hostname = 'testserver'
default_site.site_name = 'Test site'
default_site.save()
else:
root_page = Page.get_first_root_node()
cls.home_page = HomePage(title='Home', slug='home')
root_page.add_child(instance=cls.home_page)
cls.home_page.save_revision().publish()
Site.objects.create(
hostname='testserver',
root_page=cls.home_page,
is_default_site=True,
site_name='Test site',
)
cls.index_page = EventIndexPage(title='Events', slug='events-overview')
cls.home_page.add_child(instance=cls.index_page)
cls.index_page.save_revision().publish()
def test_event_index_page_renders_enhanced_timeline_layout(self):
event_page = EventPage(
title='Spring Academy',
slug='spring-academy',
subtitle='Music and masterclasses',
start_date=date.today() + timedelta(days=14),
end_date=date.today() + timedelta(days=16),
location_name='Konzerthaus',
location_street='Street 1',
location_city='10115 Berlin',
show_in_event_calendar=True,
is_partner_event=True,
registration_start_date=date.today(),
)
self.index_page.add_child(instance=event_page)
event_page.save_revision().publish()
response = self.client.get(self.index_page.url)
self.assertContains(response, 'event-index-timeline')
self.assertContains(response, 'timeline-page-hero')
self.assertContains(response, 'event-timeline-card')
self.assertContains(response, 'event-timeline-placeholder')
self.assertContains(response, 'Partner Event')
self.assertContains(response, 'Zur Anmeldung')
self.assertContains(response, 'Konzerthaus')
def test_event_index_page_shows_empty_state_without_events(self):
response = self.client.get(self.index_page.url)
self.assertContains(response, 'timeline-empty-state')
self.assertContains(response, 'Zurzeit sind keine Veranstaltungen geplant.')
Loading…
Cancel
Save