Eine Pages App erstellen

Bevor wir mit der Events-App weitermachen, wollen wir noch eine pages- App erstellen, die mehrheitlich statische Seiten wie zum Beispiel die Homepage oder das Impressum bereitstellt.

Fangen wir mal mit der Homepage an.

App installieren

Um eine App innerhalb eines Projekts anzulegen, nutzen wir wie gewohnt das Sub-Kommando startapp gefolgt von dem Namen der gewünschten App.

(eventenv) python manage.py startapp pages

und registrieren sie in den event_manager/event_manager/settings.py

INSTALLED_APPS = [
    "user",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "crispy_bootstrap5",
    "crispy_forms",
    "events",
    "pages"
]

Die URLS anlegen

Projekt URLs erweitern

Dazu öffnen wir erstmal die Projekt-URLs unter event_manager/event_manager/urls.py und fügen den Path zu den pages.urls ein:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("events/", include("events.urls", namespace="events")),
    path("", include("pages.urls", namespace="pages")), # <= das hier einfügen!
]

Dadurch, dass das erste Argument für die path-Funktion ein leerer String ist, können wir erreichen, dass unsere Index-Page bzw. das Impressum unter der Hauptdomain angezeigt wird. Die URL für die Index-Seite wäre dann zum Beispiel www.example.com und für das Impressum www.example.com/imprint. Alle anderen Apps müssen wie gewohnt mit ihrem Pfad aufgerufen werden.

App URLs anlegen

Jetzt müssen noch die URLs auf App-Ebene festgelegt werden. Dazu legen wir die Datei event_manager/pages/urls.py an und modifzieren den Inhalt wie folgt:

from django.urls import path
from . import views

app_name = "pages"

urlpatterns = [
    path('', views.HomePageView.as_view(), name='home'),
]

View für die Homepage einrichten

Die View, auf die wir in den urlpatterns referenzieren, muss jetzt noch unter event_manager/pages/views.py angelegt werden.

from django.views.generic.base import TemplateView

class HomePageView(TemplateView):
    """Das ist eine generische, klassenbasierte View."""

    template_name = "pages/index.html"

Template für die Homepage

Wir legen das Template für die Homepage unter event_manager/pages/templates/pages/index.html an und fügen folgenden Inhalt ein:

{% extends "base.html" %}

{% block title %}Homepage{% endblock %}

{% block head %}
<h1>Event Manager Startseite</h1>
{% endblock %}

Wenn wir das die URL 127.0.0.1:8000 aufrufen, sollte uns jetzt die Homepage angezeigt werden.

../_images/homepage_1.png

Auf die selbe Art könnte man nun weitere Pages wie zum Beispiel ein Impressum oder eine About-Seite anlegen. Das überlasse ich mal dem Leser als kleine Übungsaufgabe.

Den Kontext für die Homepage verändern

Wir wollen auf der Homepage nun die top 5 Kategorien auflisten, die die meisten Events haben. Dazu müssen wir einen Filter erstellen und die Methode get_context_data überschreiben.

from django.views.generic.base import TemplateView
from django.db.models import Count
from events.models import Category


class HomePageView(TemplateView):
    template_name = "pages/index.html"

    def get_context_data(self, **kwargs) -> dict:
        context = super().get_context_data(**kwargs)

        categories = Category.objects.annotate(
            number_events=Count("events")
        ).order_by("-number_events")[:5]

        context.update({"categories": categories})
        return context

Dann passen wir nochmal das Homepage-template unter event_manager/pages/templates/pages/index.html an und fügen folgenden Inhalt ein:

{% extends "base.html" %}

{% block title %}Homepage{% endblock %}

{% block head %}
<h1>Event Manager Startseite</h1>
{% endblock %}

<h2>Kategorien</h2>

<ul>
{% for category in categories %}
    <li>
    <a href="{% url 'events:category_detail' category.pk  %}">
    {{category}}
    </a>
    <span class="badge rounded-pill bg-primary">
    {{category.number_events}}
    </span>
    </li>
{% endfor %}
</ul>
../_images/homepage_2.png

Kleine Übung

Wer will, kann jetzt versuchen, zusätzlich noch 5 zufällige Events auf der Startseite darzustellen. Dazu lässt sich die gut die sample Funktion des random-Moduls nutzen.

Die Lösung könnte dann aussehen:

from random import sample
from django.views.generic.base import TemplateView
from django.db.models import Count
from events.models import Category, Event

def get_sample_events(n: int):
    """Erzeuge eine zufällige Liste mit n Event-Objekten."""
    return sample(list(Event.objects.all()), n)

class HomePageView(TemplateView):
    template_name = "pages/index.html"

    def get_context_data(self, **kwargs) -> dict:
        context = super().get_context_data(**kwargs)

        categories = Category.objects.annotate(
            number_events=Count("events")
        ).order_by("-number_events")[:5]

        events = get_sample_events(n=5)

        context.update({"categories": categories, "events": events})
        return context

Im Template würde man die erzeugten Events dann so ausgeben:

<h2>Events</h2>

 <ul class="list-group event_box">
 {% for event in object_list %}
     <a href="{% url 'events:event_detail' event.pk %}">

     <li class="list-group-item rounded">
     <small><span>am {{event.date}}</span></small><br>
     <b>{{event.name}}</b>
         <span class="badge rounded-pill bg-primary">{{event.category.name}}</span>
         <p>by {{event.author}}</p>
     </li>
     </a>
 {% endfor%}
 </ul>
../_images/homepage_3.png

Die Abfrage ist allerdings aus Performance-Sicht nicht ideal. Wir erstellen erst eine Liste aller Objekte und iterieren dann über die Objekte, während wir uns im Template auf den Autor sowie die Kategorie beziehen.

Wir müssen also die Tabellen, die zu Event in Beziehung stehen, noch vorladen, diesmal nutzen wir dazu select_related:

def get_sample_events(n: int):
    """Erzeuge eine zufällige Liste mit n Event-Objekten."""
    return sample(list(Event.objects.select_related("author","category").all()), n)

Die Anzahl der Datenbank-Anfragen hat sich nun drastisch reduziert:

../_images/homepage_4.png