gfks : Utilites for Generic Foreign Keys

The lino.modlib.gkfs plugin adds some utilities for working with Generic Foreign Keys. You should install it if your models contain GenericForeignKey fields or inherit from the Controllable mixin.

This is a tested document. The following instructions are used for initialization:

>>> from lino import startup
>>> startup('lino_book.projects.min2.settings.demo')
>>> from lino.api.doctest import *

Which means that code snippets in this document are tested using the lino_book.projects.min9 demo project.

The Controllable mixin

class lino.modlib.gkfs.Controllable

Mixin for models that are "controllable" by another database object.

Defines three fields owned_type, owned_id and owned. And a class attribute owner_label.

For example in, the owner of a Task or Event is some other database object that caused the task's or event's creation.

Or in lino.modlib.sales and lino.modlib.purchases, an invoice may cause one or several Tasks to be automatically generated when a certain payment mode is specified.

Controllable objects are "governed" or "controlled" by their controller (stored in a field called owner): If the controller gets modified, it may decide to delete or modify some or all of her controlled objects.

Non-automatic tasks always have an empty owner field. Some fields are read-only on an automatic Task because it makes no sense to modify them.

update_controller_field(cls, verbose_name=None, **kwargs)

Update attributes of the owner field and its underlying fields owner_id and owner_type.

This can be used to make the controller optional (i.e. specify whether the owner field may be NULL). Example:

class MyModel(Controllable):

MyModel.update_controller_field(blank=False, null=False)

When verbose_name is specified, all three fields will be updated, appending " (object)" and " (type)" to owner_id and owner_type respectively.

update_owned_instance(self, controllable)

If this (acting as a controller) is itself controlled, forward the call to the controller.


Deprecated. This is (and always was) being ignored. Use update_controller_field() instead. The labels (verbose_name) of the fields owned_type, owned_id and owned are derived from this attribute which may be overridden by subclasses.

controller_is_optional = True

Deprecated. This is (and always was) being ignored. Use update_controller_field() instead.

The ContentTypes table

The ContentTypes table shows all models defined in your application.

==== ============== =========================
 ID   app label      python model class name
---- -------------- -------------------------
 1    system         siteconfig
 2    users          user
 3    users          authority
 4    countries      country
 5    countries      place
 6    contacts       partner
 7    contacts       person
 8    contacts       companytype
 9    contacts       company
 10   contacts       roletype
 11   contacts       role
 12   contenttypes   contenttype
 13   gfks           helptext
 14   checkdata      problem
 15   cal            remotecalendar
 16   cal            room
 17   cal            eventtype
 18   cal            guestrole
 19   cal            calendar
 20   cal            subscription
 21   cal            task
 22   cal            eventpolicy
 23   cal            recurrentevent
 24   cal            event
 25   cal            guest
 26   sessions       session
==== ============== =========================
class lino.modlib.gkfs.ContentTypes

Lino installs this as the default table for django.contrib.ContentType.


Display a clickable list of all MTI parents, i.e. non-abstract base models.

Customized help texts

customized help text

A help text that has been locally overridden. It is stored in the database and loaded at startup. It can be modified by an end user with appropriate permissions.

class lino.modlib.gkfs.HelpText

Django model to represent a customized help text.

>>>'gfks.HelpTexts', language="en")
========== =========================== ========================================================== ==== ===========
 Field      Verbose name                HelpText                                                   ID   Model
---------- --------------------------- ---------------------------------------------------------- ---- -----------
 language   Language (database field)   Die Sprache, in der Dokumente ausgestellt werden sollen.   1    Partner
 field      Field (database field)      The name of the field.                                     2    Help Text
========== =========================== ========================================================== ==== ===========

The language field of a partner is actually defined in lino.mixins.Contactable.

>>> fld = rt.models.contacts.Partner._meta.get_field('language')
>>> for m in fld.model.__mro__:
...    if 'language' in m.__dict__:
...         print(m)
<class 'lino.mixins.Contactable'>

These custom help texts are currently not being used. The field has the help text defined by its prosa doc (lino_xl.lib.contacts.Partner.language):

>>> print(fld.help_text)  
The language to use when communicating with this partner.

That text is currently not translated to German:

>>> with translation.override("de"):
...     print(fld.help_text)  
The language to use when communicating with this partner.

Broken GFKs

class lino.modlib.gkfs.BrokenGFKs

Shows all database objects who have a broken GeneriForeignKey field.


class lino.modlib.gkfs.GenericForeignKey

Add verbose_name and help_text to Django's GFK.

Used by Controllable.

class lino.modlib.gkfs.GenericForeignKeyIdField

Use this instead of models.PositiveIntegerField for fields that are part of a GFK and you want Lino to render them using a Combobox.

Used by lino.modlib.gfks.mixins.Controllable.

Note: type_field is a mandatory argument, but you can specify anything because it is being ignored.

The gfk2lookup() function

The gfk2lookup function is mostly for internal use, but occasionally you might want to use it in your application code.

>>> from lino.core.utils import full_model_name as fmn
>>> from lino.core.gfks import gfk2lookup
>>> from lino.modlib.gfks.mixins import Controllable

List of models which inherit from Controllable:

>>> print(' '.join([fmn(m) for m in rt.models_by_base(Controllable)]))
cal.Event cal.Task checkdata.Problem
>>> obj = contacts.Person.objects.all()[0]
>>> d = gfk2lookup(cal.Event.owner, obj)
>>> pprint(d)
{'owner_id': 114, 'owner_type': <ContentType: Person>}

If the object has a non-integer primary key, then it cannot be target of a GFK. In this case we filter only on the content type because anyway the list will be empty. countries.Country is actually the only model with a non-integer primary key.

>>> obj = countries.Country.objects.all()[0]
>>> gfk2lookup(cal.Event.owner, obj)
{'owner_type': <ContentType: Country>}