[Fixed]-Django Model Inheritance And Foreign Keys

3đź‘Ť

âś…

From the Django Docs:

For example, if you were building a
database of “places”, you would build
pretty standard stuff such as address,
phone number, etc. in the database.
Then, if you wanted to build a
database of restaurants on top of the
places, instead of repeating yourself
and replicating those fields in the
Restaurant model, you could make
Restaurant have a OneToOneField to
Place (because a restaurant “is a”
place; in fact, to handle this you’d
typically use inheritance, which
involves an implicit one-to-one
relation).

Normally, you would just have Restaurant inherit from Place. Sadly, you need what I consider a hack: making a one-to-one reference from subclass to superclass (Restaurant to Place)

👤geowa4

5đź‘Ť

One way to do this is to add an intermediate class as follows:

class A(Model):
    class Meta(Model.Meta):
        abstract = True
    # common definitions here

class Target(A):
    # this is the target for links from D - you then need to access the 
    # subclass through ".b" or ".c"
    # (no fields here)

class B(Target):
    # additional fields here

class C(Target):
    # additional fields here        

class D(A):
    b_or_c = ForeignKey(Target)
    def resolve_target(self):
        # this does the work for you in testing for whether it is linked 
        # to a b or c instance
        try:
            return self.b_or_c.b
        except B.DoesNotExist:
            return self.b_or_c.c

Using an intermediate class (Target) guarantees that there will only be one link from D to either B or C. Does that make sense? See model inheritance for more information.

In your database there will be tables for Target, B, C and D, but not A, because that was marked as abstract (instead, columns related to attributes on A will be present in Target and D).

[Warning: I have not actually tried this code – any corrections welcome!]

👤andrew cooke

4đź‘Ť

You could also do a generic relation http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1 and check the types to constrain it to B or C when setting or saving. This is probably more work than figuring out the direct reference, but might feel cleaner.

👤dar

2đź‘Ť

I see a problem here:

class D(A):
    #D has foreign key to either B or C, but not both.

Can’t do it. You’ll have to add both because in SQL columns must be defined exactly.

Also even though inherited models like you have compile with syncdb – they don’t seem to behave like you would expect – at least I could not make them work. I can’t explain why.

This is how FK works in Django

class A(models.Model):
    a = models.CharField(max_length=5)

class B(models.Model):
    a = model.ForeignKey(A, related_name='A')
    b = models.CharField(max_length=5)

class D(models.Model):
    a = model.ForeignKey(A, related_name='A')
    parent = model.ForeignKey(B, related_name='D')

this way you can effectively have multiples of D in B.

Inheritance in models (e.g. class B(A)) doesn’t work as I would expect it. Maybe someone else can explain it better.

Take a look at this doc page. It’s about many-to-one relationship in django.

b = B()
b.D_set.create(...)
👤Evgeny

Leave a comment