Django Model - Cost of inheritance - python

We have django project, and we found that some models become huge.
class BigModel(models.Model):
"""
Large set of fields
"""
field1 = models.IntegerField()
field2 = models.IntegerField()
field3 = models.IntegerField()
...
fieldN = models.IntegerField()
"""
Large set of methods
"""
def method1(self): pass
def method2(self): pass
def method3(self): pass
...
def methodN(self): pass
I want to divide BigModel class into smaller classes with list of methods. But in whole project we have references to BigModel class.
So my idea is to do it by small steps:
Divide BigModel class into BigFields and BigMethods. Inherit
BigMethods from BigFields. Inherit BigModel from BigMethods.
By creating proxy models and replacing references to BigModel with them in code - reduce size of BigMethods class.
So, at the moment of refactoring our code will look like this:
class BigFields(models.Model):
class Meta:
abstract = True
"""
Large set of fields
"""
field1 = models.IntegerField()
field2 = models.IntegerField()
field3 = models.IntegerField()
...
fieldN = models.IntegerField()
class BigMethods(BigFields):
class Meta:
abstract = True
"""
Large set of methods
"""
def method1(self): pass
def method2(self): pass
def method3(self): pass
...
def methodN(self): pass
class BigModel(BigMethods):
pass
How it will affect performance?
What is the cost of one level of inheritance in python?
Does metaclasses affects cost of inheritance?

If you have sequential fields like this in your model the solution is not inheritance, but breaking out these fields into a separate model and creating a one-to-many relationship. It's hard to make the point with your example models, so I'll use one from a project I was working on.
The original model looked something like this:
class Page(models.Model):
title = models.CharField(max_length=256)
section_1_title = models.CharField(max_length=256)
section_1_content = models.TextField()
section_2_title = models.CharField(max_length=256)
section_2_content = models.TextField()
section_3_title = models.CharField(max_length=256)
section_3_content = models.TextField()
...
Obviously this was a nightmare to maintain, so I changed it to the following:
class Page(models.Model):
title = models.CharField(max_length=256)
class Section(models.Model):
page = models.ForeignKey(Page, related_name='sections')
title = models.CharField(max_length=256)
content = models.TextField()
order = models.PositiveIntegerField()
class Meta:
ordering = ['order']
order_with_respect_to = 'page'

Related

Django: How to create a dynamic related_name for an inherited parent model?

I have 4 models:
class User(models.Model):
name = models.CharField(max_length=255)
class A(models.Model):
user= models.ForeignKey("User", related_name="u_a", on_delete=models.CASCADE)
title = models.CharField(max_length=255)
class B(A):
user= models.ForeignKey("User", related_name="u_b", on_delete=models.CASCADE)
#isn't the code repeated???
b_field = CharField(max_length=255)
class C(A):
user= models.ForeignKey("User", related_name="u_c", on_delete=models.CASCADE)
#isn't the code repeated???
c_field = CharField(max_length=255)
Here, A has a ForeignKey relationsip with User and a reverse relationship as u_a. But B and C are children of A.
So It appears to me as if Do not repeat your code is violated. How to overcome this?
To work around this problem, In your model class A(models.Model) The part of the value should contain '%(app_label)s' and/or '%(class)s'. see the doc
'%(class)s' is replaced by the lower-cased name of the child class that the field is used in.
'%(app_label)s' is replaced by the lower-cased name of the app the child class is contained within. Each installed application name must be unique and the model class names within each app must also be unique, therefore the resulting name will end up being different.
class A(models.Model):
user= models.ForeignKey("User", related_name="%(class)s_set",
on_delete=models.CASCADE)
#user= models.ForeignKey("User", related_name="%(app_label)s_%(class)s_set",
#on_delete=models.CASCADE)
title = models.CharField(max_length=255)
class B(A):
b_field = CharField(max_length=255)
class C(A):
c_field = CharField(max_length=255)

Django - How do I create a model that contains a collection of its own type?

One of the fields in a model I'm creating is for a list of instances of its own type. How do I do this in django? I couldn't find any documentation on how to do this..
This is something like what I am talking about, but doesn't work because the Component class isn't defined yet (and probably for other reasons too).
class Component(models.Model):
name = models.CharField()
description = models.CharField()
status_ok = models.BooleanField()
subcomponents = models.ForeignKey(Component)
A regular class that briefly demonstrates the concept:
class Component:
def __init__(self, name, description, status_ok, *subcomponents):
self.name = name
self.description = description
self.status_ok = status_ok
self.subcomponents = []
for subcomponent in subcomponents:
if isinstance(subcomponent, Component):
self.subcomponents.append(subcomponent)
else:
raise TypeError(subcomponent)
To reference the same model use the normal Python syntax self but as a string,
Class Component(models.Model):
name = models.CharField()
description = models.CharField()
status_ok = models.BooleanField()
subcomponents = models.ForeignKey('self')

