Das Anlegen der ersten App

Wir wollen nun unsere erste App anlegen. Diese App soll sich um das Verwalten der Events in unserem Projekt kümmern und heisst deshalb auch events. Verwalten heisst zum Beispiel: Anlegen neuer Events, Löschen und Auflisten von bestehenden Events, Anzeigen von Events auf einer Detailseite.

Insbesondere müssen also die grundlegenden CRUD-Operationen implementiert werden, die im Einsatz von Datenbanken bzw. sonstigem persistenten Speicher nötig sind.

CRUD

Das Akronym CRUD steht für:

  • Create: Anlegen eines Datensatzes

  • Retrieve: Lesen eines Datensatzes

  • Update: Datensatz aktualisieren

  • Delete: Datensatz löschen

https://de.wikipedia.org/wiki/CRUD

App events anlegen

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

(eventenv) python manage.py startapp events

manage.py hat uns nun ein weiteres Verzeichnis events erstellt:

event_manager
    ├── manage.py
    ├── db.sqlite3
    ├── events
        ├──  migrations/
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── models.py
        ├── tests.py
        ├── views.py

    └── event_manager
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

Was ist ein guter App-Name?

Bei Django-Apps handelt es sich um Python Packages, deshalb benötigen sie auch einen validen Namen nach der Python-Konvention. https://www.python.org/dev/peps/pep-0008/

Der Name einer App sollte also kurz sein und nur Kleinbuchstaben verwenden. Unterstriche können verwendet werden, wenn dies die Lesbarkeit erhöht. Der App Name kann singular oder plural sein. Es muss sich um einen validen Python Package Namen handeln.

Beispiele für gute App Namen

  • geodjango

  • pandas

  • blog

Beispiele für schlechte App Namen

  • python_django_blog_app

  • BLOGGER

Das Verzeichnis der neuen App

Wenn wir das Verzeichnis events jetzt mal genauer betrachten, sehen wir darin folgendes:

event_manager
└── events
    |   migrations
    │   admin.py
    │   apps.py
    │   models.py
    │   tests.py
    │   views.py
    │   __init__.py

__init__.py

Die Datei __init__.py definiert, wie schon beim Konfigurationsverzeichnis, dass das Verzeichnis events ein Python Paket ist.

models.py

Die Models unserer Applikation, also die Geschäftsobjekte, werden wir gleich selber in der Datei models.py anlegen. Daraus werden dann später u.a. die Datenbank-Tabellen generiert.

tests.py

Unit-Tests werden per default in der Datei tests.py erstellt. Diese Datei werden wir jetzt löschen und später hier ein Test-Verzeichnis erstellen.

views.py

Last but not least die wohl wichtigste Datei: In der views.py liegen die Views der Applikation. Die Views sind die Steuer-Funtionen unserer App und reagieren auf die diversen Aktionen mit einer HTTP-Response.

Model-View-Template Muster

Django verwendet eine Unterart des Model-View-Controller Musters, nämlich das MVT-Musters. Der klassische View ist bei Django ein Template, wohingegen der View in Django in etwa dem Controller entspricht. Die Models sind die Models und repräsentieren die Geschäftsobjekte.

../_images/mvt.png

urls.py

Was jetzt noch fehlt, ist eine Datei urls.py, damit wir die Views auch ansteuern können.

Das holen wir jetzt nach und erstellen unter event_manager/events die Datei urls.py. Das Verzeichnis events sollte nun so aussehen:

└── events
    |   migrations
    │   admin.py
    │   apps.py
    │   models.py
    │   views.py
    |   urls.py    <= diese Datei wurde manuell hinzugefügt
    │   __init__.py

URLs entkoppeln

Unter event_manager/event_manager/urls.py liegen die Projekt-URLs. Rein prinzipiell könnte man auch alle URLs, die im Projekt verwendet werden, dort notieren. Das hätte aber den Nachteil, dass die URLs der App mit dem Projekt und anderen Apps komplett verdrahtet sind.

