Wer frisst meinen Plattenplatz?

Für das altbekannte Kommando du (disk usage), mit dem man sich über den Speicherplatzverbrauch innerhalb eines Verzeichnisses informieren kann, gibt es eine modernere Alternative: dust zeigt (auf Wunsch) die Dateien und Unterverzeichnisse nach Größe der Belegung sortiert an, „menschenlesbare“ Größenangaben und Prozentbalken, die ärgsten Platzfresser werden farblich gekennzeichnet, und weil es in Rust geschrieben ist, ist es auch ziemlich schnell.

Debian bietet das Programm im Paket du-dust an.

In Django mit Materialized Views arbeiten

Auch in einer Django-Anwendung kann man mit PostgreSQLs Materialized Views arbeiten – dieser Artikel erklärt, was man wie machen muss. Nämlich

  • ein passendes Model definieren, das unmanaged ist und den View als Table hat

  • den View innerhalb einer Migration erzeugen (CREATE MATERIALIZED VIEW name AS …)

  • den View gelegentlich (concurrently) refreshen (evtl. könnte man sich im Cache merken, ob er aktuell ist, und per Signal diesen Merker löschen, wenn sich an den verwendeten Tabellen etwas ändert, etwa durch Speichern oder Löschen von Objekten)

  • beachten, dass der View eine „unique“ indizierbare Spalte hat (sonst geht concurrent refresh nicht)

Abfragen solcher Views können wesentlich schneller sein als bei konventionellen Views, deren Daten bei jedem Zugriff wieder „on the fly“ erstellt werden müssen. Mehr dazu findet sich in der PostgreSQL-Doku.

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.

Django-ORM ohne das Django-Framework benutzen

Der Django-ORM kann auch nützlich sein für Anwendungen, die zwar mit einer Datenbank, aber nicht mit einem Web-Framework arbeiten sollen, und manchmal möchte man auch ein einem Programm mit einer von Django verwalteten Datenbank arbeiten, ohne die gesamte Django-Umgebung zu aktivieren (und vielleicht kommt die Lösung, das Programm als Management-Kommando laufen zu lassen, aus irgendwelchen Gründen nicht in Frage).

Es ist durchaus möglich, lediglich den ORM zu aktivieren, ohne URLs, Views, komplette Settings usw. einzurichten. Ein Blogpost zeigt eine einfache Möglichkeit auf, in wenigen Zeilen alles einzurichten, was für den ORM benötigt wird:

#!/usr/bin/env python3

def init_django():
    import django
    from django.conf import settings

    if not settings.configured:
        settings.configure(
            INSTALLED_APPS=["db"],
            DATABASES={
                "default": {
                    "ENGINE": "django.db.backends.postgresql", "NAME": …
                    }
                }
            )
        django.setup()


if __name__ == "__main__":
    from django.core.management import execute_from_command_line

    init_django()
    execute_from_command_line()

Im Modul db/models.py stehen dann die Klassendefinitionen für die Models:

from django.db import models
from manage import init_django  # oder ggf. "from ..manage …"

init_django()

class MyModel(models.Model):
    …

Es geht noch kürzer:

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
django.setup()

In dieser Variante stehen die Settings in einem eigenen Modul project/settings.py, brauchen aber natürlich auch nur das Allernötigste zu umfassen (siehe oben: INSTALLED_APPS und DATABASES).

Wie man's auch macht – man hat dann eine Anwendung, die man leicht nachträglich zu einer vollständigen Web-App aufbohren kann.

Django views the right way

Der Artikel Django views the right way von Luke Plant enthält eine ganze Reihe von best practice recommendations für Django-Views.

Einige – mir interessant erscheinende – Punkte seien herausgegriffen:

  • TemplateResponse() statt render() zu verwenden hat einige Vorteile

  • FBV statt CBV benutzen – er nennt viele Argumente dafür und weist darauf hin, dass die Einsparung von Boilerplate-Code vor allem die Lesbarkeit verbessert, mehr noch als das Schreiben zu erleichtern

  • django-urlconfchecks kann prüfen, ob die urls.py-Einträge zu den Views passen bzgl. Anzahl und Typen der View-Parameter (die statischen Type-Checker können das nicht)

  • Das request-Objekt eines Views sollte nicht herumgereicht werden, vor allem nicht zu den models.

  • Das Objekt eines Detail-Views im context sollte nicht object heißen (und demzufolge das Objekt eines List-Views nicht object_list).

  • Die Paginierung längerer Ausgaben kann man standardisieren:

    context = { …
            } | paged_object_list_context(request, products, paginate_by=5)
  • Sehr dynamische Formulare sollte man nicht als Form definieren, sondern zu Fuß definieren.

  • Precondition checks mit Decorators sind eine gute Idee – einige nützliche Beispiele werden dafür gegeben. Die Nutzung von @functools.wraps(view_func) ist ratsam!

  • Mehrere Decorators eines FBV kombinieren sich gut, mehrere Mixins eines CBV aber eher nicht!

  • Gute Hinweise, wie man sicherheitsrelevante Decorators in einer App erzwingen kann

  • Wie kann man Views „dünn“ halten?

  • Benutzerbezogene Queries sollte man über das Benutzerobjekt laufen lassen: user.bookings.in_basket()

  • Ein Service-Layer wird hier abgelehnt.

  • Optimierungen (wie select_related & Co.) lassen sich nur schwerlich aus dem Views-Layer heraushalten, eine ordentliche separation of concerns ist praktisch nicht möglich.

Neues Release von whenever

Whenever 0.6 ist freigegeben; es bringt einige inkompatible Änderungen mit sich, aber auch eine teilweise Implementierung in Rust mit entsprechenden Geschwindigkeitszuwächsen.

Whenever ist eine weitere, recht neue Alternative zum datetime aus der Python-Standardbibliothek. Beim Lesen der Doku drängt sich der Verdacht auf, es sei die erste, bei der man alles richtig gemacht hat … ;-)

Es ist nicht API-kompatibel zu datetime, aber deckt dessen Funktionsumfang ab (und lässt sich von/zu datetime-Objekten konvertieren). Dafür bietet es Typsicherheit und eine logischere Struktur.