Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

Is MTI a bad thing?

If you believe that multi-table inheritance “is a bad thing”, then this example shows how you would solve the same real-world problem as lino_book.projects.mti by using OneToOneFields instead of MTI.

The database models

Here is the models.py file used for this example.

We have a table of Places, some of them are Restaurants, some are Pubs, and some are neither Pub nor Restaurant.

# -*- coding: UTF-8 -*-
# Copyright 2015-2020 Rumma & Ko Ltd
# License: GNU Affero General Public License v3 (see file COPYING for details)

from django.db import models
from lino.api import dd


class Person(dd.Model):

    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name


class Place(dd.Model):

    name = models.CharField(max_length=50)
    owners = models.ManyToManyField(Person, related_name="owned_places")
    ceo = dd.ForeignKey(Person, related_name="managed_places")

    def __str__(self):
        if self.get_restaurant():
            if self.get_bar():
                what = "Restaurant & Bar "
            else:
                what = "Restaurant "
        elif self.get_bar():
            what = "Bar "
        else:
            what = ''
        if self.pk is None:
            return self.name
        return "%s %s(ceo=%s,owners=%s)" % (
            self.name, what, self.ceo, ','.join(
                [str(o) for o in self.owners.all()]))

    def get_restaurant(self):
        try:
            return self.restaurant
        except Restaurant.DoesNotExist:
            return None

    def get_bar(self):
        try:
            return self.bar
        except Bar.DoesNotExist:
            return None


class Bar(dd.Model):

    place = dd.OneToOneField(Place)
    serves_alcohol = models.BooleanField(default=True)

    def __str__(self):
        if self.serves_alcohol:
            return self.place.name
        return "%s (no alcohol)" % self.place.name


class Restaurant(dd.Model):

    place = dd.OneToOneField(Place)
    serves_hot_dogs = models.BooleanField(default=False)
    cooks = models.ManyToManyField(Person)

    def __str__(self):
        if self.pk is None:
            return self.place.name
        return "%s (cooks=%s)" % (self.place.name, ','.join(
            [str(o) for o in self.cooks.all()]))


class Visit(models.Model):

    allow_cascaded_delete = ['place']
    person = dd.ForeignKey(Person)
    place = dd.ForeignKey(Place)
    purpose = models.CharField(max_length=50)

    def __str__(self):
        return "%s visit by %s at %s" % (self.purpose, self.person,
                                         self.place.name)


class Meal(models.Model):
    allow_cascaded_delete = ['restaurant']
    person = dd.ForeignKey(Person)
    restaurant = dd.ForeignKey(Restaurant)
    what = models.CharField(max_length=50)

    def __str__(self):
        return "%s eats %s at %s" % (self.person, self.what,
                                     self.restaurant.name)


from .ui import *

The demo data

Here are the Persons who act in our story:

>>> rt.show('app.Persons')
========
 name
--------
 Anne
 Bert
 Claude
 Dirk
 Ernie
 Fred
========
>>> rt.show('app.Places')
==== ===================== ======== ============================
 ID   name                  person   owners
---- --------------------- -------- ----------------------------
 1    Bert's pub            Anne     `Anne <…>`__, `Bert <…>`__
 2    The Chopping Shack    Bert     `Claude <…>`__
 3    The Abacus Well       Ernie    `Fred <…>`__
 4    The Olive Lounge      Bert     `Claude <…>`__
 5    The Autumn Bite       Ernie    `Fred <…>`__
 6    The Private Mission   Bert     `Claude <…>`__
 7    Nova                  Ernie    `Fred <…>`__
 8    Babylon               Bert     `Claude <…>`__
 9    Blossoms              Ernie    `Fred <…>`__
 10   Whisperwind           Bert     `Claude <…>`__
 11   Catch                 Ernie    `Fred <…>`__
==== ===================== ======== ============================
>>> rt.show('app.Restaurants')
==== ========================================================= ================= ==============
 ID   place                                                     serves hot dogs   cooks
---- --------------------------------------------------------- ----------------- --------------
 1    The Chopping Shack Restaurant (ceo=Bert,owners=Claude)    No                `Dirk <…>`__
 2    The Abacus Well Restaurant (ceo=Ernie,owners=Fred)        No                `Anne <…>`__
 3    The Olive Lounge Restaurant (ceo=Bert,owners=Claude)      No                `Dirk <…>`__
 4    The Autumn Bite Restaurant (ceo=Ernie,owners=Fred)        No                `Anne <…>`__
 5    The Private Mission Restaurant (ceo=Bert,owners=Claude)   No                `Dirk <…>`__
 6    Nova Restaurant (ceo=Ernie,owners=Fred)                   No                `Anne <…>`__
 7    Babylon Restaurant (ceo=Bert,owners=Claude)               No                `Dirk <…>`__
 8    Blossoms Restaurant (ceo=Ernie,owners=Fred)               No                `Anne <…>`__
 9    Whisperwind Restaurant (ceo=Bert,owners=Claude)           No                `Dirk <…>`__
 10   Catch Restaurant (ceo=Ernie,owners=Fred)                  No                `Anne <…>`__
==== ========================================================= ================= ==============