Is there explicit support for Single Table Inheritance in Django? Last I heard, the feature was still under development and debate.
Are there libraries/hacks I can use in the meantime to capture the basic behavior? I have a hierarchy that mixes different objects. The canonical example of a corporation structure with an Employee class, subclasses for types of employees, and a manager_id (parent_id) would be a good approximation of the problem I am solving.
In my case, I would like to represent the idea that an employee can manage other employees while being managed by a different employee. There are not separate classes for Manager and Worker, which makes this hard to spread across tables. Sub-classes would represent types of employees-programmers, accountants, sales, etc and would be independent of who supervises who (OK, I guess it's no longer a typical corporation in some respect).
Summary
Django's proxy models provide the basis for Single Table Inheritance.
However, some effort is required to make it work.
Skip to the end for a re-usable example.
Background
Martin Fowler describes Single Table Inheritance (STI) as follows:
Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
This is precisely what Django's proxy model inheritance does.
Note, that, according to this blog post from 2010, proxy models have been around since Django 1.1.
A "normal" Django model is a concrete model, i.e. it has a dedicated table in the database.
There are two types of Django model that do not have dedicated database tables, viz. abstract models and proxy models:
Abstract models act as superclasses for concrete models. An abstract model can define fields, but it does not have a database table. The fields are only added to the database tables for its concrete subclasses.
Proxy models act as subclasses for concrete models. A proxy model cannot define new fields. Instead, it operates on the database table associated with its concrete superclass. In other words, a Django concrete model and its proxies all share a single table.
Django's proxy models provide the basis for Single Table Inheritance, viz. they allow different models to share a single table, and they allow us to define proxy-specific behavior on the Python side. However, Django's default object-relational mapping (ORM) does not provide all the behavior that would be expected, so a little customization is required. How much, that depends on your needs.
Let's build a minimal example, step by step, based on the simple data-model in the figure below:
Step 1: basic "proxy model inheritance"
Here's the content of models.py for a basic proxy inheritance implementation:
from django.db import models
class Party(models.Model):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
class Organization(Party):
class Meta:
proxy = True
Person and Organization are two types of parties.
Only the Party model has a database table, so all the fields are defined on this model, including any fields that are specific either to Person or to Organization.
Because Party, Person, and Organization all use the Party database table, we can define a single ForeignKey field to Party, and assign instances of any of the three models to that field, as implied by the inheritance relation in the figure. Note, that, without inheritance, we would need a separate ForeignKey field for each model.
For example, suppose we define an Address model as follows:
class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
We can then initialize an Address object using e.g. Address(party=person_instance) or Address(party=organization_instance).
So far, so good.
However, if we try to get a list of objects corresponding to a proxy model, using e.g. Person.objects.all(), we get a list of all Party objects instead, i.e. both Person objects and Organization objects. This is because the proxy models still use the model manager from the superclass (i.e. Party).
Step 2: add proxy model managers
To make sure that Person.objects.all() only returns Person objects, we need to assign a separate model manager that filters the Party queryset. To enable this filtering, we need a field that indicates which proxy model should be used for the object.
To be clear: creating a Person object implies adding a row to the Party table. The same goes for Organization. To distinguish between the two, we need a column to indicate if a row represents a Person or an Organization. For convenience and clarity, we add a field (i.e. column) called proxy_name, and use that to store the name of the proxy class.
So, enter the ProxyManager model manager and the proxy_name field:
from django.db import models
class ProxyManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(proxy_name=self.model.__name__)
class Party(models.Model):
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
Now the queryset returned by Person.objects.all() will only contain Person objects (and the same for Organization).
However, this does not work in the case of a ForeignKey relation to Party, as in Address.party above, because that will always return a Party instance, regardless of the value of the proxy_name field (also see docs). For example, suppose we create an address = Address(party=person_instance), then address.party will return a Party instance, instead of a Person instance.
Step 3: extend the Party constructor
One way to deal with the related-field issue is to extend the Party.__new__ method, so it returns an instance of the class specified in the 'proxy_name' field. The end result looks like this:
class Party(models.Model):
PROXY_FIELD_NAME = 'proxy_name'
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
party_class = cls
try:
# get proxy name, either from kwargs or from args
proxy_name = kwargs.get(cls.PROXY_FIELD_NAME)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(cls.PROXY_FIELD_NAME))
proxy_name = args[proxy_name_field_index]
# get proxy class, by name, from current module
party_class = getattr(sys.modules[__name__], proxy_name)
finally:
return super().__new__(party_class)
Now address.party will actually return a Person instance if the proxy_name field is Person.
As a last step, we can make the whole thing re-usable:
Step 4: make it re-usable
To make our rudimentary Single-Table Inheritance implementation re-usable, we can use Django's abstract inheritance:
inheritance/models.py:
import sys
from django.db import models
class ProxySuper(models.Model):
class Meta:
abstract = True
proxy_name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
""" create an instance corresponding to the proxy_name """
proxy_class = cls
try:
field_name = ProxySuper._meta.get_fields()[0].name
proxy_name = kwargs.get(field_name)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(field_name))
proxy_name = args[proxy_name_field_index]
proxy_class = getattr(sys.modules[cls.__module__], proxy_name)
finally:
return super().__new__(proxy_class)
class ProxyManager(models.Manager):
def get_queryset(self):
""" only include objects in queryset matching current proxy class """
return super().get_queryset().filter(proxy_name=self.model.__name__)
Then we can implement our inheritance structure as follows:
parties/models.py:
from django.db import models
from inheritance.models import ProxySuper, ProxyManager
class Party(ProxySuper):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Placement(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
More work may be required, depending on your needs, but I believe this covers some of the basics.
I think the OP is asking about Single-Table Inheritance as defined here:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
That is, a single database table for a whole hierarchy of entity classes. Django does not support that kind of inheritance.
There are currently two forms of inheritance in Django - MTI (model table inheritance) and ABC (abstract base classes).
I wrote a tutorial on what's going on under the hood.
You can also reference the official docs on model inheritance.
See my attempt:
http://djangosnippets.org/snippets/2408/
An emulation of "table per hierarchy" a.k.a. "single table inheritance" in Django. The base class must hold all the fields. It's subclasses are not allowed to contain any additional fields and optimally they should be proxies.
Not exactly "single table inheritance", but close enough for many situations.
this might be of use: https://github.com/craigds/django-typed-models
It looks to be somewhat of an implementation of Single Table Inheritance but it has the limitation that subclasses can't have any extra fields.
here is a recent discussion on the django developer mailing list about STI:
https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ
I think you can do something akin to this.
I have to implement a solution for this problem myself, and here was how I solved it:
class Citrus(models.Model):
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
class TangeloManager(models.Manager):
def get_query_set(self):
return super(TangeloManager, self).get_query_set().filter(type='Tangelo')
class Tangelo(models.Model):
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
objects = TangeloManager()
class Meta:
# 'appname' below is going to vary with the name of your app
db_table = u'appname_citrus'
This may have some locking issues... I'm not really sure how django handles that off the top of my head. Also, I didn't really test the above code, it's strictly for entertainment purposes, to hopefully put you on the right track.
Related
I want to create 73 different django models, those models will be very similar, so in theory I would inherit from a base model class and just change the name of the Model/table.
I am aware this is not the best database structure, however I believe this unconventional structure may improve other aspects of my application. The initial point is to test this hypothesis.
How can I have django create the models, without me having to define all 73 of them?
class BaseModel(models.Model):
some_field = models.CharField(max_length=255)
some_other_field = models.CharField(max_length=255)
class Model_NR_01(BaseModel):
pass
...
class Model_NR_73(BaseModel):
pass
Also, in the sample above, I believe the BaseModel would also be created. How could I prevent that, having at the end of the migration only the 73 models mentioned? (If possible, of course).
PS.: I did searched several similar questions, couldn't find an actual answer, only warnings of how bad design it is. I am aware.
The three argument form of type can be used to create classes dynamically. The only thing you need to pass in the attributes dictionary is __module__ as using type this way to create a class will not automatically populate this attribute which is required by Django
class BaseModel(models.Model):
some_field = models.CharField(max_length=255)
some_other_field = models.CharField(max_length=255)
class Meta:
abstract = True
for i in range(1, 74):
model_name = f'Model_NR_{i:02}'
globals()[model_name] = type(model_name, (BaseModel, ), {'__module__': BaseModel.__module__})
I have to models
class Parent(object):
text_field = models.TextField()
boolean_field = models.BooleanField()
class Child(Parent):
another_text_field = models.TextField()
With the following ModelAdmin
class ChildAdmin(admin.ModelAdmin):
pass
admin.site.register(Child, ChildAdmin)
I currently see all fields in the admin page, i.e. text_field, boolean_field, and another_text_field.
Question: How can I get a parent select field and exclude text_field and boolean_field (for latter I guess I can use exclude).
Current Solution: I add a Form and use its clean method to set the parent field. text_field and boolean_field can be excluded by addind it to the ModelAdmin's excluded variable.
simply overwrite fields
class Child(Parent):
another_text_field = models.TextField()
text_field = None
boolean_field = None
if you want to use inheritance in django models use abstract models please.
I am not sure if it is really necessary to use model inheritance. if not, you may consider using OneToOneField without model inheritance.
Example using OneToOneField:
models.py
class Parent(models.Model):
text_field = models.TextField()
boolean_field = models.BooleanField()
class Child(models.Model):
parent = models.OneToOneField(Parent,
on_delete=models.CASCADE,
primary_key=True)
child_field = models.TextField()
admin.py
#admin.register(Parent)
class ParentAdmin(admin.ModelAdmin):
pass
doing so you can see a drop down menu for picking Parent instance at child admin page. but meanwhile, you lose one 'benefit' of using inheritance, which is the availability of Parent field in Child
as mentioned in the docs,
All of the fields of Place will also be available in Restaurant,
although the data will reside in a different database table.
but there is a easy fix for that, just use something like Child.objects.filter(parent__text_field="something"). Query performance should be the same (I guess) since implementation in db are basically the same for these two approaches (both use separated table) (please correct if I am wrong)
Apart from from this and admin display behavior, I am not sure how these two approaches (your approach and this answer) are differed.
I'm trying to alter an app I've created so that it is reusable. It's based around a single model which sites using the app will subclass. As it stands, my non-reusable version has the following kind of structure:
# models.py
class Document(models.Model):
contents = models.TextField()
date = models.DateTimeField()
# views.py
from .models import SiteModel
# ...
class MyView(ListView):
def some_method(self, list_of_pks):
model_vals = Document.objects.filter(pk__in = list_of_pks).values()
def perform_action(request):
obj_pk = request.POST.get('obj_pk')
obj = Document.objects.filter(pk = obj_pk)
MySignal.send(sender=Document, instance = obj)
#etc, etc
This works well enough. But my use case calls for different types of Document, one per site, that will have additional fields that aren't known in advance. Based on reading the documentation on abstract base classes, I thought the a reasonable solution would look like:
# models.py for the app
class BaseDocument(models.Model):
contents = models.TextField()
class Meta:
abstract = True
# models.py for a hypothetical site using the app
class SiteDocument(myapp.BaseDocument):
date = models.DateTimeField()
# other site-specific fields
What I don't understand is how to then reference the model in the app's views.py, forms.py, etc. I know BaseDocument.objects.all(), for example, won't return anything since it isn't connected to a database. Conversely, I can't have Document.objects.all() because Document hasn't been created yet and is specific to each site. Is an abstract base class not the correct solution, and if so, what is?
Edit:
It looks like using a OneToOneField may be best suited to my use case, although it looks like that precludes inheriting methods from the superclass and that BaseDocument.objects.all() won't list out all its children.
Alternatively, I was wondering if I could just add a get_document_model() method to my abstract base class, in the style of get_user_model()?
You can't query your abstract classes directly like that since they won't have managers, only the inherited classes. If you really must do inheritance, you can use a concrete base model and inherit from that at the cost of a join on every query.
Think long and hard about whether this is truly necessary, or if you can represent your data in a more generic way. Models make inheritance seem easy, but they're not magic. There are very real performance and complexity considerations.
It might be as easy as adding a type field to your model
class Document(models.Model):
DOCUMENT_TYPES = ['site', 'another', 'whatever']
document_type = models.CharField(choices=DOCUMENT_TYPES)
...
For more information about abstract vs concrete classes and querying, visit How to query abstract-class-based objects in Django?
I ended up going with a solution mentioned in my edit, namely creating a get_document_model() method inspired by get_user_model(). This gives me exactly the desired behavior.
# models.py in app1
from django.db import models
from django.apps import apps as django_apps
class BaseDocument(models.Model):
contents = models.TextField()
class Meta:
abstract = True
def get_document_model():
# exception handling removed for concision's sake
return django_apps.get_model(settings.DOCUMENT_MODEL)
# models.py in app2
from django.db import models
from app1.models import BaseDocument
class SiteDocument(BaseDocument):
date = models.DateTimeField()
Throughout views.py and elsewhere, I changed things that would have been of the form Document.objects.all() to BaseDocument().get_document_model().objects.all().
I have a supplied database schema for which I want to create a Django application. Many of the tables in the schema share a common set of columns, such as name and date_created. That prompted me to create an abstract Standard_model class containing those columns, and subclass the relevant models from it.
Unfortunately, some of the tables have a name column with a different max_length. I'm trying to come up with a way for the subclassed model to pass the max_length value to the abstract base class, but I'm drawing a blank.
Any ideas?
class Standard_model(models.Model):
name = models.CharField(max_length=50)
date_created = models.DateTimeField()
class Meta:
abstract = True
class MyModel(Standard_model):
name = models.CharField(max_length=80) # Can't do this.
No, you cannot override the name field definition:
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
See also:
In Django - Model Inheritance - Does it allow you to override a parent model's attribute?
And, FYI, according to the model naming convention, it should be called StandardModel.
In my django project I have 2 variations of users. One subclasses User class from django.auth and second uses almost the same fields but is not a real user (so it doesn't inherit from User). Is there a way to create a FieldUser class (that stores fields only) and for RealUser subclass both FieldUser and User, but for FakeUser subclass only FieldUser ?
sure, I've used multiple inheritance in django models, it works fine.
sounds like you want to setup an abstract class for FieldUser:
class FieldUser(models.Model):
field1 = models.IntegerField()
field2 = models.CharField() #etc
class Meta:
abstract=True #abstract class does not create a db table
class RealUser(FieldUser, auth.User):
pass #abstract nature is not inherited, will create its own table to go with the user table
class FakeUser(FieldUser):
pass #again, will create its own table