Unser Ziel ist es aber, jede App so unabhängig wie möglich zu machen und eine loose Koppelung der Apps mit dem Projekt zu erreichen. Wir notieren also alle URLs, die die App betreffen, auch in der App und importieren diese dann später in die Projekt-URLs. Somit wäre eine loose Koppelung erreicht.

Deshalb gilt es als good Practice, jeder App eine eigene urls.py zu geben.

Die erste View

Die erste View in unserer App soll nur eine Test-View sein, damit wir sehen, ob unsere App funktioniert. Dazu modifizieren wir die Datei event_manager/events/views.py. Eine View muss IMMER einen Http-Response zurückgeben, deshalb importieren wir aus django.http die HttpResponse.

HttpResponse ist eine Klasse aus dem http-Paket von Django. Sie erstellt uns den nötigen HTTP-Response inklusive dem Header, den wir dann aus der View an den Client zurückgeben können.

from django.http import HttpResponse

def hello_world(request) -> HttpResponse:
    return HttpResponse("Djangoheroes to the rescue!")

Aufgabe einer View

Jede View ist für eine von zwei Aufgaben verantwortlich: ein HttpResponse-Objekt zurückzugeben, welches den Inhalt für die angeforderte Aktion enthält oder dem Auslösen einer Exception.

Die View kann Datensätze aus einer Datenbank lesen, Daten in ein Template rendern, eine PDF-Datei generieren, eine XML-Datei oder zip-Datei erstellen. Was auch immer in der View passiert, sie muss ein HttpResponse-Objekt zurückzugeben. Ausnahme ist lediglich der Fehlerfall, wenn eine Exception erhoben wird.

Diese View löst zum Beispiel eine Exception aus:

from django.http import HttpResponse

def hello_world(request) -> HttpResponse:
    raise ValueError("Exception is coming home!")
    return HttpResponse("Djangoheroes to the rescue!")

Moment, was ist denn eine View überhaupt?

Im Django-Framework sind Views Python-Funktionen oder -Klassen, die eine HTTP-Anfrage entgegennehmen (Request) und eine Http-Antwort (Response) zurückgeben. Ein User, der eine Anfrage an unser Webprojekt stellt, wird also über den URL-Resolver auf eine View geleitet.

Views werden grundsätzlich in der Datei views.py einer App erstellt. Da eine App viele Views haben kann, kann die views.py ziemlich groß und unübersichtlich werden. Deshalb sollte man sich bemühren, hier Ordnung zu halten.

App in den Settings registrieren

Damit die App von Django später bei der Datenbank-Migration auch gefunden wird, müssen wir sie in den Settings registrieren. Dazu öffnen wir die Datei event_manager/event_manager/settings.py und modifizieren folgende Zeilen:

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"events", # <= diesen Eintrag hinzufügen
]

Mehr zu den Settings gibt es in der Doku: https://docs.djangoproject.com/en/stable/settings/

URLs festlegen

Um die View jetzt aufrufen zu können, müssen wir für dieses App die URL anlegen, d.h. der Pfad, den der User im Browser eingeben muss, um diese App aufrufen zu können.

Projekt URLs

Dazu öffnen wir erstmal die Projekt-URLs unter event_manager/event_manager/urls.py und modifzieren den Inhalt wie folgt:

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

urlpatterns = [
    path("admin/", admin.site.urls),
    path("events/", include("events.urls")),
]

Pattern Matching

Hier haben wir jetzt den ersten Teil der URL festgelegt. Django untersucht die von uns im Browser eingegebene URL und prüft, ob der erste Teil des URL-Pfades davon mit dem ersten Argument von path matcht.

  • http://127.0.0.1/super/hello_heroes super matcht weder mit dem Begriff admin noch mit events. Es wird kein URL-Pfad gefunden, und Django serviert uns einen 404-Fehler. Prüfen wir das doch gleich mal.

  • http://127.0.0.1/events/hello_pingus events matcht mit events. Also werden nun die urls.py aus der App events inkludiert und die Prüfung für den restlichen Teil des URL-Pfades wird in den urls.py der inkludierten App fortgeführt.

Include

