Templates ausbauen

Wir wollen jetzt das bisherige Template ausbauen und ein wenig mehr über die Django-Template-Sprache lernen. Dazu werden wir noch ein Base-Template für das Projekt entwickeln und eine Detailseite für die Kategorie-Informationen erstellen. Wir brauchen dafür natürlich wieder eine URL, eine View und ein Template.

Template-Angaben in den Settings

Wenn wir ein neues Projekt beginnen, setzen wir in der settings.py Datei des Projekt zwei wichtige Angaben.

Templates, die das gesamte Projekt betreffen, zb. das Base-Template, müssen für alle Apps verfügbar sein. Diese Projekt-Templates liegen in einem Ordern, der in den Settings definiert wird. Für diese Angabe steht der Key DIRS in den Template-Settings zur Verfügung. Wir wollen unsere Projekt-Templates also unter event_manager/templates speichern. Jede andere Ort wäre aber auch denkbar.

App-spezifische Templates, wie die Übersicht der Kategorien, speichern wir auch in der jeweiligen App, zb. event_manager/events/templates/events/categories.html. Damit Django in den Apps nach Templates sucht, muss APP_DIRS in den settings.py auf True gesetzt werden.

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "event_manager" / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

Mehr dazu unter https://docs.djangoproject.com/en/ref/intro/tutorial03/

Die Template-Hierachie

Das Base-Template (base.html) enthält alle Elemente und Strukturen, die jede Page des Projekts haben soll. Zum Beispiel einen Headbereich mit Navigation, eine Seitenleiste, einen Footer mit Links und so weiter.

Ein Basis-Template ist das grundlegende Template, welches dann auf jeder einzelnen Seite einer Website mit spezifischeren Anweisungen erweitert wird.

Die Sub-Templates definieren ebenfalls diese Blöcke, diesmal allerdings mit Inhalt gefüllt, und rendern sie in das Base-Template. Das geschieht dadurch, dass das Sub-Template das Basis-Layout erweitert (extends). Durch geschickte Schachtelung, können tiefe Template-Hierachien geschaffen werden.

ein Base-Template für das Projekt

Unsere projektspezifischen Templates liegen also unter event_manager/templates. Dort legen wir eine neue Datei namens base.html an. In diese Datei kopieren wir folgenden Inhalt:

<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}
<html lang='{{ LANGUAGE_CODE }}'>

<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="author" content="">
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>

<h1>{% block head %}{% endblock %}</h1>

<div>
{% block content %}
{% endblock %}
</div>

</body>

</html>

Es handelt sich um eine einfache HTML-Struktur mit zwei sogenannten Tags. Diese Tags definieren in diesem Fall Blöcke (block), in die später Content gerendert wird. Es gibt aber noch viele andere Tags und wir können natürlich auch selber eigene Tags definieren.

Der LANGUAGE_CODE ist der Code, den wir in der settings.py für die aktuelle Sprache eingetragen hatten. Er wäre an dieser Stelle aktuell noch nicht notwendig, aber wir nutzen ihn trotzdem schon mal. Für diesen Zweck hatten wir {% load i18n %} ins Template geladen.

In den Block head soll eine Überschrift gerendert werden, in den Block content wird der Inhalt kommen, also zum Beispiel die Liste der Kategorien.

Die Namen head und content sind übrigens von uns frei gewählt, wir könnten sie auch kopf und inhalt nennen.

Diese base.html wird nicht direkt aufgerufen, sondern wir werden sie quasi vererben.

Tags und Variablen

die Django-Template-Sprache definiert zwei Typen von Anweisungen: Tags und Variablen. Tags bieten Funktionalität während Variablen Platzhalter sind, in die Werte gerendert werden. Beides hatten wir in event_manager/events/templates/events/categories.html schon gesehen.

Tags haben dir Form {% TAG %} während Variblen durch die doppelten geschweiften Klammern definiert werden {{ VARIABLE }}. Mehr dazu später.

