Diamond inheritance

This document summarizies some work done in Lino around diamond inheritance, especially the problem described in Django ticket Django ticket #10808.

We have two projects for this: lino_book.projects.diamond and lino_book.projects.diamond2.

Models 1

# -*- coding: UTF-8 -*-
# Copyright 2015-2018 Rumma & Ko Ltd
# License: BSD (see file COPYING for details)

from django.db import models

class Restaurant(models.Model):
    name = models.CharField(max_length=255)


class Bar(Restaurant):
    bar_restaurant = models.OneToOneField(
        Restaurant, parent_link=True, on_delete=models.CASCADE)
    min_age = models.IntegerField()


class Pizzeria(Restaurant):
    pizzeria_restaurant = models.OneToOneField(
        Restaurant, parent_link=True, on_delete=models.CASCADE)
    specialty = models.CharField(max_length=255)


class PizzeriaBar(Bar, Pizzeria):
    pizza_bar_specific_field = models.CharField(max_length=255)

Models 2

The difference with variant 1 is that now we have two abstract parents.

from django.db import models


class Addressable(models.Model):
    class Meta:
        abstract = True
    street = models.CharField(max_length=255, blank=True)
        

class Restaurant(Addressable):
    class Meta:
        abstract = True

    # restaurant_addressable = models.OneToOneField(
    #     Addressable, parent_link=True, on_delete=models.CASCADE)
    
    name = models.CharField(max_length=255)
    

class Bar(Restaurant):
    class Meta:
        abstract = True

    # bar_restaurant = models.OneToOneField(
    #     Restaurant, parent_link=True, on_delete=models.CASCADE)
    
    min_age = models.IntegerField()
    

class Pizzeria(Restaurant):
    
    # pizzeria_restaurant = models.OneToOneField(
    #     Restaurant, parent_link=True, on_delete=models.CASCADE)

    specialty = models.CharField(max_length=255)

    
class PizzeriaBar(Bar, Pizzeria):
    pizza_bar_specific_field = models.CharField(max_length=255)

    # pizzeriabar_pizzeria = models.OneToOneField(
    #     Pizzeria, parent_link=True, on_delete=models.CASCADE)

    # pizzeriabar_bar = models.OneToOneField(
    #     Bar, parent_link=True, on_delete=models.CASCADE)

The problem

>> from main.models import PizzeriaBar >> p = PizzeriaBar(name="A", min_age="B", specialty="C", ... pizza_bar_specific_field="Doodle")

Despite the fact that we specify a non-blank value for name, we get a database object whose name is blank, while the pizza_bar_specific_field field is not:

>> print(p.name) <BLANKLINE> >> print(p.pizza_bar_specific_field) Doodle

Lino has a work-around for this problem. But that workaround works only until Django 1.10. The corrresponding test case is skipped in Django 1.11+ where Django raises a django.core.exceptions.FieldError saying that "Local field u'street' in class 'PizzeriaBar' clashes with field of the same name from base class 'Pizzeria'". This comes because we additionally to simple diamond inheritance the street field is defined in a parent of the common parent. Django then gets messed up when testing for duplicate fields and incorrectly thinks that street is duplicated. (TODO: verify whether this is a problem)