14

Basically, I have a model where I've created a superclass that many other classes share, and then each of those classes has some unique features that differ from each other. Let's say class A is the superclass, and class B, C, and D are children of that class.

Both class B and class C can have multiples of class D, however I've seen that it's best to put the foreign key relationship in class D, which then refers to its parent class. Now in other languages, I could simply say it has a ForeignKey relationship to class A, and then the language recognizes the classes' true type. However, I don't think that's how it works with Python.

What's the best recommended way of pursuing this issue?

EDIT: Here is roughly what I mean...

class A(models.Model): field = models.TextField() class B(A): other = <class specific functionality> class C(A): other2 = <different functionality> class D(A): #I would like class D to have a foreign key to either B or C, but not both. 

Essentially, class B and class C both have multiple class D's. But a particular class D only belongs to one of them.

1
  • could you explain why you need inheritance? Commented Jul 11, 2009 at 22:25

4 Answers 4

6

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!]

Sign up to request clarification or add additional context in comments.

1 Comment

Yeah, this seems like a workable solution. Concrete inheritance is definitely one route. GenericForeignKey is another. Both have their pros and cons. For instance, using concrete inheritance, you can list C and D in the same queries. However, you are doing a lot of left joins and will have to Coalesce any values. GenericForeignKey is another solution that is pretty straightforward and readable, but I always regret using them. I will leave it at that.
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.

Comments

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)

2 Comments

Doesn't django provide such a one-to-one reference by default? e.g. for multi-table inherited models, "restaurant.place" returns the corresponding instance of Place, without the need for the user to define any relationships other than the inheritance.
maybe it does now, but i don't think it did in 2009.
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(...) 

1 Comment

My problem is, I would like class D to belong to either a Class A or a Class B and not both. I will clarify in my question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.