Django rest framework - how to serialise the model class name base serialiser?

I have a BaseModel class:
class BaseModel(models.Model):
title = models.CharField(max_length=250, blank=True, null=True)
class Meta:
abstract = True
Then I have mutliple model classes that extend such class e.g.:
class Article(BaseModel):
slug = models.SlugField(max_length=250, default=timezone.now, unique=True)
My goal is to have a field in a JSON object returned through my webservices to indicate the type of the object (so that the client applications can easily tell an Article from a e-commerce Product). Something like the following:
{
"id": 1,
"object_type: "article",
"title": "some article",
"slug": "some-article"
}
I imagine the there could be a BaseModelSerializer class similar to the following:
class BaseModelSerializer(serializers.ModelSerializer):
object_type = self.__class__.__name__ # ??? how to get the name/ label of the child class?
Then I can have a ArticleSerializer extending the BaseModelSerializer like the following:
class ArticleSerializer(BaseModelSerializer):
class Meta:
model = Article
I would be happy if this could be achieved through modifying the BaseModel class too. Something like the following?
class BaseModel(models.Model):
title = models.CharField(max_length=250, blank=True, null=True)
object_type = self.__class__.__name__ # ??? how to get the name/ label of the child class?
class Meta:
abstract = True
Use SerializerMethodField.
class BaseModelSerializer(serializers.ModelSerializer):
object_type = serializers.SerializerMethodField()
def get_object_type(obj):
return obj.__class__.__name__.lower()
class ArticleSerializer(BaseModelSerializer):
class Meta:
model = Article
fields = ('object_type',)

Django Inheriting from classes