Der include-Befehl importiert alle Url-Angaben, die in event_manager/events/urls.py festgelegt wurden. Mit dem namespace-Keyword-Argument behalten wir uns die Möglichkeit vor, die URLs der App unter einem anderen Namespace aufzurufen. In diesem Buch beschäftigen wir uns mit dieser Möglichkeit nicht weiter, es ist aber gute Practice, das hier gleich mit anzugeben. Der Namespace sollte hier per default einfach der Name der App sein.

Der Pfad zu unserer App

Unsere App heisst events und so haben wir uns entschlossen, jede erdenkliche URL zu dieser APP, die wir später entwerfen werden, ebenfalls mit events anzusprechen, nämlich im ersten Argument der path-Funktion. Das könnte zum Beispiel die URL http://127.0.0.1:8000/events/event/3 für eine Detail-Ansicht eines Events sein oder http://127.0.0.1:8000/events für die Übersicht aller Events.

Wichtig: Dieser Pfad-Name muss nicht so heissen, wie die App! Wir hätten auch definieren können, dass der Pfad ereignisse heisst. http://127.0.0.1:8000/ereignisse/3

Wir sind also im URL-Design relativ flexibel und können das jederzeit im Projektverlauf anpassen!

App URLs

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

from django.urls import path
from . import views

app_name = "events"

urlpatterns = [
    path("hello-world", views.hello_world, name="hello_world"),
]

in der Variable app_name wird immer der Name der jeweiligen App eingetragen. Anhand dieser Variable können wir später im Template auf die URLs referenzieren.

Auch hier haben wir wieder eine urlpatterns-Liste. Diese wird später in event_manager/event_manager/urls.py per include inkludiert. Das name-Argument von path() sollte eindeutig sein. Keine schlechte Idee ist es, es genauso zu bennen, wie die View in dem jeweiligen Path.

Gucken wir uns nochmal die URL an, die der User eingeben wird:

http://127.0.0.1:8000/events/hello-world

Damit hätten wir auch den zweiten Teil der URL, also das hello-world definiert. Wenn wir jetzt nochmal den Runserver starten und http://127.0.0.1:8000/events/hello-world in den Browser eingeben, sollten wir eine weiße Webseite mit dem Text „Djangoheroes to the rescue!“ auf dem Bildschirm sehen

(eventenv) python manage.py runserver

Das Request-Objekt

Jede View, sei sie funktions- oder klassenbasiert, erhält das Request-Objekt als Argument übergeben. Das Request-Objekt enthält alle möglichen Daten über den HTTP-Request wie die GET-und POST Daten, URL-Pfade oder gesendete Cookies. Mit request.method kann zum Beispiel die gewählte HTTP-Methode untersucht werden.

Mit print(dir(request)) können wir das Request-Objekt auch untersuchen.

Verändern wir die hello_world Funktion nochmal und lassen uns ein paar Informationen ausgeben:

from django.http import HttpResponse

def hello_world(request) -> HttpResponse:
    print("das Request Objekt: ", dir(request))
    print("verwendete HTTP-Methode: ", request.method)
    print("Der User: ", request.user)
    return HttpResponse("Djangoheroes to the rescue!")

Wenn wir jetzt nochmal die URL im Browser aufrufen, http://127.0.0.1:8000/events/hello-world, können wir in der Ausgabe der Shell den angemeldeten User und die verwendete HTTP-Methode sehen.

Mehr zum Request-Objekt findet sich in der Doku: https://docs.djangoproject.com/en/stable/request-response/

Print-Aufrufe in der View

Standardmäßig werden print-Aufrufe in einer View auf der Konsole ausgegeben. Das ist für die ersten Schritte in Ordnung. Später werden wir noch eine bessere Methode kennenlernen, nämlich das Loggen mit dem logging-Modul in Dateien.

Mehr zum Anlegen von Apps findet ihr im offiziellen Django-Tutorial:

https://docs.djangoproject.com/en/ref/intro/tutorial01/

Zusammenfassung

Eine Zusammenfassung aller nötigen Schritte zum Erstellen eines Projekts und Anlegen einer ersten App findet ihr im Kapitel Cookbooks: Cookbook 1, ein Projekt erstellen.