'Django models: dynamic inheritance from classes defined in standalone app
I am using Django 3.2
I have written a standalone app social
that has models defined like this:
from abc import abstractmethod
class ActionableModel:
# This MUST be implemented by ALL CONCRETE sub classes
@abstractmethod
def get_actionable_object(self):
pass
# This MUST be implemented by ALL CONCRETE sub classes
@abstractmethod
def get_owner(self):
pass
def get_content_info(self):
actionable_object = self.get_actionable_object()
ct = ContentType.objects.get_for_model(actionable_object)
object_id = actionable_object.id
return (ct, object_id)
# ...
class Likeable(models.Model, ActionableModel):
likes = GenericRelation(Like)
likes_count = models.PositiveIntegerField(default=0, db_index=True)
last_liked = models.DateTimeField(editable=False, blank=True, null=True, default=None)
# ...
in my project (that uses the standalone app), I have the following code:
myproject/userprofile/apps.py
class UserprofileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'userprofile'
verbose_name = _('User Profile')
def ready(self):
# Check if social app is installed
from django.apps import apps
if apps.is_installed("social"):
# check some condition
# inherit from social.Likeable
# ... ?
How can I dynamically get my model userprofile.models.UserAccount
to derive from Likeable?
Solution 1:[1]
If I understand the requirement correctly - you need that models from the application userprofile
to implement Likeable
features.
So the inheritance in this case does not only allow to use implemented methods but also makes it to have the same common fields in database (likes
, likes_count
, etc)
Which means that such an inheritance also requires migration script to be generated (to add the necessary fields in the database). Thus, doing it in runtime when application is ready might be even not possible since you have to have model defined for migrations.
However, even if there is a way to make such "dynamic" inheritance on-the-fly - I would highly recommend not to go this way because it's really not transparent when some features of the models are defined in such a hidden way.
So the good solution for your request is simple - just do not do it dynamically. I.e.:
myproject/userprofile/models.py
from social.models import Likeable
class UserAccount(Likeable):
# the definition of the user account class
And so other models that requires Likeable
features will just inherit from it directly.
To your question of
How can I dynamically get my model
userprofile.models.UserAccount
It can be done via:
from django.apps import apps
app_models = apps.get_app_config('userprofile').get_models()
And as a follow-up for dynamical inheritance you can refer here How to dynamically change base class of instances at runtime?
UPD - a bit different approach to solve the problem
Instead of using inheritance of the classes which is probably even not possible in this case - target models can be extended via relationship with Likeable
entity in database.
I.e. instead of carrying Likeable
inherited - it can be provided as one-to-one relationship which can be constructed in runtime as follows:
class UserprofileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'userprofile'
verbose_name = _('User Profile')
def ready(self):
# Check if social app is installed
from django.apps import apps
from django.db import models
if apps.is_installed("social"):
for obj in apps.get_app_config(self.name).get_models():
# add relationship
obj.add_to_class(
'likeable',
models.OneToOneField(Likeable, on_delete=models.SET_NULL, null=True
)
Then, methods of likeable can be accessed on the models of userprofile
via likeable
field. I.e.:
u = UserAccount.objects.last()
u.likeable.get_content_info()
Solution 2:[2]
To dynamically inherit another class, we usually do:
def make_class(cls, cls_to_inherit):
return type(
cls.__name__,
(cls, cls_to_inherit),
{},
)
To dynamically inherit another Django model that has metaclass=ModelBase
, we do:
def make_model(model, model_to_inherit):
model._meta.abstract = True
if model._meta.pk.auto_created:
model._meta.local_fields.remove(model._meta.pk)
return type(
model.__name__,
(model, model_to_inherit),
{'__module__': model.__module__},
)
Usage:
class UserprofileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'userprofile'
verbose_name = _('User Profile')
def ready(self):
# Check if social app is installed
from django.apps import apps
if apps.is_installed("social"):
# check some condition
# inherit from social.models.Likeable
from social.models import Likeable
from . import models
models.UserAccount = make_model(models.UserAccount, Likeable)
Solution 3:[3]
You can do this at class creation time in your models.py
, but it probably causes more problems than it solves.
# myproject/userprofile/models.py
from django.db import models
from django.apps import apps
if apps.is_installed('someotherapp'):
from someotherapp.models import Likeable
BaseKlass = Likeable
else:
BaseKlass = models.Model
class UserAccount(BaseKlass):
username = models.CharField(max_length=64)
You can't control inheritance for a model class that has already been created from your AppConfig.ready
. Models in your models.py
are already created by the point in time that Django calls the ready
method. Django's model metaclass, which performs essential operations to correctly form fields, only works at class creation time; you can't edit a model after it has been created.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | aaron |
Solution 3 |