I have run into a problem developing my Django site.
from django.db import models
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
def __unicode__(self):
return self.title_name
class TopTitlesPostPage(models.Model):
title_post = models.OneToOneField(TitlePost)
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
class NewTitlesPostPage(models.Model):
title_post = models.OneToOneField(TitlePost)
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
Why don't TopTitlesPostPage and NewTitlesPostPage inherit all the attributes from TitlePost? For instance, if I try to call the likes in my template using TopTitlesPostPage, it will not execute because the likes attribute is not inherited. Does OneToOneField have something to do with the problem? I did read that making TitlePost a meta class will help but I need it to have a table in my database. I actually want all of them to have a table in my data base. Then again, maybe I am approaching this the wrong way and I should use just TitlePost as a model to generate everything?
The behaviour you would like to see is called multi table inheritance. Every child class internally ends up with the same thing that you wrote, so with a one to one field to the base class TitlePost, but it's internally managed by django.
If you do multiple inheritance like the code below you will be able to write:
k=TopTitlesPostPage.objects.create(hello="Hello",title_name="Heh")
That means the fields will be directly accessible.
from django.db import models
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
def __unicode__(self):
return self.title_name
class TopTitlesPostPage(TitlePost):
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
class NewTitlesPostPage(TitlePost):
hello = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.hello
In case you are never actually going to reference the base class TitlePost, but only its children it might be more appropriate to make `TitlePost abstract:
class TitlePost(models.Model):
title_name = models.CharField(max_length=100, unique=True)
title_body = models.TextField(max_length=30000)
title_why = models.TextField(max_length=250, null=True)
title_publication_date = models.DateTimeField('date')
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
class Meta:
abstract = True
def __unicode__(self):
return self.title_name
Making TitlePostabstract will omit the creation of the table TitlePostin the database, and the child models will end up with the fields of the base class inserted into their own tables seperately. If the base class is just for factoring out common functionality this is the preferred way to go.
For huge queries this will also make a difference in performance because the ORM will need to do less JOINoperations.
It's not possible to install Foreign Keys to abstract models in Django.
You can however install Foreign Keys to a non abstract base class. The only limitation is that the reverse Foreign Key relation will return the base class instances.
You can circumvent this limitation by using django-polymorphic.
Django Polymorphic allows you to query the base class objects but retrieves the child class instances:
>>> Project.objects.create(topic="Department Party")
>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
>>> Project.objects.all()
[ <Project: id 1, topic "Department Party">,
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
To use django polymorphic you only need to declare your models with Polymorphic Model as base class:
from django.db import models
from polymorphic import PolymorphicModel
class ModelA(PolymorphicModel):
field1 = models.CharField(max_length=10)
class ModelB(ModelA):
field2 = models.CharField(max_length=10)
class ModelC(ModelB):
field3 = models.CharField(max_length=10)
Foreign keys will also return the child class instances, which is really cool if you're trying to be polymorphic.
# The model holding the relation may be any kind of model, polymorphic or not
class RelatingModel(models.Model):
many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model
>>> o=RelatingModel.objects.create()
>>> o.many2many.add(ModelA.objects.get(id=1))
>>> o.many2many.add(ModelB.objects.get(id=2))
>>> o.many2many.add(ModelC.objects.get(id=3))
>>> o.many2many.all()
[ <ModelA: id 1, field1 (CharField)>,
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Take into account that these queries will be slightly less performant.
U need to extend the classes like follows:
class TopTitlesPostPage(TitlePost):
U can add more and inherit from multiple models just by mentionin g all the models comma separated! This all the fields from the models will be created in the child class as well
EDIT:
The way i would do it is to create an Abstract class which contains all your common fields and extend it into your TitlePost, TopTitlesPostPagea and NewTitlesPostPage
You need to have TopTitlesPostPage and NewTitlesPostPage extend the base class of TitlePost like so ...
class TopTitlesPostPage(models.Model)
You don't need a OneToOneField if you are inheriting from the base class, since the attributes of TitlePost will be available to you in the subclass. If you want to make TitlePost abstract (you can not declare an instance of that class, only inherit from it) you have to add it to the meta class
class TitlePost(models.Model):
class Meta:
abstract = True
Here is a link to the documentation.

Is it possible/sensible to customize Django's auto-through tables to include other fields...through-factory?

I know that django will helpfully generate a through table for simple many-to-many tables. However if you want to add information about the relationship, you need a 'linker' or through table. This example from the docs gives an example:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
I have in my design several tables/models like group, that vary mostly just by their choices attribute. And I'll be adding more later.
Is it possible to customize what sort of through table is generated by Django's magic? If so is this sensible?
The sort of thing I'm talking about is this:
class CharacterSkillLink(models.Model):
character = models.ForeignKey('NWODCharacter', related_name='%(class)s_by_skill')
skill = models.ForeignKey('Skill', choices = SKILL_CHOICES)
value = models.IntegerRangeField(min_value=1, max_value=5)
speciality = models.CharField(max_length=200)
class CharacterAttributeLink(models.Model):
character = models.ForeignKey('NWODCharacter', related_name='%(class)s_by_skill')
attribute = models.ForeignKey('Attribute', choices = ATTRIBUTE_CHOICES)
value = model.IntegerRangeField(min_value=1, max_value=5
class CharacterArcanaLink(models.Model):
character = models.ForeignKey('Mage', related_name='%(class)s_by_skill')
arcana = models.ForeignKey('Arcana', choices = ARCANA_CHOICES)
value = model.IntegerRangeField(min_value=1, max_value=5
In the future there'll be more like these. It's be handy if there was some way to django, much like with the through_field attribute, which defines keys on the through table to use, that there should be extra values to add to it (e.g. extra_field=value).
Is this possible/sensible?
IMO, adding fields to a "through" table is a great pattern for many possible uses. I'm not sure that Django needs new syntax to handle this case, but if you think you're creating lots of these tables and mixing/matching different tables, perhaps some abstract mixins will simplify things. For Example:
class CharacterLink(models.Model):
character = models.ForeignKey('NWODCharacter')
class Meta:
abstract = True
class SkillLink(models.Model):
skill = models.ForeignKey('Skill', choices = SKILL_CHOICES)
class Meta:
abstract = True
class AttributeLink(models.Model):
attribute = models.ForeignKey('Attribute', choices = ATTRIBUTE_CHOICES)
class Meta:
abstract = True
class CharacterSkillLink(CharacterLink, SkillLink):
value = models.IntegerRangeField(min_value=1, max_value=5)
speciality = models.CharField(max_length=200)
class CharacterAttributeLink(CharacterLink, AttributeLink):
value = model.IntegerRangeField(min_value=1, max_value=5)

Categories

Resources