18

What is the differences between these implementations? What does Django differently (beside inheriting Meta ordering and get_latest_by attribute) ?

1.

# models.py
from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(models.Model):
    place = models.OneToOneField(Place)
    serves_pizza = models.BooleanField()

2.

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    serves_pizza = models.BooleanField()

3.

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    place = models.OneToOneField(Place, parent_link=True)
    serves_pizza = models.BooleanField()
kissgyorgy
  • 2,947
  • 2
  • 32
  • 54
  • I want to extend the built in sites framework Site model with extra fields and trying to decide which method is best. – kissgyorgy Aug 18 '13 at 07:10

2 Answers2

20

1. You don't really get any python inheritance, that is you can't inherit/override methods or attributes from the model class Place in your class Restaurant:

For instance:

class Place(models.Model):
    name = models.CharField(max_length=50)

    def get_x(self):
        return 'x'

class Restaurant(models.Model):
    place = models.OneToOneField(Place)
    serves_pizza = models.BooleanField()

a_restaurant = Restaurant()
a_restaurant.get_x() # -> wouldn't work

This means that to obtain the name of a restaurant you can't do a_restaurant.name, you would need to follow the link: a_restaurant.place.name

Also note that when querying a Place object with the related Restaurant.

a_restaurant.save()
Place.objects.get(pk=a_restaurant.pk)  # won't work

You would have to write:

a_restaurant.save()
Place.objects.get(restaurant__pk=a_restaurant.pk)

2 and 3. are almost the same. You do get real python inheritance with these.

a_restaurant = Restaurant()
a_restaurant.get_x() # would actually work and print 'x'

Your model class Restaurant inherits everything from Place: model fields, normal instance/class attributes, managers, methods... and you can also override almost anything of these: You can't override field attributes, that's not supported.

So now you can get the values of the fields from the parent model directly:a_restaurant.name because they are inherited.

Since with these implementation a Restaurant is a also Place you can query for a Place object with Restaurant data:

a_restaurant.save()
the_place = Place.objects.get(pk=a_restaurant.pk)  
# ^ this works now and returns the equivalent `Place` instance.
the_same_restaurant = the_place.restaurant

The difference between 2 and 3 is easier to see if you give a different name to the field:

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    where = models.OneToOneField(Place, parent_link=True)
    serves_pizza = models.BooleanField()

Works exactly the same but to obtain the parent place of a Restaurant the attribute name is where:

the_place = a_restaurant.where

with 2 would have been:

the_place = a_restaurant.place_ptr

These means that place = models.OneToOneField(Place, parent_link=True) will only change the name of the link to the parent model instance. The default name is '{lowercase_model_name}_ptr'.


Last example:

With 1:

place1 = Place.objects.create(name='place_1')
place2 = Place.objects.create(name='place_2')
restaurant1 = Restaurant.objects.create(place=place1, serves_pizza=True)

print Place.objects.all() # prints [place1, place2]
print Restaurant.objects.all() # prints [restaurant1]

With 2-3:

place1 = Place.objects.create(name='place_1')
place2 = Place.objects.create(name='place_2')
restaurant1 = Restaurant.objects.create(name='place_3', serves_pizza=True)

print Place.objects.all() # prints [place1, place2, place3]
print Restaurant.objects.all() # prints [restaurant1]

Hope these helps. It grow a bit too long :/

Adrián
  • 6,135
  • 1
  • 27
  • 49
  • My personal opinion: the 2º option seems the best implementation for this example because a `Restaurant` is indeed a `Place`. – Adrián Aug 18 '13 at 00:31
  • very nice explanation, I didn't expected this long :) thank you! – kissgyorgy Aug 18 '13 at 07:39
  • 1
    One notice: Default name for the OneToOneField from the child model is `objectname.parentobject_ptr` (not objectname.parentobject) in Django 1.5. So you can query the Place with `a_restaurant.place_ptr` in #2. But the other way around (query from place) it's: `a_place.restaurant` – kissgyorgy Aug 19 '13 at 07:14
  • @Adrian You would have to write: a_restaurant.save() Place.objects.get(restaurant__pk=a_restaurant.pk) I don't understand how we can write restaurant__pk if Restaurants derive from/contain places, and not the other way round. Thanks – dowjones123 Jul 10 '14 at 13:32
  • @dowjones123 Django can follow (and query) relationships backwards. Have a look at https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward , https://docs.djangoproject.com/en/dev/topics/db/queries/#one-to-one-relationships and specially https://docs.djangoproject.com/en/dev/topics/db/queries/#how-are-the-backward-relationships-possible – Adrián Jul 10 '14 at 15:15
  • @Adrián This is great. Perfect explanation that I was looking for. Though I do not understand any usecase/requirement of 3rd option. The main thing to be learned is "when you extend a model and get it's objects, the returned list will also contain all the child objects". – Kadam Parikh Jul 17 '21 at 09:22
3

1 - to create restaurant you need to create place, after create restaurant, after link them, 2 - then creating restaurant, new place created and linked automaticaly, 3 - you renamed parent link to place.

Using Model Inheriting with Content Types you can list all Cafes, Restaurants, Bars, etc iterating on Place.objects.all()

eri
  • 3,133
  • 1
  • 23
  • 35