Semantic models

The standard way in Django to define models is something like this:

class User(Model):
    name = CharField()
    person_number = CharField()
    birth_place = ForeignKey(Location, on_delete=CASCADE)
    manager = ForeignKey('self', on_delete=CASCADE)

The issue with that is that the semantic meaning of each field is hidden behind the name, and not in the type. The name and person_number fields have the same type but should be handled differently. Since iommi shortcut registrations are based on the type, you can’t customize the parsing or rendering of the person_number or birth_place fields on the project level via the Style.

Moreover in this example the type information alone is not enough for other customization. For ForeignKey, by default in iommi you’ll get a select2 drop-down to select from all items in that table. This is not good UX for a location, and it’s probably not good UX for a manager field either as that should most likely exclude non-active users, and/or limited to users with a certain role.

For CharField, the default is to present a text field, but in the above model we want a Swedish “person number”, which has a specific storage format, can accept a variety of input formats that can be unambiguously parsed, and even has a checksum that can be used to validate that the user input is correct.

A solution to this is to create additional specialized types to specify semantic model fields:

class PersonNumberField(CharField):
    pass


class LocationField(ForeignKey):
    def __init__(self, to=None, *args, **kwargs):
        assert to is None
        to = Location
        super().__init__(to=to, *args, **kwargs)


class UserField(ForeignKey):
    def __init__(self, to=None, *args, **kwargs):
        assert to is None
        to = User
        super().__init__(to=to, *args, **kwargs)

These fields can then be registered in iommi:

register_factory(PersonNumberField, shortcut_name='person_number')
register_factory(LocationField, shortcut_name='location')
register_factory(UserField, shortcut_name='user')

You will then need to add shortcuts for these in your subclasses of Column, Field, and Filter. These can start out empty, and configuration can be done in the style definition:

your_style = Style(
    bootstrap5,
    Field=dict(
        shortcuts=dict(
            person_number__parse=person_number__parse,
        ),
    ),
)

It requires a little bit more initial setup, but for commonly used field types, it will make new views correct by default and super easy to setup.