Manchmal kann die Verwendung von Datenbankansichten anstelle von Datenbanktabellen für die Abfrage aggregierter Daten sehr hilfreich sein. Typischerweise ist im Django ORM eine Datenbanktabelle an ein Django ORM-Modell gebunden. Wir haben jedoch festgestellt, dass es auch möglich ist, eine Datenbankansicht mit einem Django ORM-Modell zu verknüpfen. Dadurch lässt sich die Datenaggregationslogik auf Datenbankebene (in der Ansicht, die SQL erstellt) verbergen. Die meisten ORM-Funktionen, wie die Suche nach Fremdschlüsseln mit doppeltem Unterstrich, funktionieren auch für das an eine Datenbankansicht gebundene Modell. In diesem Blogbeitrag zeige ich Ihnen anhand eines einfachen Beispiels, wie das funktioniert.
Die regulären Modelle
In unserem Beispiel werden die folgenden regulären Modelle verwendet. An jedes Modell ist eine Datenbanktabelle angebunden.
aus django.db importiere Modelle Klasse Job(models.Model): Name = models.CharField(max_length=200) Klasse User(models.Model): Name = models.CharField(max_length=200) Klasse Charge(models.Model): HARDWARE = 'hw' SOFTWARE = 'sw' TYPE_CHOICES = ( (HARDWARE, 'Hardware'), (SOFTWARE, 'Software'), ) Typ = models.CharField(choices=TYPE_CHOICES, max_length=2) Betrag = models.DecimalField(max_digits=8, decimal_places=2) Monat = models.DateField() Benutzer = models.ForeignKey(Benutzer) Job = models.ForeignKey(Job, null=True, blank=True)
Das Chargenmodell dient zur Speicherung der Gebühren. Es verfügt über einen Fremdschlüssel zum Benutzermodell und einen optionalen Fremdschlüssel zum Jobmodell. Nicht jeder Chargendatensatz kann einem Job zugeordnet werden.
Die Datenbankansicht
Die Datenbankansicht wird durch Befolgen des SQL erstellt und kann mit dem Roh-SQL-Ausführungsbefehl in eine benutzerdefinierte Datenmigration eingefügt werden.
ERSTELLEN ODER ERSETZEN SIE VIEW app_chargesummary ALS SELECT row_number() ÜBER () als ID, ci.user_id, ci.job_id, ci.month, SUMME (FALL, WENN ci.type = 'hw' DANN ci.amount SONST 0 ENDE) ALS Hardware, SUMME (FALL, WENN ci.type = 'sw' DANN ci.amount SONST 0 ENDE) ALS Software VON app_charge ci GRUPPE NACH ci.user_id, ci.job_id, ci.month;
Diese Ansicht fasst alle Hardware- und Softwaregebühren für die Gebührendatensätze mit demselben Job, Benutzer und Monat zusammen. Sie enthält die folgenden Spalten:
[id, Benutzer-ID, Job-ID, Monat, Hardware, Software]
Der nächste Schritt besteht darin, ein Django-Modell zu erstellen, das dieser Ansicht zugeordnet ist, sodass wir Django ORM verwenden können, um die Daten aus der Ansicht abzurufen.
Das Modell für die Ansicht
Um die Datenbankansicht abzubilden, haben wir das folgende Modell erstellt.
Klasse ChargeSummary (Modelle.Modell): ID = Modelle.BigIntegerField (Primärschlüssel = True) Benutzer = Modelle.ForeignKey (Benutzer, beim Löschen = Modelle.NICHT TUN) Job = Modelle.ForeignKey (Job, beim Löschen = Modelle.NICHT TUN) Monat = Modelle.DateField () Hardware = Modelle.DecimalField (maximale Ziffern = 19, Dezimalstellen = 2) Software = Modelle.DecimalField (maximale Ziffern = 19, Dezimalstellen = 2) Klasse Meta: verwaltet = False db_table = „app_chargesummary“
Obwohl das obige Modell einem regulären Django-Modell sehr ähnlich sieht, gibt es drei bemerkenswerte Unterschiede.
- Für die Fremdschlüsselfelder benötigen wir einen zusätzlichen kwarg: on_delete=models.DO_NOTHING. Obwohl wir normalerweise nicht versuchen, delete() für das ChargeSummary-Modell aufzurufen, da die dahinterliegende Ansicht nicht beschreibbar ist, würde ohne diesen kwarg einige Testfälle fehlschlagen. Dies könnte mit dem automatischen Bereinigungsmechanismus für die Testdatenbank nach Ausführung der Testfälle zusammenhängen.
- managed = False muss in der Klasse Meta angegeben werden, damit die automatische Migration der South- oder Django-Datenbank dieses Modell ignoriert und nicht versucht, eine Datenbankschemamigration dafür zu erstellen.
- db_table = „Name der Ansicht“ muss in der Klasse Meta angegeben werden, damit Django ORM weiß, aus welcher Datenbankentität die Daten abgerufen werden sollen.
Abfrage mit dem neuen Modell
Mit dem neuen Modell können Sie die regulären ORM-Abfragemethoden ohne Unterschiede zum regulären Django-Modell verwenden.
Alle ChargeSummary-Objekte abrufen:
ChargeSummary.object.all()
Holen Sie sich alle ChargeSummary-Objekte für Mai 2015:
ChargeSummary.object.filter(Monat=DatumUhrzeit(Jahr=2015, Monat=5, Tag=1, tzinfo=utc))
Holen Sie sich alle ChargeSummary-Objekte mit einem bestimmten Jobnamen:
ChargeSummary.object.filter(job__name='Testjob')
Rufen Sie alle ChargeSummary-Objekte mit einer bestimmten Benutzer-E-Mail-Adresse ab (angenommen, Sie haben ein 1:1-Benutzerprofilmodell, das mit dem Benutzermodell verknüpft ist):
ChargeSummary.object.filter(user__profile__email='irwen@rescale.com')
Natürlich werden bestimmte Methoden wie save() oder delete() vermutlich fehlschlagen, da DML-SQLs von Natur aus nicht auf Datenbankansichten anwendbar sind.
Zu guter Letzt
Wenn Sie Ihre App von einer früheren Version auf Django 1.7 oder höher aktualisieren, müssen Sie Folgendes ausführen:
python manage.py makemigrations your_app_label
um 0001_initial.py zu generieren und führen Sie dann den folgenden Befehl aus, um Ihre Datenbank zu aktualisieren.
python manage.py migrieren --fake-initial
Dies schlägt leider fehl, wenn Sie die Datenbankansicht und das nicht verwaltete ChargeSummary-Modell haben. Der Grund: Wenn Django die Migrationen mit dem Flag „Fake Initial“ ausführt, überprüft es, ob die Tabellen für alle Modelle in den CreateModel-Operationen der Migration 0001_inital.py in der Datenbank vorhanden sind. CreateModel-Operationen werden für jedes Modell erstellt, auch für Modelle mit „managed=False“. Daher schlägt dies für das ChargeSummary-Modell fehl, da die tatsächliche Tabelle nicht gefunden werden kann. Glücklicherweise können wir dieses Problem umgehen, indem wir die Ansicht „create sql“ in 0001_initial.py einfügen und die Migrationen dann mit dem Flag „Fake Initial“ ausführen.
... migrations.RunSQL( """ DROP VIEW IF EXISTS app_chargesummary; CREATE OR REPLACE VIEW app_chargesummary ...; """ ), ...
Viel Spaß beim Modellieren und Codieren mit Django ORM.
