20130220

Why Django needs a populated signal

lino.projects.homeworkschool had a problem with the new version, caused by the Ross McFarland’s trick of having lino as the last installed_app and calling lino.Lino.startup automatically from lino.models.

The problem is that lino.projects.homeworkschool has apps which don’t import at first run. The populate function in django.db.models.loading “postpones” them and gives them a second chance.

The order of installed applications is:

lino.modlib.school
lino.projects.homeworkschool

These two modules have the following interdependency:

  • homeworkschool defines the concrete implementation of contacts.Person (which is declared in override_modlib_models)
  • school needs the concrete implementation of contacts.Person at module-level to define the Pupil and Teacher classes.

This means that school will get postponed until homeworkschool has been loaded.

All the above is rather trivial, but it took me a lot of time to understand the problem because of a “little bug”:

The resolve_model function may not use seed_cache=True (which causes Django to populate the models cache before looking for the model) since we want to have it usable at the top-level of models modules. Which is what lino.projects.homeworkschool did. This caused a recursive populate, leading to very confusing error messages.

Another piece of time went into finding out how to cope with this situation. Which you can admire nor in lino.models.

All this shows how regrettable it is that Django doesn’t provide a populated signal. It would be so easy, just two lines of code in /django/db/models/loading.py that wouldn’t hurt anybody:

populated = signals.Signal() # <<<<<<<<<<<<<<<<<<<<<<<<< first line

class AppCache(object):

    ...

    def _populate(self):
        if self.loaded:
            return
        imp.acquire_lock()
        try:
            if self.loaded:
                return
            for app_name in settings.INSTALLED_APPS:
                if app_name in self.handled:
                    continue
                self.load_app(app_name, True)
            if not self.nesting_level:
                for app_name in self.postponed:
                    self.load_app(app_name)
                self.loaded = True
            populated.send(sender=self)  # <<<<<<<<<<<<< second line
        finally:
            imp.release_lock()

A test suite for watch_tim

A first bug in yesterday’s release occured. Here is my first reaction:

Patsch, der erste Bug in der neuen Version: diese Fehlermeldung kommt, wenn man in TIM einen Partner mit leerem PAR->IdUsr bearbeitet, der in Lino als Klient existiert und noch keine Begleitungen hat… Im vorliegenden Fall war es Partner Nummer 23633 (Kurt Sch.).

Die Unstabilität von watch_tim ist normal. Eigentlich müsste ich eine ganze Latte von unit tests schreiben. Das ist technisch nicht sehr kompliziert, würde aber viel Aufwand bedeuten, weil alle möglichen Normal- und Sonderfälle darin vorkommen müssten. Angesichts der Tatsache, dass watch_tim voraussichtlich bei euch schon bald Vergangenheit ist und auch garantiert nie bei irgendeinem anderen TIM-Benutzer jemals Verwendung finden wird finde ich, dass wir das einfach aushalten müssen…

After having written this, I nevertheless decided to start a unit test suite: watchtim_tests.

This took only 5 minutes, and at least this special case is now covered. I won’t invest much energy into getting everything covered for the said reasons, but who knows…

à propos test suite

Damit der obige Test in der Praxis taugt, müsste ich freilich erstmal die Testsuite wieder aufpäppeln, die ich in letzter Zeit vernachlässigt habe. Also ran an den Speck. Jetzt oder nie. Zumal ich vor dem Release heute abend sowieso nichts großes Neues anfangen will.

Eine zeitraubende Sache war folgendes: In einem Demo-Test (test002) machte er eine Abfrage nach /api/cv/SoftSkillsByPerson. Nun ist SoftSkillsByPerson eine dynamische Tabelle, deren Titel und Inhalt von der SiteConfig abhängt. Und die SiteConfig kriegt sinnvolle Werte erst wenn die Demo-Daten eingelesen werden. Wenn ich aber die gesamte Test-Suite laufen lasse, wurde vorher (quick_tests) schon das UI initialisiert, und zwar ohne Demo-Daten. Und unsere dynamische Tabelle wurde natürlich nur ein einziges Mal pro Prozess generiert: wenn man in den Site-Parametern eines der Felder “Eigenschaftsgruppe Fähigkeiten”, “Eigenschaftsgruppe Sozialkompetenzen” oder “Eigenschaftsgruppe Hindernisse” verändert hätte, muss man anschließend den Server neustarten, damit es aktiv wird. So war das schon immer gewesen. Wer will denn auch was Feineres. Jawohl, unsere Testsuite will das.