20140101 (Wednesday, 01 January 2014)

Why the new apps management in Django 1.7 is not enough

(Note: the following may not be true anymore. See future posts and /dev/apps)

Remember 2013-12-26 when I was delighted to discover Aymeric’s new app management, but then discovered that it has a serious difference: AppConfig objects don’t get instantiated before importing the models modules, but at the same time. Which for me made it impossible to use Aymeric’s implementation for doing the things I need to do.

Today during a chat with Joe I grew new hope that maybe I was wrong, that maybe it is possible?

But after some meditation I –unfortunately– came again to the same conclusion: the new apps management in Django 1.7 is not enough for me. Here is an example which explains why.

Let’s take Renamed “Lino Faggio” to “Lino Voga”. It uses Lino’s standard calendar module lino.apps.cal, but extends the Room model defined there:

  • it adds two fields
  • it adds another base class (the ContactRelated mixin)
  • it overrides the __unicode__ method

Here is the relevant application developer’s code which defines the Faggio version of cal.Room:

from lino.apps.cal.models import Room
from lino.modlib.contacts.models import ContactRelated

class Room(Room, ContactRelated):

    tariff = dd.ForeignKey('products.Product', ...)
    calendar = dd.ForeignKey('cal.Calendar', ...)

    def __unicode__(self):
        s = dd.BabelNamed.__unicode__(self)
        if self.company and self.company.city:
            s = '%s (%s)' % (self.company.city, s)
        return s

For this to work, the library version of cal.Room must have abstract=True. Here is how that class is begin defined:

class Room(dd.BabelNamed):
    class Meta:
        abstract = settings.SITE.is_abstract_model('cal.Room')
        verbose_name = _("Room")
        verbose_name_plural = _("Rooms")

The abstractness of certain models in a library app must be optional. Not all Lino applications who use the calendar module want to override the Room model. Afaics there is no way in Django to make a model abstract “afterwards”. IOW we need a central place where models modules can ask whether it wants a given model to be abstract or not.

This is why we call the is_abstract_model method. The implementation of this method has evolved in time. The first implementation used a simple set of strings in a class attribute of djangosite.Site. That might have been a standard Django setting. But as things got more and more complex, it became difficult to define this manually. And it was useless work because every app does know which library models it is going to override. But how to load that information from an app before actually importing it? I then discovered that Django doesn’t use the __init__.py files of installed apps. And of course I was lucky to have a djangosite.Site class which is being instantiated before settings have finished to load…

The trick here is that the lino_faggio/cal/__init__.py file now contains this information in the extends_models attribute:

class Plugin(Plugin):

    extends = 'lino.apps.cal'
    extends_models = ['cal.Event', 'cal.Room']

This is an important pattern used by Lino’s “modlib” (a collection of reusable apps designed to work together.).

And I don’t see how it would be possible to do this using the current “plain Django 1.7” implementation. Or maybe I have a mind lock;-)

Note that I am in the process of renaming lino.modlib to lino.apps, and the cal module has already moved, contacts not yet.