Tabellenübergreifende Constraints in Django realisieren

Django-Datenbank-Constraints können keine Felder außerhalb der jeweiligen Tabelle referenzieren und damit z. B. Foreign-Key-Beziehungen plausibilisieren. Die PostgreSQL-Doku erklärt, was DB-seitig möglich ist und warum keine anderen Rows oder Tables angesprochen werden dürfen (bzw. sollen). Um bestimmte übergreifende Restriktionen beim Einfügen einer Zeile abzuprüfen, sollten demzufolge Trigger verwendet werden.

Dieser Artikel gibt ein schönes Beispiel für einen Trigger (in plpgsql), der Werte einer neu eingefügten Row gegen vorhandene Einträge in anderen Tabellen prüft und ggf. ein Insert (oder auch ein Update) verhindert.

Wie man den Trigger (und ähnliches) per Django-Migration in die Datenbank bekommt, wird in der Antwort auf diese Stackexchange-Frage mit einem einfachen Beispiel erläutert.

Allerdings muss man in den Views vorsichtig sein: Wenn bei einem Insert oder Update über den Django-ORM so ein Trigger mit einer Exception zuschlägt, bekommt man einen IntegrityError, der dann auch abgefangen werden sollte. Sonst kommt es zum Error500. Die Ursache für den Error ist nicht so ganz einfach zu erkennen – deshalb sollte man durch Vorababfragen die Situation möglichst vermeiden (und dann ordentlich anmeckern).

Der Trigger ist dann trotzdem nicht überflüssig – er verhindert vor allem, dass durch Umgehung der entsprechenden Views oder durch Race conditions die Prüfung das Eintragen falscher Daten nicht wirksam verhindert.