Das Base-Template erweitern

Um das Base-Template zu nutzen, ändern wir event_manager/events/templates/events/categories.html wie folgt ab:

{% extends 'base.html' %}

{% block head%}
Übersicht der Kategorien
{% endblock %}

{% block content %}
    <ul>
    {% for cat in categories %}
    <li>
        {{cat.name}}
    </li>
    </a>
    {% endfor %}
{% endblock %}

Durch das extends-Tag erweitern wir unser Template jetzt und definieren den Inhalt, der in die Blöcke von base.html gerendert werden soll. In dem Fall rendern wir Text in den head-Block und die Liste mit den Kategorien in den content-Block.

Wenn wir jetzt mit python manage.py runserver den Entwicklungsserver starten und auf die Kategorie-Übersicht navigieren (), sehen wir im Ergebnis keinen Unterschied zu vorher. Ist ja auch klar, wir hatten am Style an sich nichts verändert.

Bootstrap HTML

Wir wollen nun das Base-Template nochmal abändern und es ein bisschen besser formatieren. Dazu nutzen wir das Bootstrap-Framework (https://getbootstrap.com/).

Die neue base.html Datei sieht nun so aus:

<!DOCTYPE html>
{% load i18n %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<html lang='{{ LANGUAGE_CODE }}' class="h-100">
<head>
  <meta charset="utf-8">
  <title>{% block title %}{% endblock %}</title>
  <meta name="author" content="{% block author %}{% endblock %}">
  <meta name="description" content="{% block description %}{% endblock %}">
  <meta name="viewport" content="width=device-width, initial-scale=1">

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link href="{% static 'css/style.css' %}" rel="stylesheet" />

</head>

<body class="d-flex flex-column h-100">


<header class="p-3 bg-dark text-white">

    <div class="container">
      <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
        <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
            <img src="{% static 'images/penglogo.png' %}" style="width:50px; margin-right:10px;" />
        </a>

        <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0" style="margin-left:120px;">

          <li><a href="" class="nav-link px-2
                  text-white">Events</a></li>

          <li><a href="" class="nav-link px-2
                  text-white">Kategorien</a></li>
        </ul>

        <form action="" method="get" class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
          <input name="q" required="required" type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search">
        </form>

        <div class="text-end">

          {% if user.is_authenticated %}

          <div class="dropdown text-end">
          <a href="#" class="d-block link-dark text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
            <img src="https://github.com/mdo.png" alt="mdo" width="32" height="32" class="rounded-circle">
          </a>
          <ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1">
            <li><a class="dropdown-item" href="#">Event anlegen</a></li>

            {% comment %} 
            <!-- PASSWORD CHANGE: Freischalten, wenn der Login und Signup implementiert wird -->
            <li><a class="dropdown-item" href="{% url 'password_change' %}">Passwort ändern</a></li>
            {% endcomment %}
            <li><a class="dropdown-item" href="">Passwort ändern</a></li>
            <li><hr class="dropdown-divider"></li>

            {% comment %} 
            <!-- LOGOUT: Freischalten, wenn der Login und Signup implementiert wird -->
            <li>
                <!-- ab django 4.2 ist kein logout per GET mehr erlaubt! -->
                <form method="POST" 
                      action="{% url 'logout' %}">
                        {% csrf_token %}
                      <button class="dropdown-item">Sign out</button>
                </form>
            </li>
            {% endcomment %}
            <li>
              <a class="dropdown-item" href="">Sign out</a>
            </li>

          </ul>
        </div>

          {% else %}

          {% comment %} 
          <!-- das Freischalten, wenn der Login und Signup implementiert wird -->
          <a href="{% url 'login' %}">
          <button type="button" class="btn btn-outline-light me-2">Login</button>
          </a>
          {% endcomment %}
          <a href="">
          <button type="button" class="btn btn-outline-light me-2">Login</button>
          </a>

          {% comment %} 
          <!-- das Freischalten, wenn der Login und Signup implementiert wird -->
          <a href="{% url 'user:signup' %}">
          {% endcomment %}
          <a href="">
          <button type="button" class="btn btn-warning">Sign-up</button>
          </a>

         {% endif %}
        </div>
      </div>
    </div>
</header>

<main class="flex-shrink-0">

<div class="container">
<div class="row">
    <div class="col-12" style="margin-top:25px;">
    {% block head %}{% endblock %}
    </div>
</div>
</div>

<div class="container">
<div class="row">
  {% block content %}{% endblock %}
</div>
</div>

</main>


<footer class="footer mt-auto py-3 text-light bg-dark">
  <div class="container">
    <p class="float-end mb-1">
      <a href="#">Back to top</a>
    </p>
    <h3 class="mb-1">Event Manager</h3>
    <p class="mb-0">Neu in Django? <a href="/">Visit the Pingu homepage</a>
    oder <a href="https://djangoheroes.spielprinzip.com">Leg los mit Django</a>.</p>
  </div>
</footer>

<!-- JavaScript Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>

</html>

In dem Template finden sich einige Template-Kommentare, die mit dem comment-Tag umgesetzt wurden. Insbesonder wurden damit HTML-Elemente und Verlinkungen auskommentiert, die aktuell noch zu einem Fehler führen würden, da die entsprechenden Pfade in den urls.py - Dateien nicht angelegt wurden:

{% comment %}
<!-- das Freischalten, wenn der Login und Signup implementiert wird -->
<a href="{% url 'login' %}">
<button type="button" class="btn btn-outline-light me-2">Login</button>
</a>
{% endcomment %}

In diesem Beispiel sieht man, dass der Login-Button auskommentiert wurde. Den Kommentar werden wir entfernen, wenn wir die Authentifizierung implementieren.

Den Code kann man sich hier bequem runterladen: Code Base HTML:

statische CSS-Datei

Damit dieses Template später funktioniert und richtig angezeigt wird, muss eine statische CSS-Datei angelegt werden. Dafür werden wir unter event_manager/static ein Verzeichnis für statische Dateien anlegen.

Dort erstellen wir noch ein Verzeichnis für CSS-Dateien unter event_manager/static/css/ und legen dort die Datei style.css an.

In diese neu erstelle CSS-Datei unter event_manager/static/css/style.css kopieren wir folgenden Inhalt.

main>.container {
  padding: 20px 25px 0;
}

.event_box a {
  text-decoration: none;
}

.event_box li {
  margin-bottom: 15px;
}

.form-signin {
  width: 100%;
  max-width: 330px;
  padding: 15px;
  margin: auto;
}

.form-signin .checkbox {
  font-weight: ref;
}

.form-signin .form-floating:focus-within {
  z-index: 2;
}


.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

ul {
  padding-left: 0px;
  list-style-type: none;
  margin-left: 0px;
}

ul li a {
  text-decoration: none;
}

.card a {
  text-decoration: none;
}

.card h3,
.card p {
  color: #333;
}

Der aktuelle Verzeichnisbaum sieht aktuell so aus:

├── event_manager
│   ├── db.sqlite3
│   ├── event_manager
│   │   ├── settings.py
│   │   ├── templates
│   │   │   └── base.html
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── events
│   ├── manage.py
│   ├── static
│   │   ├── css
│   │   │   └── style.css
│   │   └── images
│   │       └── penglogo.png
│   └── user
│       ├── models.py
│       └── views.py
├── README.md
├── requirements-dev.in
├── requirements-dev.txt
├── requirements.in
└── requirements.txt

Das neu angelegte static - Verzeichnis liegt also auf der gleichen Ebene, wie das manage.py. Dort befinden sich aktuell die css/style.css und das Logo.

Was ist CSS?

Cascading Style Sheets ist eine Stylesheet-Sprache für elektronische Dokumente und zusammen mit HTML und JavaScript eine der Kernsprachen des World Wide Webs. Sie ist ein sogenannter living standard ‚lebendiger Standard‘ und wird vom World Wide Web Consortium beständig weiterentwickelt. (Wikipedia)

statische Logo-Datei

Das Logo kann unter https://github.com/realcaptainsolaris/event_manager_code/raw/main/media/penglogo.png heruntergeladen und in das statische Verzeichnis unter event_manager/static/images/penglogo.png kopiert werden.

Auf das HTML bzw. CSS in der Datei einzugehen, würde den Rahmen dieses Buches sprengen. Im Netz finden sich aber unzählige Tutorials, um schnell einen Überblick über CSS zu erhalten.

Im base-Template haben wir noch einen weiteren Block title eingefügt, um der rerenderten Seite einen Seitentitel zu geben. Dieser Seitentitel wird zum Beispiel in dem entsprechenden Brower-Tab angezeigt.

Ändern wir event_manager/events/templates/events/categories.html noch ab:

{% extends 'base.html' %}

{% block title %}
Übersicht der Kategorien
{% endblock %}

{% block head %}
<h1>Übersicht der Kategorien</h1>
{% endblock %}

{% block content %}
    <ul>
    {% for cat in categories %}
    <li>
        {{cat.name}}
    </li>
    </a>
    {% endfor %}
{% endblock %}

Mehr dazu unter https://docs.djangoproject.com/en/ref/topics/templates/

Wenn wir jetzt die Seite unter http://127.0.0.1:8000/events/categories sehen wir zwar ein verändertes Layout, allerdings fehlt das Logo. Es fällt auch auf, dass die Datei style.css nicht geladen werden konnte. Damit diese beiden Resourcen gefunden werden, müssen wir in der settings.py noch etwas ändern. Das machen wir aber im nächsten Kapitel (siehe statische Dateien einbinden).

Intermezzo: die Django-Template-Sprache

Um das Arbeiten mit Templates zu erleichtern, haben wir gesehen, dass uns Django die Möglichkeit gibt, direkt im Template über Listen zu iterieren oder Variablen auszugeben. Und dazu haben wir die sogenannte Django-Template-Sprache genutzt. Solche Template-Engines gibt es in den meisten Web-Frameworks in ähnlicher Art und Weise, zum Beispiel das bekannte Twig aus dem PHP-Framework Symphony 1.

Wozu noch eine neue Sprache?

Wenn wir Daten an den User ausgeben wollen, gehört diese Aufgabe zur sogenannten Präsentationsebene. Die Datenverarbeitung in den Views und Models ist eher auf der Applikationslogik zu finden. Generell gilt: die Formatierung und optische Aufbereitung von Daten zum Zweck der Präsentation darf nicht in der Appikationsebene vorgenommen werden. Wir wollen Logik und Ausgabe strikt getrennt halten.

Variablen

Wenn die Template-Engine auf eine Variable trifft, die als Key im Kontext-Dictionary übergeben wurde, wertet sie diese Variable aus und rendert sie in das Template. Variablen sehen so aus: {{ Variable }}.

Variablennamen bestehen aus einer beliebigen Kombination von alphanumerischen Zeichen und dem Unterstrich („_“), dürfen jedoch nicht mit einem Unterstrich beginnen. Falls für eine Variable kein Wert verfügbar ist, resultiert das in keinem Fehler.

Oben im Code sehen wir die Variable {{cat.name}}. Dort wird das name-Attribut eines Kategorie-Objekts gerendert. Wir haben in den Variablen via der Dot-Notation also auch ganz wie gewohnt Zugriff auf die Attribute eines Objekts, wie wir das von Python gewohnt sind.

Filter

Der Inhalt der Variablen kann im Template an Filter übergeben werden, die vom Prinzip her ähnlich aufgebaut sind, wie der Pipe-Operator auf der Unix - Shell.

Ein Filter kann über diesen Operator an die Variable angehängt und deren Ausgabe verändern, zum Beispiel den Beschreiungstext in Kleinbuchstaben ausgeben:

{{ company.description|lower }}
Wichtige Filter in den Django-Templates

capfirst

enspricht dem Capitalize aus Python

date

zur schönen Formatierung von Datumsangaben

default

Der Defaultwert einer leeren Variable

escape

konvertiert Zeichen in HTML-Entsprechungen

linebreaks

Konvertiert Newlines in HTML-Breaks

pluralize

pluralisiert Wörter

striptags

löscht HTML-Tags

Filter lassen sich auch chainen, dh. verketten. Der fertige Filter reicht den Output an den nächsten Filter in der Reihe weiter:

{{ company.description|striptags|lower|capfirst }}

So wird die Beschreibung erst von HTML-Tags bereinigt, dann in Kleinbuchstaben unmgewandelt um schließlich via capfirst alles wieder zu Kapitalisieren. Erst dann wird die Variable in den HTML-Code gerendert.

Der Vollständigkeitshalber sei noch erwähnt, dass es nicht sonderlich schwierig ist, Filter selber zu erstellen. Filter sind nichts anderes als Python-Funktionen, die man registrieren und laden muss, um sie im Template zu nutzen. Mehr dazu findet sich in der Doku.

Weitere Built-in Filter finden sich hier: https://docs.djangoproject.com/en/stable/ref/templates/builtins/#filter

Tags

Tags sind komplexer als Variablen: Einige erstellen Text in der Ausgabe, andere steuern den Fluss durch Ausführen von Schleifen oder Logik und einige laden externe Informationen in die Vorlage, die von späteren Variablen verwendet werden sollen. Tags sehen so aus: {% tag %}. Es gibt Tags, die aus einem öffnenden und schließenden Teil bestehen sowie einzelstehende Tags.

Wir hatten im Code bisher den for - Tag gesehen.

For - Tag

{% for company in companies %}
    <li>{{ company.name }}</li>
{% endfor %}

if - else - Tag

Natürlich können wir auch den Kontrollfluss mit Bedingungen regeln:

{% if categories.exist %}
    Es sind Kategorien vorhanden
{% else %}
    Es befinden sich keine Kategorien im System!
{% endif %}

if - elif - else (hier mit der Filter-Direktive length) Wie wir sehen können, lassen sich sogar arithmetische Operatoren in der Django-Template-Sprache nutzen:

{% if categories|length > 5 %}
    Es binden sich mehr als 5 Kategorien im System
{% elif companies|length > 3 %}
    Es binden sich mehr als 3 Kategorien im System
{% else %}
    Es binden sich wenige Kategorien im System
{% endif %}

Die Django-Template-Sprache ist eine ziemlich komplexe Sprache und bietet unzählige Möglichkeiten, Daten zu präsentieren.

Mehr zu Tags und Variablen in der Doku: https://docs.djangoproject.com/en/stable/templates/language/

So sieht unsere Seite zur Zeit aus:

../_images/category_overview_neu.png

Die Detailseite einer Kategorie

Um von der Kategorie-Übersicht auf die Detailseite einer Kategorie weiterzuleiten, müssen wir die Liste verlinken. Die URL zur Detailseite zu einer Kategorie ist allerdings noch nicht festgelegt, das müssten wir machen, bevor wir die Verlinkung einbauen können.

Dazu ändern wir die urlpatterns in den event_manager/events/urls.py ab:

urlpatterns = [
    path("hello_world", views.hello_world, name="hello_world"),
    path("categories", views.categories, name="categories"),

    # diese Zeile hinzufügen:
    path("category/<int:pk>", views.category_detail, name="category_detail"),
]

Das heisst, wenn wir http://127.0.0.1:8000/events/category/3 via Browser aufrufen, sollte uns die Detailansicht einer Kategorie ausgegeben werden.

Die View für die Detailseite der Kategorie

Die View category_detail existiert in den views.py noch nicht, deshalb legen wir sie an. Lade das entsprechende Objekt aus der Datenbank, und werfen eine 404 Not Found Exception, falls das Objekt nicht gefunden wird. Die Http404 Exception muss importiert werden:

from django.shortcuts import render
from django.http import Http404
from .models import Event, Category

def category_detail(request, pk: int):
    """
    die Detailseite einer Kategorie
    http://127.0.0.1:8000/events/category/3
    """
    try:
        category = Category.objects.get(pk=pk)
    except Category.DoesNotExist:
        raise Http404("Diese Kategorie existiert nicht")

    return render(request, 'events/category_detail.html', {
                'category': category
    })

404 Fehler

Wenn eine URL nicht angezeigt werden kann, weil zum Beispiel das gesuchte Objekt nicht in der Datenbank vorhanden ist, lösen wir einen Http404-Fehler aus. Dem User wird daraufhin eine 404-Fehlerseite angezeigt (Not found), die wir später noch als Template anlegen werden.

Falls wir in den event_manager/settings.py DEBUG=True gesetzt haben, bekommen wir eine ausführliche Fehlerseite angezeigt, die uns detailliert aufzeigt, wo das Problem liegt. Im Live-Modus sollte zwingend DEBUG=False sein. Dann könnten wir unsere eigene 404-Fehlerseite anzeigen.

Wir rendern das Kategorieobjekt category in das Template event_manager/events/templates/events/category_detail.html.

Das Template für die Detailseite der Kategorie

Legen wir eine neue Datei event_manager/events/templates/events/category_detail.html im Template-Ordner der App an und kopieren folgenden Inhalt hinein:

{% extends 'base.html' %}
{% load i18n %}

{% block title %}
Kategorie {{category}}
{%endblock%}

{% block head %}
<h1>Kategorie {{category}}</h1>
{%endblock%}

{% block content %}
<p><a href="{% url 'events:categories' %}">zurück zur Kategorie Übersicht</a></p>
<p>{{category.description}}</p>
<p>created at: {{category.created_at}}</p>
{%endblock%}

Wir definieren hier unter anderem einen Link, der uns zurück zur Übersichtsseite führt. Wie solche Verlinkungen in Django funktionieren, sehen wir gleich weiter unten genauer.

Wenn wir jetzt eine Detailseite aufrufen, sollte das klappen. Dazu starten wir den Runsever mit python manage.py runserver und navigieren auf die URL http://127.0.0.1:8000/events/category/3. Falls hier ein Not-Found Error kommt, muss womöglich eine andere ID gewählt werden (dazu in der Admin-Oberfläche nachschauen, welche Kategorien vorhanden sind).

../_images/category_detail_1.png

Prüfung ob Vorhanden und der 404-Fehler

In der Funktion category_detail hatten wir im try-except-Block geprüft, ob das Selektieren des Objekts über die übergebenen ID keinen Fehler schmeisst, und hatten im Erfolgsfall das Objekt in category gespeichert. Im Fehlerfall hatten wir mit raise eine Http404-Exception ausgelöst.

Dieser Fall, also das Prüfen des Vorhandenseins eines Objektes, ist ein so häufiger Use-Case, dass es dafür einen Shortcut gibt: die Funktion get_object_or_404. Die macht das, was wir vorhin umständlich gemacht hatten, in einer Zeile.

Wir ändern die View event_manager/events/views.py und ersetzen die Funktion category_detail mit dieser hier. Am Seitenanfang importieren wir noch get_object_or_404 aus den Shortcuts.

from django.shortcuts import render, get_object_or_404
from django.http import Http404

def category_detail(request, pk: int):
    """
    http://127.0.0.1:8000/events/category/3
    """
    category = get_object_or_404(Category, pk=pk)

    return render(request, 'events/category_detail.html', {
                'category': category
    })

get_object_or_404

Für die immerwiederkehrende Aufgabe, ein Objekt aus der Datenbank zu holen und per try-except zu prüfen, ob es in der Datenbank vorhanden ist, nutzen wir ab jetzt den Shortcut get_object_or_404, den wir aus den django.shortcuts importieren. Wenn sich das Objekt nicht in der Datenbank befinden, wird ein 404-Fehler ausgelöst und dem User wird eine 404-Fehlerseite anzeigt.

Wir versuchen jetzt, die URL so aufzurufen: http://127.0.0.1:8000/events/category/3

Die Detailseite verlinken

Zuletzt wollen wir noch die Verlinkung auf die Kategorie bauen. Ändern wir dazu event_manager/events/templates/events/categories.html noch ab.

{% extends 'base.html' %}

{% block title %}
Übersicht der Kategorien
{% endblock %}

{% block head %}
<h1>Übersicht der Kategorien</h1>
{% endblock %}

{% block content %}
    <ul>
    {% for category in categories %}
    <li>
        <a href="{% url 'events:category_detail' category.pk %}">{{category.name}}</a>
    </li>
    </a>
    {% endfor %}
{% endblock %}

Pro Schleifendurchlauf bauen wir ein Listen-Elemente li, und setzen dort einen Hyperlink zu der jeweiligen Kategorie. Der Hyperlink wird mit einem URL-Tag angegeben, der in der Form APP-Name:PFAD-Name angeben wird, sowie dahinter die jeweiligen Parameter.

Mehr dazu in der Doku: https://docs.djangoproject.com/en/stable/templates/builtins/#url

Die Verlinkungen sollten jetzt blau hinterlegt sein:

../_images/category_overview_verlinkt.png

Verlinkungen mit dem URL-Tag

Einen Template-Tag, den wir noch nicht besprochen haben, ist der url-Tag. Dieser Tag ist dafür da, URLs zu generieren. Zum Erstellen der URL sind zwei Dinge wichtig: der app_name in der urls.py und der Name des Pfades, so wie er in der path-Funktion der urlpatterns festgelegt wurde.

Der URL-Tag hat immer folgendes Schema:

{% url „<APP_NAME:PATH_NAME>“ <PATH_ARGUMENTS (optional)> %}

  • APP_NAME: das entspricht der Variable app_name aus den urls.py der App, also hier events (siehe Das Anlegen der ersten App).

  • PATH_NAME: das enstpricht dem name-Argument der path-Funktion aus den App-Urls.

  • PATH_ARGUMENTS: falls unsere Route einen oder mehrere Parameter definiert hat, in unserem Fall also einen Primary Key, werden diese als Leerzeichen-separierte Liste angegeben.

path("category/<int:pk>", views.category_detail, name="category_detail")

Mehr zum Thema url-Template Tag in der Django-Doku: https://docs.djangoproject.com/en/stable/templates/builtins/#url

URL-Tag vs. hardkodierte URL

Wir erstellen die Verlinkungen über den URL-Tag, und das ist auch der richtige Weg, dies zu tun. Wir könnten die Verlinkung natürlich auch direkt hardkodiert erstellen, zb so:

<a href="/events/category/{{cat.pk }}">{{cat}}</a>

Das würde gehen, gilt aber als schlechter Stil. Falls sich nämlich der URL-Pfad der Route ändern würde, zum Beispiel von events/category zu events/genre, müsste man den Link an allen Stellen, wo man ihn so hardkodiert definiert hat, manuell ändern. Und diese Änderungen kommen häufiger vor, als man meinen würde.

Wenn wir jetzt den runserver starten, sollten wir unter http://127.0.0.1:8000/events/categories die Kategorien in einer Liste sehen.

1

https://twig.symfony.com/