20130321 (Thursday, 21 March 2013)

Storing Site configuration in a singleton database row

lino.ui.Site has a site_config property which returns the one and only SiteConfig instance. One might call it a “singleton database row”.

The concept includes some challenges, has caused me quite some work in the past and todays whole workday.

Some fragments on how it is implemented.

The site_config property on the lino.ui.Site class uses a cached object instance:

@property
def site_config(self):
    if self._site_config is None:
        SiteConfig = self.modules.ui.SiteConfig
        try:
            self._site_config = SiteConfig.objects.get(pk=1)
        except SiteConfig.DoesNotExist:
            kw = dict(pk=1)
            kw.update(self.site_config_defaults)
            self._site_config = SiteConfig(**kw)
    return self._site_config

One complication is that we must clear this cached instance at certain moments:

def clear_site_config(self):
    self._site_config = None

But when to call this method? There is unfortunately no single clear signal to which to connect it. That’s why we connect it to all the following signals:

def my_callback(sender,**kw):
    settings.SITE.clear_site_config()
from django.db.backends.signals import connection_created
connection_created.connect(my_callback)
testcase_setup.connect(my_callback)
models.signals.post_syncdb.connect(my_callback)
models.signals.post_save.connect(my_callback,sender=SiteConfig) # NOTE

The testcase_setup signal is my own custom signal, emitted only by djangoutils.utils.test.TestCase.

NOTE : I didn’t manage to get that last line working. When specifying a sender, the signal seems to just not get sent. Worked around this by overriding SiteConfig.save() to call directly clear_site_config()

Another complication is that we must write a custom manager:

Can’t we avoid all these complications by not caching the object at all? One of the things I tried yesterday was something like this:

@property
def site_config(self):
    SiteConfig = self.modules.ui.SiteConfig
    try:
        return SiteConfig.objects.get(pk=1)
    except SiteConfig.DoesNotExist:
        kw = dict(pk=1)
        kw.update(self.site_config_defaults)
        sc = SiteConfig(**kw)
        sc.full_clean()
        sc.save()
        return sc

But this caused code as the following (in lino_welfare.pcsw.modlib.tests.pcsw_tests) to produce unexpected results:

def test00(self):
    sc = settings.SITE.site_config
    sc.signer1 = Person(first_name="Ernst",last_name="Keutgen") ; sc.signer1.save()
    sc.signer2 = Person(first_name="Johann",last_name="Ossemann") ; sc.signer2.save()
    sc.full_clean() ; sc.save()

Because saving a Person itself causes an update of the SiteConfig record (to increment the next_partner_id

BTW I also stumbled over Django ticket Django ticket #10200 (loaddata command does not raise CommandError on errors).

These problems seem now solved. Just the pcsw_demo_tests and pcsw_sql_tests are still deactivated, waiting for adaptation. That doesn’t stop me from checking it in.

TODO

Yes, there is much to do:

User requests for welfare.debts

  • The Reference field of an Account wasn’t visible
  • add_site_attribute()