django abstract models versus regular inheritance
Asked Answered
K

6

107

Besides the syntax, what's the difference between using a django abstract model and using plain Python inheritance with django models? Pros and cons?

UPDATE: I think my question was misunderstood and I received responses for the difference between an abstract model and a class that inherits from django.db.models.Model. I actually want to know the difference between a model class that inherits from a django abstract class (Meta: abstract = True) and a plain Python class that inherits from say, 'object' (and not models.Model).

Here is an example:

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...
Keramics answered 20/5, 2013 at 17:40 Comment(1)
This is a great overview of the trade-offs between using the two inheritance approaches charlesleifer.com/blog/django-patterns-model-inheritancePredate
H
179

I actually want to know the difference between a model class that inherits from a django abstract class (Meta: abstract = True) and a plain Python class that inherits from say, 'object' (and not models.Model).

Django will only generate tables for subclasses of models.Model, so the former...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

...will cause a single table to be generated, along the lines of...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

...whereas the latter...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

...won't cause any tables to be generated.

You could use multiple inheritance to do something like this...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

...which would create a table, but it will ignore the fields defined in the User class, so you'll end up with a table like this...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Hyperemia answered 30/5, 2013 at 14:19 Comment(4)
Thanks, this answers my question. It's still unclear as to why first_name does not get created for Employee.Keramics
@Keramics You'd have to check Django's source code, but IIRC it uses some metaclass hackery in models.Model, so fields defined in the superclass won't be picked up (unless they're also subclasses of models.Model).Hyperemia
@Keramics I know this was 7 years ago when you asked, but in ModelBase's __new__ call, it performs an initial check of the class' attributes, checking if the attribute value has a contribute_to_class attribute - the Fields that you define in Django all do, so they are included in a special dictionary called contributable_attrs that are ultimately passed along during migration to generate the DDL.Pellet
EVERY TIME I LOOK AT DJANGO I just want to cry, why????? why all the nonsense, why the framework has to behave in a completely different way than the language? What about the principle of least astonishment? This behaviour (like ALMOST everything else in django) is not what anyone that understand python would expect.Petras
N
43

An abstract model creates a table with the entire set of columns for each subchild, whereas using "plain" Python inheritance creates a set of linked tables (aka "multi-table inheritance"). Consider the case in which you have two models:

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

If Vehicle is an abstract model, you'll have a single table:

app_car:
| id | num_wheels | make | year

However, if you use plain Python inheritance, you'll have two tables:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Where vehicle_id is a link to a row in app_vehicle that would also have the number of wheels for the car.

Now, Django will put this together nicely in object form so you can access num_wheels as an attribute on Car, but the underlying representation in the database will be different.


Update

To address your updated question, the difference between inheriting from a Django abstract class and inheriting from Python's object is that the former is treated as a database object (so tables for it are synced to the database) and it has the behavior of a Model. Inheriting from a plain Python object gives the class (and its subclasses) none of those qualities.

Nyctophobia answered 20/5, 2013 at 17:55 Comment(6)
Doing models.OneToOneField(Vehicle) would also be equivalent to inheriting a model class, right? And that would result in two separate tables, isn't it?Erase
@MohammadRafayAleem: Yes, but when using inheritance, Django will make, e.g., num_wheels an attribute on a car, whereas with a OneToOneField you'll have to do that dereferencing yourself.Nyctophobia
How can I force with regular inheritance that all fields are re-generated for app_car table so that it has also the num_wheels field, instead of having the vehicle_id pointer?Pinchas
@DorinGrecu: Make the base class an abstract model, and inherit from that.Nyctophobia
@Nyctophobia the problem is that I cannot make the base class abstract, it's being used all over the project.Pinchas
@DorinGrecu: In that case, subclasses will have to link back to the parent. This is handled transparently by Django, though.Nyctophobia
A
25

The main difference is how the databases tables for the models are created. If you use inheritance without abstract = True Django will create a separate table for both the parent and the child model which hold the fields defined in each model.

If you use abstract = True for the base class Django will only create a table for the classes that inherit from the base class - no matter if the fields are defined in the base class or the inheriting class.

Pros and cons depend on the architecture of your application. Given the following example models:

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

If the Publishable class is not abstract Django will create a table for publishables with the columns title and date and separate tables for BlogEntry and Image. The advantage of this solution would be that you are able to query across all publishables for fields defined in the base model, no matter if they are blog entries or images. But therefore Django will have to do joins if you e.g. do queries for images... If making Publishable abstract = True Django will not create a table for Publishable, but only for blog entries and images, containing all fields (also the inherited ones). This would be handy because no joins would be needed to an operation such as get.

Also see Django's documentation on model inheritance.

Agueweed answered 20/5, 2013 at 17:59 Comment(1)
This is the best answer that covers both abstract = True and abstract = FalseFullrigged
K
10

Just wanted to add something which I haven't seen in other answers.

Unlike with python classes, field name hiding is not permited with model inheritance.

For example, I have experimented issues with an use case as follows:

I had a model inheriting from django's auth PermissionMixin:

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

Then I had my mixin which among other things I wanted it to override the related_name of the groups field. So it was more or less like this:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

I was using this 2 mixins as follows:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

So yeah, I expected this to work but it didn't. But the issue was more serious because the error I was getting wasn't pointing to the models at all, I had no idea of what was going wrong.

While trying to solve this I randomly decided to change my mixin and convert it to an abstract model mixin. The error changed to this:

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

As you can see, this error does explain what is going on.

This was a huge difference, in my opinion :)

Kingdon answered 1/6, 2013 at 13:26 Comment(3)
I have a question not related to the main question here, but how did you managed to implement this (to override the related_name of the groups field) at the end ?Helenhelena
@Helenhelena IIRC I just changed the name to something else entirely. There is just no way to remove or replace inherited fields.Tack
Yes I was almost ready to give up on this, when I found a way to do it using the contribute_to_class method.Helenhelena
F
2

The main difference is when you inherit the User class. One version will behave like a simple class, and the other will behave like a Django modeel.

If you inherit the base "object" version, your Employee class will just be a standard class, and first_name won't become part of a database table. You can't create a form or use any other Django features with it.

If you inherit the models.Model version, your Employee class will have all the methods of a Django Model, and it will inherit the first_name field as a database field that can be used in a form.

According to the documentation, an Abstract Model "provides a way to factor out common information at the Python level, whilst still only creating one database table per child model at the database level."

Fabric answered 1/6, 2013 at 0:43 Comment(0)
B
2

I will prefer the abstract class in most of the cases because it does not create a separate table and the ORM does not need to create joins in the database. And using abstract class is pretty simple in Django

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
Bechtold answered 8/11, 2020 at 21:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.