- Django per Repository installieren
- Environment für Projekt anlegen
- Projekt mit startproject einrichten
- Apps als Module eines Projekts
- Verarbeitung von Anfragen
- HTML-Dateien als Templates
- Formulare
- Umgang mit Datenbanken
- Views für die Datenverwaltung
- Mehrere Tabellen/Objekte
- Die Forms von Django
- Django Admin
- Authentifizierung
- Zugriff auf APIs von REST-Datenquellen
- Links und Literatur
Für dynamische Seiten wie etwa Shops werden Seiten benötigt, die sich serverseitig dynamisch verändern, beispielsweise anhand von Datenbankinhalten. Solche Systeme der Webprogrammierung können auch in anderen Programmiersprachen umgesetzt werden.
- Bei Jakarta/JEE wird ein Application Server gestartet, der Java-programmierte Servlets ausführt.
- Bei PHP liefert Apache den Interpreter als Modul mit, der die die Webseiten dynamisch verändert.
- Bei Node.js & Co wird der Server in JavaScript geschrieben.
Django per Repository installieren
Unter Linux (Debian, Ubuntu, Linux Mint) installiert man Django mit dem Befehl:apt-get install python3-djangoDamit steht Django allen Benutzern des Systems zur Verfügung. Außerdem wird das Paket python3-venv installiert.
apt install python3-venv
Visual Code
Man kann Django mit Kommandozeilen-Tools bearbeiten. Etwas angeneher ist die Verwendung von Visual Code. Unter Ubuntu Users wird beschrieben, wie man es installiert.Visual Code wird über den Befehl code gestartet.
code .
Durch den Punkt hinter dem Befehl wird das aktuelle Verzeichnis als Arbeitsverzeichnis gewählt. Will man ein anderes, kann dieses über den Explorer (links oben) ausgewählt werden.
Über den Menüpunkt Terminal|New Terminal kann eine Shell gestartet werden.
Environment für Projekt anlegen
Nun kann ein Environment in einem Verzeichnis erstellt werden. Darin wird sich das Projekt befinden. Es ist also auch der Ort, an dem man mit dem Befehl git init das ein Git-Projekt anlegt.Das Django-Environment wird angelegt. Dieses enthält neben den Sourcen auch die verwendeten Tools. Aufruf:
python3 -m venv env
python3 ruft explizit die Version 3 von Python, da Python 2 nicht kompatibel ist, aber noch verbreitet ist. Aus diesem Grund steht der Aufruf python nicht auf allen Systemen (insbesondere Linux und Mac) zur Verfügung.
Das Verzeichnis env entsteht. Gern wird als Verzeichnisname auch .env verwendet. Das führt unter Nicht-Windows-Systemen dazu, dass das Environment ohne explizite Nennung verdeckt bleibt.
Aktivieren unter Linux und Mac
Im aktuellen Verzeichnis ist ein Verzeichnis namens env oder .env entstanden. Es muss noch aktiviert werden. Unter Unixoiden (Linux, Mac) rufen Sie auf:source env/bin/activateWer mag, kann von hier aus auch die Entwicklungsumgebung code starten. Als Argument übergibt man den Pfad, in diesem Fall also den Punkt. Natürlich kann man auch direkt auf dem Terminal arbeiten.
Aktivieren auf Windows mit CMD.EXE
Bei Windows steht der entsprechende Skript unter env/Skripts/activate.bat.Das Skript existiert auch in einer Power-Shell-Version an gleichem Ort mit der Erweiterung ps1. Das Skript activate sorgt dafür, dass die Pfade auf dieses Verzeichnis umgebogen werden und so erreicht wird, dass Django nur im lokalen Umfeld installiert wird.
Aktivieren auf Windows mit Power-Shell
Für die Powershell liegt ein eigener Skript parallel zu activate.bat vor. Die Powershell darf aber nicht in jeder Umgebung Skripten ausführen.Test der Aktivierung
Nach der Aktivierung des Environment sollte auch der Python-Interpreter aus dem Environment gezogen werden. Linux und Mac können mit dem Befehl which feststellen, welche Instanz bei Aufruf eines Befehls gezogen wird.$ which python /home/.../env/bin/pythonWindows kennt den Befehl nicht, aber den Befehl where, der wohl ähnlich funktioniert wie der Befehl whereis unter Linux. Er zählt alle Pfade auf, unter denen der Befehl aufrufbar ist. Der erste sollte sich dann im Environment befinden.
Reaktivierung
Die Aktivierung muss jedes Mal neu erfolgen, wenn eine neue Shell gestartet wird.Installation von Django per PIP
Nun installieren Sie Django. Tatsächlich wird hier ein Großbuchstabe verwendet.pip install DjangoIn diesem Verzeichnis kann nun ein Projekt eingerichtet werden.
Projekt mit startproject einrichten
Nach der Installation steht der Befehl django-admin zur Verfügung. Mit diesem erzeugt man ein Projektverzeichnis, in dem die Infrastruktur weitgehend vorbereitet wird.Mit dem Argument startproject wird ein Projekt erzeugt. Als weiteren Parameter muss der Name des Projekts angegeben werden. Im folgenden Beispiel ist das fewo.
django-admin startproject fewoEs entsteht ein Verzeichnis namens fewo. Darin liegt wiederum ein Verzeichnis fewo mit einigen Python-Sourcen. Ansonsten eine Datei namens manage.py.
Nun wechselt man in das Projektverzeichnis und ruft die Datei manage.py über den Python-Interpreter auf:
cd fewo python3 manage.py runserverDer Server startet und blockiert anschließend die Konsole. Durch die Tastenkombination [Strg]+[C] kann er beendet und die Konsole wieder freigegeben werden.
Der Server meldet, dass er unter der Portnummer 8000 zu erreichen ist. Um das zu testen, ruft man einen Browser auf und gibt als URL localhost:8000 ein. Es meldet sich die Standard-Django-Seite mit der Gratulation, dass die Installation geklappt hat.
Möglicherweise tritt auch eine Fehlermeldung auf, die besagt, dass es noch "unapplied migrations" gäbe und schlägt auch gleich die Behandlung vor:
python manage.py migrateEs sind Python-Programme unter fewo/fewo verfügbar:
- settings.py
- urls.py: Hierin steht, welches Modul sich um welche URL kümmert.
- asgi.py
- wsgi.py
Apps als Module eines Projekts
Eine App ist ein Modul, das ein Objekt oder datenbanktechnisch eine Tabelle umfasst. Eine App bietet typischerweise eine Auflistung, eine Eingabe, eine Änderung und ein Löschen des Objekts an.Am Beispiel des Ferienwohnungsprojekt erstellt man für den Gast, für die Wohnung und für die Buchung je eine App.
Für jede App wird später eine Klasse in models.py definiert. Für das Navigieren zwischen den Funktionen wird eine urls.py eingesetzt.
Erzeugen einer App
Ein Projekt kann mehrere Apps enthalten. Die Apps können aber auch in anderen Projekten wiederverwendet werden. Wir legen eine App an. Wir nennen diese gast. Sie soll sich um den Gast kümmern.python3 manage.py startapp gastNun wird ein Verzeichnis gast unter dem Hauptverzeichnis fewo parallel zu dem Unterverzeichnis fewo angelegt. Sowohl das Unterverzeichnis fewo als auch das Verzeichnis gast enthalten nun diverse Python-Dateien. Davon sind besonders views.py und models.py wichtig. Daneben gibt es die Dateien tests.py, apps.py und admin.py.
fewo (Das Projekt) +-- fewo | +-- urls.py - Pfade des Projekts | +-- gast | +-- urls.py - Pfade der App (muss später angelegt werden) | +-- models.py | +-- views.py | : | +-- manage.py
Infrastruktur der App in urls.py
Zunächst muss die Infrastruktur für den Server angelegt werden. Diese befindet sich in dem Unterverzeichnis, das den Projektnamen trägt, also hier fewo/fewo. Dort wird die Datei urls.py mit einem Editor bearbeitet. Dieses wird bei Aufruf des Servers mit der URL localhost:8000/gast die Anfragen an das Modul gast weiterleiten.
Die Projekt-urls.py
Die Datei urls.py im Verzeichnis fewo/fewo ist bereits angelegt. Dort findet man neben ausführlichen Kommentaren einige Codezeilen:from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ]Hier wird das Modul gast eingetragen. Dazu wird der Pfad gast/ eingetragen. Sobald eine URL mit diesem Pfad auftaucht, soll sich die App gast darum kümmern. Der Name der App wird in app_name angegeben.
from django.urls import path, include app_name="gast" urlpatterns = [ path('admin/', admin.site.urls), path('gast/', include('gast.urls')), ]Der Aufruf include sucht im Pfad gast nach einer weiteren Datei urls.py. Da es die bisher nicht gibt, muss sie im Verzeichnis gast angelegt werden. Ihre Aufgabe ist es, die Anfragen innerhalb der App zu verteilen.
Die App-urls.py
Im Unterverzeichnis gast wird eine eigene Datei namens urls.py angelegt. Als erstes wird der Leerstring definiert. Er soll alle Aufrufe der App erst einmal auf die Funktion index in der Datei views.py weiterleiten.from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]Später werden noch weitere Funktionen hinzukommen.
Verarbeitung von Anfragen
Die urls.py-Dateien leiten die Anfrage typischerweise zu views.py-Dateien. Diese ist durch den Aufruf von python3 manage.py startapp gast bereits erzeugt worden. Die Datei gast/views.py wird bearbeitet und die Funktion index definiert. Sie wird eine HTTP-Anfrage des Browsers bekommen und muss eine Antwort an den Browser erstellen. Dazu gibt sie eine HttpResponse zurück. Die einfachste Variante ist es, dem HttpResponse-Konstruktor einen String als Parameter zu übergeben.from django.http import HttpResponse def index(request): return HttpResponse("<h1>Hallo</h1>")Zum Test kann nun der Server wieder gestartet werden.
python manage.py runserverBeim Aufruf von localhost:8000/gast erscheint nun ein Hallo in großen Buchstaben. Falls aber etwas nicht korrekt konfiguriert ist, erhalten Sie auf der Konsole eine Fehlermeldung. Sie können direkt Änderungen an den Dateien vornehmen. Bei Änderungen wird die Konfiguration üblicherweise neu eingelesen.
Der Parameter Request und die Rückgabe Response
Die Funktion index erhält als Parameter die Anfrage des Clients vom Typ HttpRequest. Dieses Objekt enthält alle möglichen Attribute einer HTTP-Anfrage. Die Funktion liefert als Rückgabe HttpResponse.Die Request
Die Request ist in django.http.HTTPRequest definiert. Sie hat die folgenden Attribute:- scheme: Hier finden Sie typischerweise die Strings 'http' oder 'https'
- method ('GET', 'POST', 'PUT', 'DELETE', ...)
- GET: holt eine Datei
- POST: legt ein neues Objekt an, bei wiederholtem Aufruf wird ein weiteres Objekt angelegt.
- PUT: ändert ein Objekt, sofern es vorliegt, ansonsten legt es ein neues an.
- PATCH: ändert Teile eines Objekts
- DELETE: löscht ein Objekt
- OPTIONS: fragt, welche Optionen ein Server unterstützt
- HEAD: holt nur den head-Bereich einer Datei.
- path
- content-type: MIME-Typ des Objekts
- 'text/plain'
- 'text/html'
- 'text/css'
- 'image/png'
- 'application/json'
- 'application/xml'
- 'video/mp4'
- GET bzw POST Ein Objekt, das dictionary-artig ist
- headers ist eine Liste
- session
- user
Die Response
Die Antwort wird in django.http.HTTPResponse definiert und muss von einer view-Funktion zurückgegeben werden, wie im Beispiel oben gesehen:from django.http import HttpResponse def index(request): return HttpResponse("<h1>Hallo</h1>")Eine Response hat einige Attribute, die später noch genutzt werden.
- content: Der Inhalt (Body) der Seite
- status_code: Liefert den HTTP-Status-Code einer Anfrage.
- headers: Liste der Headereinträge
Dynamische Response
Als Antwort können wir den String auch in einer Variablen definieren und diese an HttpResponse weitergeben.Um dynamische Antworten zu geben, können in den String lokale Variablen eingebettet werden. Das ist natürlich auch mit den üblichen String-Manipulationen möglich. Eine häufig verwendete Sonderform für Python stellt der f-String dar.
from django.http import HttpResponse def index(request): name = "James Bond" text = f"<h1>Hallo {name} </h1>") return HttpResponse(text)Wird vor den String ein f gestellt, kann innerhalb des Strings in geschweiften Klammern eine Variable gesetzt werden, die dann an dieser Stelle ausgewertet wird.
Die Ausgabe von Listen wird immer wieder gebraucht. Hier können die Möglichkeiten von Python im Umgang mit Listen ausgiebig genutzt werden.
from django.http import HttpResponse def index(request): name = "Reisegruppe 007" actlist = ["Sean Connery", "Roger Moore", "Timothy Dalton", "Pierce Brosnan"] liste = "".join([f"<li>{act}" for act in actlist]) text =f""" <h1> {name} </h1> <ul> {liste} </ul> """ return HttpResponse(text)Die Pythonlisten können in den Text so integriert werden, dass sie in Kombination mit den UL- und LI-Kommandos von HTML eine hübsche Darstellung der Liste möglich macht.
Damit haben wir hier erreicht, dass wir mit einem Python-Programm die Antwort an den Browser dynamisch verändern können. Dabei können wir alle Möglichkeiten nutzen, die uns Python zur Verfügung stellt.
HTML-Dateien als Templates
Es ist etwas umständlich, auf jede Client-Request größere HTML-Dokumente als String zu senden. Darum wird im ersten Schritt eine Django-App dazu gebracht, eine gewöhnliche HTML-Datei auszuliefern und damit das zu tun, was jeder Web-Server als Antwort auf eine GET-Request tut.
- In der App wird ein Ordner templates angelegt.
- In dem Ordner templates wird wiederum ein Ordner für jede App angelegt. Praktischerweise trägt dieser den Namen der App.
- Darin wird für dieses Beispiel eine Datei mit dem Namen index.html angelegt und mit HTML-Code gefüllt.
- Der Gesamtpfad wäre in unserem Beispiel gast/templates/gast/index.html
INSTALLED_APPS = [ 'gast.apps.GastConfig', 'django.contrib.admin', ...In der apps.py der App ist eine Config-Klasse automatisch definiert.
from django.apps import AppConfig class GastConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "gast"
Die View (views.py) der App kann nun angepasst werden, um die Datei index.html zu laden.
from django.shortcuts import render def index(request): return render(request, "gast/index.html")Die Funktion render leitet die Request an die HTML-Datei weiter. Auf diese Weise kann die HTML-Datei Informationen über die Anfrage erhalten.
Vererbung von Templates
Oft sind einige Teile der Webseiten gleich. Anstatt diese jedes Mal neu einzutippen oder mit Copy-Paste zu übernehmen, kann eine Basis-Seite definiert werden, die das Standardverhalten vorgibt. Nennen wir diese Seite base.html.<html> <head> <title>Mein Titel</title> </head> <body> {% block header %} <h1>Eine einfache Überschrift</h1> {% endblock header %} {% block main %} Hier könnte Ihre Werbung stehen! {% endblock main %} </body> </html>Nun kann eine andere Webseite wie etwas index.html davon erben. Dazu muss das Verzeichnis mit angegeben werden, das unter templates steht, hier im Beispiel meineapp.
{% extends 'meineapp/base.html' %} {% block main %} <h2>Hier ist index.html</h2> {% for menus in data.items %} <h2>{{day}}</h2> <ul> {% for menu in menus %} {{ menu.name }} <br> {{ menu.art }} - {{ menu.location }} - {{ menu.price }} <p> {% endfor %} </ul> {% endfor %} {% endblock main %}Da der block header von index.html nicht überschrieben wird, wird "Eine einfache Überschrift" erscheinen so wie auch "Mein Titel" im Titel. Da aber block main überschrieben wurde, wird man den Werbungshinweis in index.html nicht sehen.
Tags im Templates
Für statische Webseiten benötigen wir kein Django. Das könnte jeder gängige Webserver auch allein. Interessant wird es, wenn Daten in den HTML-Code gezaubert werden können. Dazu kann dem render-Aufruf in der Funktion in views.py ein weiterer Parameter übergeben werden, der ein Dictionary der gewünschten Daten enthält.def index(request): gastlist = ["Sean Connery", "Roger Moore", "Timothy Dalton", "Pierce Brosnan"] gastcontext = { "liste": gastlist, "spruch": "Geschüttelt nicht gerührt" } return render(request, "gast/index.html", gastcontext)Über dieses Dictionary können beliebige Datenstrukturen eingebettet werden. Als Beispiel haben wir hier unter dem Namen spruch einen String und unter dem Namen liste die Liste von Gästen erstellt.
Diese Daten können innerhalb des HTML-Dokuments zugegriffen werden. Sehr einfach ist der Zugriff auf den String in spruch. Eine Variable kann zugegriffen werden, indem sie mit doppelt geschweiften Klammern umgeben wird.
<html> <body> <h1>Gästeliste</h1> {{spruch}} </ul> </body> </html>
Das for-Tag
Um auf die Liste zugreifen zu können, wird eine Schleife benötigt. Hier wird ein Python-for eingesetzt. Um es zu kennzeichnen, wird es mit geschweifter Klammer und Prozentzeichen umgeben. Innerhalb des HTML-Codes gibt es aber keine Einrückung. Darum muss das Ende der Schleife mit {% endfor %} gekennzeichnet werden.
<html> <body> <h1>Gästeliste</h1> {{spruch}} <ul> {% for gast in liste %} <li>{{gast}}</li> {% endfor %} </ul> </body> </html>Auf die Variable der for-Schleife kann innerhalb des HTML-Dokuments genauso zugegriffen werden, wie es zuvor schon mit der Variablen spruch funktionierte.
Über die Variable kann mittels eines Punktes auf Elemente zugegriffen werden. Beispiel:
{{ element.name }}
- Ist element ein Dictionary, wäre name der Key.
- Ist element eine Liste, kann name als Index verwendet.
- Ist element ein Objekt, wäre name ein Attribut.
Das if-Tag
Neben dem {% for ... %} gibt es auch ein {% if ... %}. Damit können Bedingungen abgefragt werden. Auch hier muss ein End-Tag {% endif %} definiert werden.{% if status == s %} <h4>Status!</h4> {% endif %}
Formulare
Der typische Weg, wie Anwender aus einer Webseite Daten an den Server senden kann, erfolgt über HTML-Formulare. Ein HTML-Formular wird durch das Tag <form ...> eingeleitet. Darin sind zwei Attribute wesentlich.
- action: Das Ziel, das die Formularinhalte verarbeitet
- method: Die Art der Sendung, GET oder POST.
- GET sendet die Fomularinhalte als Teil der URL (nicht verschlüsselbar).
- POST sendet die Formularinhalte im Body.
Formular ruft Django per GET
Für die Eingabe in ein Formular erstellen wir unter templates/gast eine neue Datei namens eingabe.html. Sie soll später einfach über die URL localhost:8000/gast/eingabe aufgerufen werden. Dazu müssen aber noch einige Arbeiten erfolgen.Mit dem folgenden Formular wird eine GET-Anfrage an insert gesendet. Diese Funktion soll später die Verarbeitung der eingegebenen Daten übernehmen.
<form action="insert" method="GET"> <label>Name: </label> <input type="text" name="name"><br> <label>E-Mail</label> <input type="text" name="mail"> <input type="submit" value="ok"> </form>In der Datei urls.py der App gast muss nun ein Eintrag für eingabe existieren, damit die URL localhost:8000/gast/eingabe funktioniert. Die action lautet auf insert. Und auch diese muss in der urls.py hinterlegt werden, damit die Funktion gefunden werden kann.
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('eingabe', views.eingabe), path('insert', views.insert), ]Und dann muss es auch eine Funktion eingabe und eine Funktion insert in der views.py der App geben. Die Funktion eingabe wird einfach die HTML-Datei eingabe.html aufrufen darstellen, so wie es bei index schon gemacht wurde. Die Funktion insert soll dann beim Klick auf den Submit-Button aktiv werden. Hier soll erst einmal nur ein Text auftauchen, damit wir wissen, dass sie erreicht wurde.
from django.http import HttpResponse from django.shortcuts import render # Create your views here. def index(request): return render(request, "gast/index.html") def eingabe(request): return render(request, "gast/eingabe.html") def insert(request): return HttpResponse("Insert")Nun kann das Eingabeformular über die URL localhost:8000/gast/eingabe aufgerufen werden. Sobald auf den Submit-Button geklickt wird, erscheint das Wort Insert im Browser.
Da die Eingabemaske die Methode GET verwendet, sieht man die Eingaben auch in der URL:
http://localhost:8000/gast/insert?name=Hugo&mail=hm%40m.de
Durch den Zugriff auf die Variable request und deren GET können die eingegebenen Werte auch in der Funktion insert ausgelesen werden.
def insert(request): name = request.GET.get('name', '') mail = request.GET.get('mail','') return HttpResponse("Name: " + name + " E-Mail: " + mail)Nun erscheinen die Eingaben auf dem Browser-Fenster.
Formular-POST und das CSRF-Cookie
Wird dasselbe Formular per POST versendet, wird Django melden, dass dies nicht geht, weil das CSRF-Cookie (Cross Site Request Forgery) nicht gesetzt sei.Der Hintergrund ist, dass der Browser von einem Tab zum anderen springen kann (Cross Site Request) und vielleicht auf diesem bereits angemeldet ist. Auf diese Weise könnte diese Anmeldung benutzt werden, ohne authentifiert zu sein. Um solche Angriffe zu verhindern, verfügt Django über einen CSRF-Mechanismus per Cookie. Dieser kann mit der Tag {% csrf_token %} im FORM eingetragen werden.
<form method=POST ... {% csrf_token %} <input .... </form>Auf der Empfangsseite muss natürlich auf POST statt auf GET zugegriffen werden.
def insert(request): name = request.POST.get('name', '') mail = request.POST.get('mail','') return HttpResponse("Name: " + name + " E-Mail: " + mail)Das Ergebnis ist das gleiche wie bei GET. Allerdings werden die Daten nun im BODY übertragen und sind dann bei HTTPS verschlüsselt. Außerdem ist die Größe nicht wie bei GET beschränkt.
Umgang mit Datenbanken
Die Daten einer Web-Anwendung müssen in der Regel gespeichert werden. Datenbanken werden im Projekt in der Datei settings.py definiert. Hier wird definiert, welche Datenbank zum Einsatz kommt, ob beispielsweise PostreSQL oder auch MySQL. Der einfachste Weg führt zu SQLite, das von Python direkt zugegriffen werden kann. Aus dieseem Grund ist hier SQLite auch bereits eingetragen.# settings.py im Projektordner # ... DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }Hinter ENGINE wird der Datenbanktreiber aufgeführt. Für die anderen Datenbanken müsste hier stehen:
- MySQL: django.db.backends.mysql
- PostgreSQL: django.db.backends.postgresql_psycopg2
Definition eines Modells
Im Gegensatz zu vielen anderen Systemen setzt Django nicht auf die Einbettung von SQL-Befehlen, sondern verwendet ORM (Object Relation Mapping), also die Modellierung der Datenbanktabellen durch Objekte im objektorientierten Sinn.Die Klassen dieser Objekte werden in der Datei models.py der App definiert. Für einen Gast enthält die Klasse an Attributen:
- ein Name,
- eine Adresse,
- eine Telefonnummer und
- eine E-Mail-Adresse.
Die Klasse wird zum Modell, indem Sie die Basisklasse models.Model erweitert.
# models.py der App from django.db import models class Gast(models.Model): name = models.CharField(max_length=100) adresse = models.CharField(max_length=100) telefon = models.CharField(max_length=50) email = models.EmailField() def __str__(self) -> str: return f"{self.name}, Adresse {self.adresse}, Tel {self.telefon}, E-Mail {self.email}"Datenbanken benötigen im Gegensatz zu Python klare Ansagen bezüglich der Typen ihrer Spalten. Darum müssen diese in der Klasse erwähnt werden. Beispiele für gültige Typen:
- models.CharField(max_length=200)
- models.EmailField()
- models.DateTimeField('date published')
- models.IntegerField(default=0)
Datenbankerzeugung: Migration
Um aus dieser Definition ein nutzbares Modell zu erzeugen, wird eine Migration erzeugt.python manage.py makemigrations gastAls Antwort auf den Befehl erscheint:
Migrations for 'gast': gast/migrations/0001_initial.py - Create model GastEs entsteht auf der oberen Projektebene die Datei db.sqlite3, sofern Sie SQLite verwenden.
Bevor man mit der Datenbank arbeiten kann, muss sie explizit migriert werden. Das ist auch erforderlich, wenn sich am Modell irgendetwas ändert.
python manage.py migrate
Erzeugen von Objekten in der Datenbank
Das Datenbankmodell kann über eine Shell bedient werden. Dazu muss das Modell der App importiert werden. Im folgenden Beispiel wird ein Objekt des Modells unter dem Namen gast erzeugt und kann mit der Methode save in der Datenbank gespeichert werden.Ohne dass dies explizit erwähnt wird, wird automatisch eine Spalte id vom Typ integer angelegt, die einen primären Schlüssel darstellt.
python manage.py shell >>> from gast.models import Gast >>> gast = Gast(name="Anton Meier", adresse="Quarkmuehle", telefon="089-112", email="anton@m.de") >>> gast.save() >>> gast <Gast: Anton Meier, Adresse Quarkmuehle, Tel 089-112, E-Mail anton@m.de> >>>Durch Erzeugung eines neuen Gastobjekts und anschließendem Aufruf der Methode save können nun beliebig viele Gäste in die Datenbank eingetragen werden.
Anfragen
Über den Modellnamen Gast und objects kann auf die Elemente der Datenbank zugegrifen werden. So liefert all() alle Einträge der Datenbanktabelle.>>> Gast.objects.all()Das zeigt alle Einträge in der Datenbank an. Werden rechteckige Klammern angehängt, kann auf die Elemente wie bei einer Liste zugegriffen werden.
>>> Gast.objects.all()[0]Mit der Methode get können Elemente anhand der Daten gesucht werden.
>>> Gast.objects.get(id=1) >>> Gast.objects.get(adresse="Hamburg")
Filter
Analog zu der WHERE-Klausel in SQL können bei Anfragen in Django Filter eingesetzt werden. Diese werden an objects angehängt.
>>> Gast.objects.filter(adresse__contains="amb") <QuerySet [<Gast: Anke Evers, Adresse Hamburg, Tel 220, E-Mail a@e.de>]>
Als Parametervariable wird die Klassenvariable, gefolgt von zwei Unterstrichen und dem Filter-Lookup gesetzt. Das Filter-Lookup ist hier contains (enthält): Es kann startswith (beginnt mit), gt (größer), lt (kleiner), in (in Liste enthalten) oder week (Kalenderwoche) lauten.
Die folgende URL verweist auf eine ausführlichere Beschreibung der Lookups:
Änderungen
Änderungen an einem Objekt können durch save() zu einer Änderung in der Datenbank führen, sofern die id nicht geändert wird.>>> g = Gast.objects.get(adresse="Hamburg") >>> g.telefon="220" >>> g.save()
Löschen
Durch Anhängen der Methode delete() an eine Anfrage können alle gefundenen Einträge aus der Datenbank gelöscht werden.Views für die Datenverwaltung
Für die Verwaltung von Daten werden unterschiedliche Bildschirme für das Erfassen oder die Darstellung benötigt. Diese werden typischerweise durch Funktionen in der Datei views.py in der App realisiert.
index: Liste der Objekte
Die bisher schon verwendete Funktion index wird meist für eine Übersicht verwendet, also einer Liste der Objekte.Nach wie vor steht in der Datei models.py das Model für einen Gast.
from django.db import models class Gast(models.Model): name = models.CharField(max_length=100) adresse = models.CharField(max_length=100) telefon = models.CharField(max_length=50) email = models.EmailField() def __str__(self) -> str: return f"{self.name}, Adresse {self.adresse}, Tel {self.telefon}, E-Mail {self.email}"In der Datei views.py ist die Funktion index definiert. Sie holt alle Einträge aus der Datenbank, schiebt sie dem context unter und lässt die Datei index.html anzeigen.
from django.shortcuts import render from . import models def index(request): gastlist = models.Gast.objects.all() gastcontext = { "liste": gastlist, "spruch": "Geschüttelt nicht gerührt" } return render(request, "gast/index.html", gastcontext)Prinzipiell hat sich gegebenüber der bisherigen Funktion index nichts geändert, nur dass die Liste nun nicht in der Funktion statisch angelegt wird, sondern aus der Datenbanktabelle geholt wird.
Die Datei index.html befindet sich immer noch im App-Verzeichnis templates/gast und benötigt keine Änderungen. Sie wertet die übergebene Variable gaeste in einer for-Schleife aus, die daraus die li-Elemente einer ul erzeugt.
<html> <body> <h1>Gästeliste</h1> {{spruch}} <ul> {% for gast in gaeste %} <li> {{gast}} </li> {% endfor %} </ul> </body> </html>
Detailanzeige eines einzelnen Objekts
Um von der Übersicht auf die Anzeige eines einzelnen Gastes zu kommen, wird jeder li-Eintrag um einen Link erweitert. Er soll die id des jeweils angezeigten Gastes an die Funktion übergeben, die einen einzelnen Gast anzeigt.<ul> {% for gast in gaeste %} <li> {{gast}} <a href="{% url 'gast:detail' gast.id %}">Details</a></li> {% endfor %} </ul>
Die URL gast:detail in der index.html zeigt auf eine URL, die in der Datei urls.py aufgeführt werden muss. Hier muss auch erwähnt werden, dass die Funktion die id als Parameter benötigt. Diese wurde hinter der URL als gast.id angegeben und ist eine ganze Zahl, also ein int.
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('detail/<int:id>', views.detail, name='detail'), ]Um die Details eines einzelnen Gastes anzuzeigen, wird in der views.py eine zusätzliche Funktion detail definiert. Diese erhält als weiteren Parameter die id des zu betrachtenden Objekts.
from django.shortcuts import render from . import models def index(request): gaeste = models.Gast.objects.all() context = { 'gaeste': gaeste } return render(request, "gast/index.html", context) def detail(request, id): gast = models.Gast.objects.get(id=id) context = { 'gast': gast } return render(request, "gast/detail.html", context)Nun muss noch die Template-Seite als detail.html in den Ordner templates/gast erstellt werden. Auf dieser Seite werden die einzelnen Elemente eines Gastes angezeigt. wird ein Link zu der Gästeliste gesetzt.
<html> <body> <h1>Gast</h1> {{gast.name}}<br> {{gast.adresse}}<br> {{gast.telefon}}<br> {{gast.email}}<p> <p> <a href="{% url 'gast:index' %}">Liste</a> </p> </body> </html>Es ist auch möglich, nur die Variable gast anzugeben. Dann würde der Text ausgegeben, den die Gast-Methode __str__ zurückgibt. <h1>Gast</h1> {{gast}} <a href="{% url 'gast:index' %}">Liste</a>
Anlegen eines Gastes
In der Datei index.html kann ein Link erzeugt werden, der auf eine Maske für die Eingabe eines neuen Gastes verweist. Diese Maske wird dann durch die Funktion eingabe erstellt, die wir oben schon einmal verwendet haben.<a href="{% url 'gast:eingabe' %}">Neuer Gast</a>
Dazu muss eingabe in der Datei urls.py der App eingetragen werden, wo sich die Funktion für das Anlegen eines Gastes befindet. Da die Funktion über einen Link mit gast:eingabe aufgerufen wird, muss der dritte Parameter name gesetzt sein.
from django.urls import path from . import views app_name="gast" urlpatterns = [ path('', views.index, name='index'), path('detail/<int:id>', views.detail, name='detail'), path('eingabe/', views.eingabe, name='eingabe'), ]Nun kann in der Datei views.py wie gehabt eine Funktion für den Aufruf des Templates erstellt werden, die eingabe heißen soll. Hier ändert sich noch nichts gegenüber der vorigen Funktion eingabe.
def eingabe(request): return render(request, "gast/eingabe.html")Das Template eingabe.html enthält ein Formular, dessen FORM-Tag eine ACTION besitzt, die wiederum auf die Funktion insert verweist, die den Inhalt des Formulars in die Datenbank schafft.
<form action="{% url 'gast:insert' %}" method="POST"> {% csrf_token %} <label>Name: </label> <input type="text" name="name"><br> <label>Adr:</label> <input type="text" name="adr"><br> <label>Tel.:</label> <input type="text" name="tel" id="tel"><br> <label>Mail:</label> <input type="text" name="mail" id="mail"><br> <input type="submit" value="ok"> </form>Auch die Funktion insert muss in der Datei urls.py erwähnt werden.
from django.urls import path from . import views app_name="gast" urlpatterns = [ path('', views.index, name='index'), path('detail/<int:id>', views.detail, name='detail'), path('eingabe/', views.eingabe, name='eingabe'), path('insert/', views.insert), ]Sie trifft dann auf die Funktion insert in der Datei views.py. Sie liest wie gehabt die POST-Informationen aus, schafft sie dieses Mal aber in die Datenbank, indem ein gast-Objekt angelegt wird und darüber save aufgerufen wird.
from django.shortcuts import render, redirect, reverse from . import models # ... def insert(request): name = request.POST.get("name") adr = request.POST.get("adr") tel = request.POST.get("tel") mail = request.POST.get("mail") gast = models.Gast(name=name, adresse=adr, telefon=tel, email=mail) gast.save() return redirect(reverse("gast:detail", args=[gast.id]))Nach dem Einfügen wird der Detail-Bildschirm wieder aufgerufen, um den Neuantrag zu zeigen. Dort befindet sich der Link zur Liste in index.html.
Pflichtfelder und Eingabevalidierung
Die Eingaben des Benutzers müssen auf Konsistenz geprüft werden. Pflichtfelder können bereits in HTML5 mit der Option required bzw. required=true bei jedem input-Element festgelegt werden. Versucht der Anwender einen Submit, ohne das Feld auszufüllen, wird er auf sein Versäumnis hingewiesen.<input type="text" name="adr" id="adr" required>Anstatt den Browser prüfen zu lassen, kann auch das Backend die Validität prüfen. Zunächst stellen wir fest, ob alle Felder eingegeben wurden. Dann können wir das required aus dem Formular entfernen.
def insert(request): name = request.POST.get("name") adr = request.POST.get("adr") tel = request.POST.get("tel") mail = request.POST.get("mail") if name and adr and tel and mail: # Alle Felder sind gefüllt b = models.Gast(name=name, adresse=adr, telefon=tel) b.save() return redirect(reverse("gast:detail", args=[gast.id])) else: # Da fehlt ein Feld. Zurück in die Eingabemaske return render(request, "gast/eingabe.html",{"error":"Alle Eingabefelder füllen"})Die Fehlervariable error kann direkt in der Eingabemaske dargestellt werden. Sofern das Feld error besetzt ist, wird es angezeigt.
<html> <body> <h1>Gast erfassen</h1> <form action="{% url 'gast:insert' %}" method="post"> {% csrf_token %} {% if error %} <p>Fehler: {{error}}</p> {% endif %} Name: <input type="text" name="name" id="name"><br> Adr.: <input type="text" name="adr" id="adr"><br> Tel.: <input type="text" name="tel" id="tel"><br> Mail: <input type="text" name="mail" id="mail"><br> <input type="submit" value="OK"> </form> </body> </html>Nun können auch weitere Prüfungen in der Funktion in views.py erfolgen und diese angezeigt werden.
def insert(request): name = request.POST.get("name") adr = request.POST.get("adr") tel = request.POST.get("tel") mail = request.POST.get("mail") if name and adr and tel and mail: # Alle Felder sind gefüllt b = models.Gast(name=name, adresse=adr, telefon=tel) b.save() return redirect(reverse("gast:detail", args=[gast.id])) elif not "@" in mail: return render(request, "gast/eingabe.html",{"error":"Mail-Adresse nicht korrekt"}) elif len(tel) < 5: return render(request, "gast/eingabe.html",{"error":"Telefonnummer zu kurz"}) else: # Da fehlt ein Feld. Zurück in die Eingabemaske return render(request, "gast/eingabe.html",{"error":"Alle Eingabefelder füllen"))Um die bisher eingegebenen Werte auch bei einer Fehlermeldung zu erhalten, müssen diese per context wieder zurück an die Maske übertragen werden. Im Fehlerfall wird dann das context-Dictionary um das Feld error erweitert.
def insert(request): name = request.POST.get("name") adr = request.POST.get("adr") tel = request.POST.get("tel") mail = request.POST.get("mail") context = {'name': name, 'adr': adr, 'tel':tel, 'mail':mail} if name and adr and tel and mail: # Alle Felder sind gefüllt b = models.Gast(name=name, adresse=adr, telefon=tel) b.save() return redirect(reverse("gast:detail", args=[gast.id])) elif not "@" in mail: context.update({'error':"Mail-Adresse nicht korrekt"}) return render(request, "gast/eingabe.html",context) elif len(tel) < 5: context.update({"error":"Telefonnummer zu kurz"}) return render(request, "gast/eingabe.html", context) else: # Da fehlt ein Feld. Zurück in die Eingabemaske context.update({"error":"Alle Eingabefelder füllen"}) return render(request, "gast/eingabe.html", context)Nun sind die bisherigen Eingaben im context. Damit diese in der Maske erscheinen, muss den input-Feldern die Tag value hinzugefügt werden.
<html> <body> <h1>Gast erfassen</h1> <form action="{% url 'gast:insert' %}" method="post"> {% csrf_token %} {% if error %} <p>Fehler: {{error}}</p> {% endif %} Name: <input type="text" name="name" id="name" value={{name}}><br> Adr.: <input type="text" name="adr" id="adr" value={{adr}}><br> Tel.: <input type="text" name="tel" id="tel" value={{tel}}><br> Mail: <input type="text" name="mail" id="mail" value={{mail}}><br> <input type="submit" value="OK"> </form> </body> </html>
Mehrere Tabellen/Objekte
In den meisten Fällen wird es nicht bei einer Tabelle bleiben. Ein Gast ist aus Sicht eines Vermieters nur interessant, wenn er eine Wohnung bucht. Wir schaffen darum eine weitere Tabelle Buchung. Jede Buchung gehört zu genau einem Gast. Ein Gast darf aber gern mehrfach buchen. Man spricht von einer 1:n-Beziehung.In der Welt der Datenbanktabellen erreicht man solch eine Beziehung, indem die Buchung einen Eintrag für den eindeutigen Schlüssel des Gastes enthält, der bucht. Man spricht von einem Fremdschlüssel oder foreign key.
class Buchung(models.Model): gast = models.ForeignKey(Gast, on_delete=models.DO_NOTHING) wohnung = models.CharField(max_length=30) von = models.DateField() bis = models.DateField() preis = models.DecimalField(decimal_places=8, max_digits=11) def __str__(self) -> str: return f"{self.von} bis {self.bis} Gast {self.gast.name} Wohnung {self.wohnung.bez}"Die Wohnung wird als reiner Text in der Buchung eingetragen. Für eine einfache Anwendung mag das reichen, aber wenn der Vermieter mehrere Wohnungen hat oder vermittelt, dann sollte für die Wohnung eine eigene Tabelle angelegt werden. Diese kann dann auch die Anschrift und andere Details enthalten.
class Wohnung(models.Model): bez = models.CharField(max_length=30) adr = models.CharField(max_length=70) preis = models.DecimalField(decimal_places=8, max_digits=11) preis_hs = models.DecimalField(decimal_places=8, max_digits=11) def __str__(self) -> str: return self.bezNeu sind hier die Zahlenwerte. Da es sich um Preise handelt, ist es sinnvoll, diese nicht binär, sondern als dezimale Werte zu verwenden.
Gast und Wohnung werden durch eine Buchung verbunden. Ein Gast kann nicht nur eine Wohnungen buchen. Er kann gern jedes Jahr wiederkommen und wieder eine Wohnung buchen. Und auch eine Wohnung ist nicht nur für einen Gast da, sondern wird im Laufe der Zeit von mehreren Gästen gebucht werden. Man spricht von einer m:n-Beziehung.
class Buchung(models.Model): gast = models.ForeignKey(Gast, on_delete=models.DO_NOTHING) wohnung = models.ForeignKey(Wohnung, on_delete=models.CASCADE) von = models.DateField() bis = models.DateField() preis = models.DecimalField(decimal_places=8, max_digits=11) def __str__(self) -> str: return f"{self.von} bis {self.bis} Gast {self.gast.name} Wohnung {self.wohnung.bez}"Datenbanktechnisch handelt es sich bei Buchung um eine Relation, da sie die Beziehung zwischen den Entities Gast und Wohnung beschreibt. Aber auch eine Relationstabelle darf neben den beiden Fremdschlüsseln weitere Spalten besitzen.
Die Forms von Django
Wenn ein Model für die Datenstruktur erstellt wurde, kann dieses für die Erstellung eines Forms verwendet werden und vereinfacht damit die Erstellung der HTML-Forms. Die Datei models.py mit der Klasse Gast wird hier vorausgesetzt.Es wird eine neue Datei forms.py in der App angelegt. Diese bindet die Django-Klasse ModelForm und das lokal definierte Modell ein.
from django.forms import ModelForm from .models import Gast class GastForm(ModelForm): class Meta: model = Gast fields = ('name', 'adresse','telefon','email')Nun wird die Funktion eingabe in views.py auf die Forms angepasst. Dazu muss die Form aus forms.py importiert werden. In der Funktion eingabe wird ein form aus der GastForm erstellt und diese als dritter Parameter an render übergeben.
from .forms import GastForm # ... def eingabe(request): form = GastForm() return render(request, "gast/eingabe.html", {'form': form})Nun kann die HTML-Datei entrümpelt werden. Im templates/gast-Verzeichnis sieht die eingabe.html nun so aus.
<html> <body> <h1>Gast erfassen</h1> <form method="post" action="{% url 'gast:insert' %}" > {% csrf_token %} {{ form }} <button type="submit" class="save btn btn-default">Erfassen</button> </form> </body> </html>Hier erscheinen alle Eingabefelder in einer Zeile. Wenn statt {{ form }} die Variante {{ form.p_as }} verwendet wird, erscheint jedes Eingabeelement in einer eigenen Zeile, also untereinander.
Das Eingabeformular von Wohnung kann ähnlich realisiert werden, da die Tabelle ebenfalls keine Fremdschlüssel enthält.
Form mit Fremdschlüssel
Die Form basiert immer auf einem Model, also sehen wir uns noch einmal das Model in der Datei models.py an:class Buchung(models.Model): gast = models.ForeignKey(Gast, on_delete=models.DO_NOTHING) wohnung = models.ForeignKey(Wohnung, on_delete=models.CASCADE) von = models.DateField() bis = models.DateField() preis = models.DecimalField(decimal_places=8, max_digits=11) def __str__(self) -> str: return f"{self.von} bis {self.bis} Gast {self.gast.name} Wohnung {self.wohnung.bez}"Darauf wird eine Form namens BuchForm in der Datei forms.py definiert.
from django.forms import ModelForm, DateInput # ... class BuchForm(ModelForm): class Meta: model = Buchung fields = ('von', 'bis', 'preis', 'gast', 'wohnung') widgets = { 'von': DateInput(attrs={'class':'form-control', 'type':'date'}), 'bis': DateInput(attrs={'class':'form-control', 'type':'date'}), }Für die Fremdschlüssel wird anscheinend also ein komplettes Objekt und nicht nur die ID herangezogen.
Für die Datumsfelder sind die widgets besonders interessant. Ohne diese würde Django für die Datumsfelder ganz normale Texteingaben erstellen. Der Benutzer müsste dort ein zulässiges Datumsformat eingeben, ohne darauf hingewiesen zu werden, wie dieses aussieht.
Das DateInput-Element sorgt dafür, dass das Datumsformat angezeigt und beim Klicken eines Feldes ein Auswahldialog aufgeht, das die Auswahl des Tages aus einem Kalender erlaubt.
Um eine Buchung zu erfassen, wird eine Funktion zum Aufruf der Formular-Webseite (buchform) benötigt und eine weitere Funktion für die spätere Auswertung (buchinsert). Beides sollte gleich in der urls.py vorgemerkt werden.
# urls.py der App urlpatterns = [ # ... path('buchform/', views.buchform, name='buchform'), path('buchinsert/', views.buchinsert, name='buchinsert'), ]Zunächst wird buchform aufgerufen, um das Buchungsformular anzuzeigen.
def buchform(request): gast = models.Gast.objects.all() wohnung = models.Wohnung.objects.all() form = BuchForm() context = { 'gast': gast, 'wohnung': wohnung, 'form': form } return render(request, "gast/buchform.html", context)Hier werden also alle Gast- und Wohnungselemente übergeben. Das HTML-Template buchform.html zeigt einfach nur die Form.
<html> <body> <h1>Buchen</h1> <form method="post" action="{% url 'gast:buchinsert' %}" > {% csrf_token %} {{ form }} <button type="submit" class="save btn btn-default">Erfassen</button> </form> </body> </html>Sowohl für gast als auch für wohnung wird in dem Formular eine Klappbox angezeigt, in der ein Element ausgewählt werden kann. Solange man übersichtlich viele Wohnungen und nur wenige Stammgäste hat, ist diese Vorgehensweise durchaus praktikabel.
Die action der form leitet weiter auf die Funktion buchinsert. Sobald der Anwender also das Formular ausgefüllt hat, geht es dort weiter. Diese Funktion liest aus der POST-Liste der Request die einzelnen Felder aus. Dabei wird deutlich, dass für Gast und Wohnung nicht das komplette Objekt, sondern nur die id übertragen wird.
In der Annahme, dass der Anwender beim Ausfüllen des Formulars schön artig war, können aus den ids die Objekte gewonnen werden und mit den anderen Eingaben über den Konstruktor zur Erzeugung einer Buchung genutzt werden. Durch den anschließenden Aufruf der Methode save() wird alles in die Datenbank geschrieben.
def buchinsert(request): von = request.POST.get("von") bis = request.POST.get("bis") preis = request.POST.get("preis") gid = request.POST.get("gast") gast = models.Gast.objects.get(id=gid) wid = request.POST.get("wohnung") wohnung = models.Wohnung.objects.get(id=wid) buchung = models.Buchung(von=von, bis=bis, preis=preis, gast=gast, wohnung=wohnung) buchung.save() return redirect(reverse("gast:detail", args=[gast.id]))
Django Admin
Das Admin-Konto von Django ist von vornherein eingerichtet. Es existiert bereits ein urlpattern dafür.from django.contrib import admin from django.urls import path, include urlpatterns = [ path('buchform/', views.buchform, name='buchform'), ... path('admin/', admin.site.urls), ]Um diesen Administrator anzulegen muss createsuperuser gestartet werden.
python manage.py createsuperuser
- Der Name des Aministrationskonto muss angegeben werden. Dazu wird der Username des Entwicklers vorgeschlagen. Hier ist admin aber passender.
- Es wird eine E-Mail-Adresse eingefordert.
- Es muss zwei Mal ein Passwort mit mindestens acht Zeichen eingegeben werden. Die Sicherheitskriterien können aber übergangen werden.
Benutzer und Gruppen verwalten
Im Administrationsdialog lassen sich Benutzer und Gruppen eintragen.Durch den Klick auf +Add hinter Users kann ein Benutzer angelegt werden. Es wird ein Benutzername und ein Passwort benötigt. Dabei prüft Django sehr genau, dass das Passwort dem Benutzer nicht zu ähnlich ist und auch mindestens acht Buchstaben groß ist.
In einem weiteren Dialog können weitere Informationen für den Benutzer definiert werden, insbesondere seine Berechtigung, bestimmte Modelle zu bearbeiten.
Verwaltung der Modelle per Admindialog
Um die Möglichkeiten des Admin-Zugangs zu erweitern, wird die Datei admin.py in der App erweitert. Dort können die eigenen Modelle eingetragen werden. Dazu wird aus der lokalen models.py das Modell Gast eingetragen.# admin.py from django.contrib import admin from .models import Gast # Register your models here. admin.site.register(Gast)Nun kann im Adminbereich auch die App GAST mit ihrem Modell Gast bearbeitet werden. Diese erscheint unterhalb der Benutzerverwaltung. Klickt man auf das Modell, erscheint rechts eine Liste der bisherigen Gäste, die hier sehr einfach bearbeitet, ergänzt oder gelöscht werden können. Die Elemente der Liste wird durch die __str__-Methode dargestellt.
Abbildung: Administrationsbildschirm mit Modell
Die Darstellung der Admin-Modelle kann durch Vererbung erweitert werden. In der folgenden admin.py wird die Buchung durch eine BuchAdmin ersetzt. Die Klasse erweitert admin.ModelAdmin und trägt in fields die Feldnamen und in list_display diejenigen Elemente von fields, die in der Listendarstellung auftreten sollen. Dabei wird der Fremdschlüssel gast wieder durch die __str__-Methode, diesmal von Gast dargestellt.
from django.contrib import admin from .models import Gast, Buchung, Wohnung # Register your models here. admin.site.register(Gast) admin.site.register(Wohnung) class BuchAdmin(admin.ModelAdmin): fields = ('gast', 'wohnung', 'von', 'bis') list_display = ('gast', 'wohnung', 'von', 'bis') #admin.site.register(Buchung) admin.site.register(Buchung, BuchAdmin)Klickt man in der Liste von BuchAdmin auf ein Element, wird dieses in einem Bearbeitungsmodus dargestellt. Das Element gast ist dann eine Klappbox, über die ein anderes Element von Gast ausgewählt werden kann.
Ein weiteres Element von BuchAdmin kann readonly_fields sein. Dieses sollte alle Felder enthalten, die nicht änderbar sein sollen, sondern nur lesbar. Das betrifft beispielsweise Datumsfelder, die automatisch gesetzt werden.
Mit dem Element list_filter kann die Liste durch Setzen eines Filters eingegrenzt werden, mit dem Element search_fields kann in den angeführten Spalten gesucht werden.
Für alle diese Elemente gilt, dass sie ein Tupel oder eine Liste darstellen müssen. Ist nur ein Element vorhanden, muss ein Komma dahinter gesetzt werden.
Authentifizierung
Wenn jemand einen Gast eintragen will, sollte er dazu auch berechtigt sein. Dazu kann ein Login eingefordert werden. Einloggen kann sich natürlich nur jemand, der zuvor als Benutzer angemeldet wurde. Das kann beispielsweise über die Django-Administration erfolgen.
Berechtigungen
Für die verschiedenen Modelle mus die Zugriffsberechtigung geklärt werden. Django ist da großzügig. Jeder darf alles. Unterteilt ist das in add, change, delete und view. Dieser Fähigkeit wird der App-Name mit einem Punkt vorangestellt und hinter einem Unterstrich das Modell angehängt, also gast.change_buchung, um die Buchung in der App ändern zu dürfen.In einer Anwendung würde zunächst der Benutzer angefordert und dann mit has_perm geprüft, ob die Berechtigung vorliegt.
from django.contrib.auth.models import User, Permission user = User.objects.get(username = "wirt") if user.has_perm(gast.add_wohnung'):In der Anwendung kann mit authenticate geprüft werden, ob ein Benutzer registriert ist.
from django.contrib.auth import authenticate user = authenticate(username='wirt', password='geheim') if user is not None: user.set_password('ganzgeheim') user.save()
User einer Request abfragen
def tudochwas(request): if request.user.is_authenticated: return HttpResponse("Ach, Du bist's, "+user.username) else: return HttpResponse("Nur Mitglieder!")
from django.contrib.auth.decorators import login_required @login_required def eingabe(request):Ein Aufrufer wird nun auf die Seite accounts/login umgeleitet. Diese muss erst angelegt werden. Darum muss in der urls.py des Projekts (nicht der App) ein Eintrag dafür angelegt werden.
# ... from django.contrib.auth import views urlpatterns = [ path('admin/', admin.site.urls), path('gast/', include("gast.urls")), path('accounts/login/', views.LoginView.as_view(), name='login'), ]Das Template für die Login-Seite befindet sich in einer Datei login.html, die unter templates in einem eingabe anzulegendem Verzeichnis registration abgelegt wird.
{% if form.errors %} <p>Benutzer und Passwort passen nicht.</p> {% endif %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <table> <tr> <td>{{ form.username.label_tag }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.password.label_tag }}</td> <td>{{ form.password }}</td> </tr> </table> <input type="submit" value="Anmelden" /> <input type="hidden" name="next" value="{{ next }}" /> </form>
Zugriff auf APIs von REST-Datenquellen
Für einen Zugriff auf Web-Interface-APIs gibt es die Python-Lib requests. Diese muss explizit installiert werden.pip install requestsNun kann über requests ein Zugriff auf eine URL ausgeführt werden.
from django.http import HttpResponse import requests def index(request): r = requests.get('https://official-joke-api.appspot.com/random_joke') if r.status_code == 200: js = r.json() frage = js.get("setup") antwort = js.get("punchline") text = "<h2>"+frage+"</h2><h3>"+antwort+"</h3>" response = HttpResponse(text) else: response = HttpResponse(f"Fehler mit Status-Code {response.status_code}") return responseDas Beispiel zeigt, wie eine URL per GET geladen wird.
JSON-Struktur
Bei dem Ergebnis handelt es sich um eine JSON-Struktur. Mit dem Befehl curl kann man diese sehen (der besseren Lesbarkeit umbrochen):$ curl https://official-joke-api.appspot.com/random_joke {"id":187,"type":"general","setup":"What did the late tomato say to the early tomato?","punchline":"I’ll ketch up"} $Hier kann man sehen, dass Fragen hinter der Variablen setup und Antworten hinter punchline stehen.