.. _create_events: .. index:: single: Model-Api single: ORM single: shell single: Zeitzone single: timezone single: Manager single: migrate single: UTC single: sortieren single: order_by die Model-Api ************************ Wir wollen nun Kategorie- und Event-Objekte in die Datenbank eintragen. Um das nicht direkt in der Datenbank zu machen, was definitiv auch der falsche Weg wäre, machen wir das hier nun testweise in der Django-Shell. Später werden diese Events freilich von Usern über Web-Formulare erstellt bzw. von uns als Testdatensatz mit einem Faker generiert. Wir öffnen nun also die **Django Shell** mit folgendem Befehl: .. code-block:: bash python manage.py shell .. admonition:: die Django Shell Der Befehl ``python manage.py shell`` öffnet den interaktiven Python-Interpreter. Zusätzlich zu dem, was der Python Interpreter per default bietet, lädt die Django-Shell auch noch die **projektspezifischen Settings**, damit wir mit dem Projekt interaktiv agieren können. Die Django-Shell eignet sich hervorrangend zum Testen kleiner Code-Schnipsel oder Abfragen. Für wiederkehrende administrative Aufgaben, wie zum Beispiel dem Erstellen von Reports oder Datenexporten, sollte der Weg über ein sogenanntens ``Management-Command`` genommen werden (siehe Kapitel **Testdaten generieren**, dort erstellen wir ein Management-Command zum Generieren von Testdaten). Objekte erstellen ================== wir wollen nun erstmal eine Kategorie anlegen. Dazu importieren wir aus ``events`` das Kategorie- und das Event-Model. Um ein Kategorie-Objekt zu erstellen, rufen wir den Konstuktor der Klasse ``Category`` mit den entsprechenden Keyword-Argumenten ``name`` und ``description`` auf. Damit existiert ein Objekt der Klasse ``Category``, es ist allerdings noch nicht persistent in der Datenbank gespeichert. Um das Objekt dauerhaft zu speichern, gibt es die Methode ``save``. **Hier ein Ausschnitt einer interaktiven Session in der Shell:** .. code-block:: python >>> from events.models import Category, Event >>> # ein Objekt der Klasse Category anlegen >>> sport = Category(name='Sport', description='fit and fun') >>> type(sport) >>> # Auf Attribut name zugreifen >>> sport.name Sport >>> # Das Objekt hat noch keine ID, denn die wird von der DB vergeben. >>> sport.id >>> >>> # Um das Objekt persistent zu speichern, führen wir save() aus >>> sport.save() >>> >>> # id und die beiden Zeitangaben stammen aus der Datenbank. >>> D.h. das Objekt wurde tatsächlich in die Datenbank eingetragen. >>> sport.id 9 >>> sport.created_at datetime.datetime(2022, 1, 6, 11, 20, 14, 931188, tzinfo=) >>> sport.updated_at datetime.datetime(2022, 1, 6, 11, 20, 14, 931188, tzinfo=) Der Befehl ``sport.__dict__`` ist eine elegante Möglichkeit in Python, alle Attribute eines Objekts in Form eines Dictionaries auszugeben. Wer will, probiert es gleich mal aus! Wir können jetzt mit dem sqlite-Browser bzw. der VSCode-Extension in der Tabelle ``events.category`` prüfen, ob eine Kategorie namens Sport eingetragen wurde. Das sollte der Fall sein. .. image:: /images/sqlite_browser_category_1.png Wir haben also ein Kategorie-Objekt mit den nötigen Keyword-Argumenten erstellt und dann mit ``save()`` persistent in unserer Entwicklungs-Datenbank gespeichert. Dazu waren zwei Schritte nötig. Wir können ein Objekt auch in nur einem Schritt erstellen und zwar über den ``Manager`` der Kategorie-Klasse (mehr zu Managern später). .. code-block:: python >>> books = Category.objects.create(name="Bücherwurm") >>> books.id 11 >>> books.name Bücherwurm >>> # das Objekt verändern und speichern >>> books.description = "Alles was mit Büchern zu tun hat" >>> books.save() >>> books.description Alles was mit Büchern zu tun hat **Hinweis für Windows-Nutzer:** Es kann sein, dass die Powershell mit dem falschen Zeichen-Encoding konfiguriert ist, und keine Umlaute darstellen kann. Dann entweder auf ``utf-8`` umstellen oder die ``Git-Bash`` nehmen, falls zur Hand. Im Notfall kann man sich auch erstmal mit "ue" behelfen. Die Objekte, die wir hier anlegen, sind alles nur Testobjekte, die später wieder rausgelöscht werden. Event-Objekt erstellen ======================= Wir wollen nun ein Event-Objekt erstellen. Um eine ``1:n Beziehung`` auf die Tabelle ``Category`` zu realisieren, setzen wir das Attribut ``category`` auf das oben erstellte Kategorie-Objekt ``books``. Um ein Datum für das Event festzulegen, importieren wir ``timezone`` aus den Django Utils und ``timedelta`` aus dem datetime-Modul. Unser Events werden jetzt pauschal zwei Tage in der Zukunft stattfinden. Später kann man über ein Formular natürlich einen gewünschten Zeitpunkt eingeben. **Hier ein Ausschnitt einer interaktiven Session in der Shell:** .. code-block:: python >>> from django.utils import timezone >>> from datetime import timedelta >>> >>> d = timezone.now() + timedelta(days=2) >>> d datetime.datetime(2022, 1, 8, 12, 50, 11, 205027, tzinfo=) >>> >>> e1 = Event.objects.create(name="Hitchhikers Club", date=d, category=books) >>> # Wenn wir das Objekt e1 anzeigen lassen, bekommen wir die # String-Repräsentation des Objektes, wie im Model festgelegt >>> e1 >>> >>> e1.date datetime.datetime(2022, 1, 8, 12, 50, 11, 205027, tzinfo=) >>> type(e1) Bisher hatten wir uns nur einfache Felder wie String oder Datum des Eventobjekts ausgeben lassen. Was ist aber mit dem Feld ``category``? Jedes Event hat genau eine Category, und über das category Attribut können wir dieses Category-Objekt ansprechen. Es handelt sich hier also nicht nur um die numerische ID des Category-Objekts, sondern um das Category-Objekt selbst. .. code-block:: python >>> # über e1.category haben wir Zugriff auf das Related Objekt. >>> Nämlich ein Objekt der Klasse Category, >>> # welches wir über den Foreign-Key adressieren >>> e1.category # wenn wir die ID des related Objects haben wollen, können wir das mit # dieser Syntax tun: >>> e1.category_id 11 # category_id entspricht dem Datenbankfeld. # alternativ ginge natürlich auch: >>> e1.category.pk 11 >>> e1.category.id 11 >>> >>> # mit dieser neuen Schreibweise kann man die Events auch direkt mit der >>> # Category-ID anlegen >>> e1 = Event.objects.create(name="Hitchhikers Club", date=d, category_id=11) >>> # und sprechen jetzt über das Event-Objekt die Beschreibung der >>> # Kategorie an >>> e1.category.description 'Alles was mit büchern zu tun hat' >>> >>> # Wir können Objekte natürlich auch aus der Datenbank löschen >>> e1.delete() (1, {'events.Event': 1}) >>> >>> # Das Objekt hitchhiker existiert zwar noch hier in der Shell, + >>> # aber nicht mehr in der Datenbank >>> e1 >>> >>> # wir können das daran erkennen, dass das Objekt nun keine ID mehr besitzt >>> e1.id >>> >>> # da wir das Objekt gleich noch benötigen, erstellen wir es nochmal >>> e1 = Event.objects.create(name="Hitchhikers Club", date=d, category=books) >>> >>> e1.id 45 # und noch eine Leserunde >>> e2 = Event.objects.create(name="Harry Potter Fans", date=d, category=books) Dem aufmerksamen User ist vielleicht aufgefallen, dass wir die ID eines Category-Objektes einmal mit e1.category.pk und einmal mit e1.category.id ausgegeben haben. Das Ergebnis war das selbe. Warum ist das so? Wie wir gesehen hatten, erstellt Django für jedes Model hinter den Kulissen ein id-Feld. Wir können aber auch ein anderes Feld als id mit einem Primary Key versehen, wenn wir das wollten. .. admonition:: Intermezzo: Timezone Generell stellt es vor allem in internationalen Projekten ein Problem dar, wenn eine Zeitangabe (datetime) nicht mit der Zeitzone gespeichert wird, in der es erstellt wurde. Deshalb ist es in international ausgerichteten Projekten sinnvoll, Daten im System immer als ``UTC`` (Universal Time Coordinated) zu speichern und in der Ausgabe dann darauf zu achten, dass die Zeit in der lokalen Zeitzone ausgegeben wird. Führen wir in der Shell ``timezone.now()`` aus und formatieren das Ergebnis entsprechend, sehen wir, dass die Zeit eine Stunde vor unserer Zeit liegt. Deutschland liegt in der Zeitzone UTC+1 (bzw. UTC+2 in der Sommerzeit) und das erklärt den Unterschied. .. code-block:: python >>> timezone.now().strftime("%y-%m-%d %H::%M::%S") .. admonition:: Python Objekt untersuchen Wir hatten schon zwei Methoden gesehen, ein Python-Objekt zu untersuchen. Das war einmal die built-in Funktion ``dir()`` und einmal das ``__dict__``-Attribut, welches uns den aktuellen Zustand eines Objekts zeigt. Eine weitere Methode, Informationen über ein Objekt zu erhalten, ohne einen Debugger zu nutzen, ist das Modul ``inspect``. .. code-block:: python from inspect import getmembers, isfunction getMembers(e1) # nur die Methoden des Objekts betrachten functions = list(map(lambda x: x[0], getmembers(e1, isfunction))) Um mit Objekte zu arbeiten, müssen wir diese aus der Datenbank laden und modifizieren können. Hier kommt jetzt der ``Manager`` ins Spiel. der Model-Manager ================== Ein Manager ist eine Python-Klasse, die die Schnittstelle zwischen Datenbankabfragen und einem Django-Model bereitstellt. Jedes Model in Django besitzt mindestens einen Manager. Der Default-Manager eines Objektes heisst ``objects``. Der Manager wird formal auch als ``Object Relation Mapper (ORM)`` bezeichnet. .. admonition:: einen eigenen Manager entwickeln Später im Buch werden wir unseren eigenen Manager entwickeln. Ein Manager ist nichts weiter, als eine Pythonklasse, die uns eine Schnittstelle auf die Datenbank-Tabelle liefert. Damit lassen sich komfortabel immerwiederkehrende Aufgaben lösen. **Mehr zu Managers schon mal hier:** ``_ **Wichtig:** der Manager ist immer der Klasse und nicht dem Objekt zugeteilt. Das würde auch keinen Sinn machen, denn mit dem Manager hat man Zugriff auf die Datenbank-Tabelle und damit auf alle Einträge einer Klasse. Gucken wir uns zwei Methoden des Managers mal genauer an: ``get`` und ``all``. ``get`` liefert uns ein Objekt der Klasse zurück und ``all`` liefert uns alle Objekte der Klasse in einem ``Queryset`` zurück. **Hier ein Ausschnitt einer interaktiven Session in der Shell:** .. code-block:: python >>> # der Manager der Event-Klasse >>> Event.objects >>> >>> # der Manager der Category-Klasse >>> Category.objects >>> >>> # mit get holen wir uns ein Objekt der Klasse Event. >>> # pk steht für Primary Key und ist die ID des Objekt >>> harry = Event.objects.get(pk=46) >>> harry.name 'Harry Potter Leserunde' >>> >>> # wir können jedes beliebige Attribut abfragen: >>> harry = Event.objects.get(name="Harry Potter Leserunde") >>> >>> # oder auch mehrere >>> harry = Event.objects.get(name="Harry Potter Leserunde", sub_title=None) >>> harry 'Harry Potter Leserunde' >>> >>> # Falls ein Objekt nicht existieren sollte, wird eine Exception ausgelöst >>> Event.objects.get(pk=3333) events.models.Event.DoesNotExist: Event matching query does not exist. >>> >>> # über den Bezeichner harry haben wir jetzt wieder Zugriff auf das Objekt >>> >>> # wir ändern jetzt das Attribut name und speichern es in der DB ab. >>> harry.name = "Harry Potter Lesezirkel" >>> harry.save() >>> # um uns alle Event Objekte anzeigen zu lassen, nutzen wir all() >>> Event.objects.all() , ] >>> >>> # Wir legen jetzt noch einen weiteren Event an (gleiches Datum und Kategorie) >>> >>> Event.objects.create(name="Kafka Freunde", date=d, category=books) >>> >>> Event.objects.all() , , ] der Related Manager ======================== Wann immer wir mit einem Foreign-Key eine Beziehung zu einem anderen Model herstellen, können wir auch von Objekten dieser Klasse einen Manager aufrufen, und zwar über den ``related_name``, den wir beim Definieren des Feldes angegeben hatten. Dieser Manager wird als ``Related-Manager`` bezeichnet und kann genauso genutzt werden, wie der normale Manager. Sehen wir uns nochmal kurz das Event-Model und das Feld category an: .. code-block:: python category = models.ForeignKey( Category, on_delete=models.CASCADE, related_name="events" ) Das ``related_name``-Argument beim Definieren des Foreign-Keys ermöglicht es, über ein Objekt der Klasse ``Category`` den Related-Manager via ``events`` aufzurufen: Im Gegensatz zum *normalen* Manager lässt sich der *Related-Manager* nicht auf die Klasse anwenden. **Hier ein Ausschnitt einer interaktiven Session in der Shell:** .. code-block:: python >>> Category.objects.all() , ] >>> >>> buecherwurm = Category.objects.all()[0] >>> buecherwurm >>> >>> # oder auch >>> buecherwurm = Category.objects.first() >>> buecherwurm >>> >>> # Aufrufen der all-Methode des Related-Managers events, um alle >>> # Events aufzulisten, der der Kategorie Bücherwurm zugeordnet sind. >>> >>> # der Related Manager events des Objekts buecherwurm >>> buecherwurm.events >>> >>> # über den Related Manager auf alle Event-Objekte zugreifen >>> # also hier: alle Events, aus der Kategorie "Bücherwurm" >>> buecherwurm.events.all() , ]> **Mehr zum Thame related Objects:** ``_ Der Manager beinhaltet noch einige weitere Methoden, von denen wir hier und im nächsten Kapitel noch einige vorstellen wollen. .. code-block:: python >>> # erstes Objekt >>> Event.objects.first() >>> # letztes Objekt >>> Event.objects.last() >>> # vorhandene Events in Datenbank >>> Event.objects.count() 3 >> # Queryset sortieren Event.objects.order_by("date") Grundsätzlich unterscheiden wir zwischen zwei Methoden des Managers: Methoden, die ein ``Queryset`` zurückgeben, zb. ``all`` und Methoden, die etwas anderes zurückgeben, zb. ``get`` (liefert Objekt) oder ``count`` (liefert Integer). Was ein ``Queryset`` eigentlich genau ist, besprechen wir im nächsten Kapitel. .. admonition:: Best Practice Ein häufiger Anfängerfehler ist es, Aufgaben, die auf der (sehr viel schnelleren) Datenbank gelöst werden könnten, direkt in der Applikationslogik via Python zu lösen. So resultiert zum Beispiel der Aufruf von ``Event.objects.count()`` in der SQL Entsprechung ``SELECT COUNT(*) as anzahl from events``. Eine sehr viel langsamere Lösung wäre es, sich alle Objekte per ``Event.objects.all()`` zu holen und über diese die Python Funktion ``len()`` auszuführen. **TIP:** soviele Operationen wie möglich auf der Datenbank machen lassen! **Eine Übersicht aller Methoden des Managers, die kein Queryset zurückgeben, findet sich hier:** ``_ Weiterführende Links ...................... * ``_ * ``_ * ``_ Eine Übersicht der Managermethoden ...................................................................... Eine Übersicht aller wichtigen Manager-Methoden findet ihr im **Cookbook 3, der Model-Manager**