Django 模型:从独立应用程序中定义的 类 动态继承

Django models: dynamic inheritance from classes defined in standalone app

我正在使用 Django 3.2

我写了一个独立的应用程序 social,它的模型定义如下:

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)
   
    # ...

在我的项目(使用独立应用程序)中,我有以下代码:

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 
            # ... ?

如何动态地让我的模型 userprofile.models.UserAccount 从 Likeable 派生?

如果我对要求的理解正确 - 您需要应用程序 userprofile 中的模型来实现 Likeable 功能。

因此这种情况下的继承不仅允许使用已实现的方法,而且还使其在数据库中具有相同的公共字段(likeslikes_count 等)

这意味着这样的继承还需要生成迁移脚本(以在数据库中添加必要的字段)。因此,甚至可能无法在应用程序准备就绪时在运行时执行此操作,因为您必须为迁移定义模型。

然而,即使有一种方法可以实现这种“动态”继承 on-the-fly - 我强烈建议不要采用这种方式,因为当模型的某些功能以这种方式定义时,它确实不透明一个隐藏的方式。

因此,针对您的请求的好的解决方案很简单 - 只是不要动态执行。即:

myproject/userprofile/models.py

from social.models import Likeable

class UserAccount(Likeable):
    # the definition of the user account class

因此其他需要 Likeable 功能的模型将直接继承它。

关于

的问题

How can I dynamically get my model userprofile.models.UserAccount

可以通过以下方式完成:

from django.apps import apps

app_models = apps.get_app_config('userprofile').get_models()

作为动态继承的 follow-up 你可以参考这里 How to dynamically change base class of instances at runtime?

UPD - 解决问题的方法有点不同

而不是使用 类 的继承,这在这种情况下甚至可能是不可能的 - 可以通过与数据库中的 Likeable 实体的关系来扩展目标模型。

即而不是携带 Likeable 继承 - 它可以作为 one-to-one 关系提供,可以在运行时构建如下:

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
                )

然后,可以通过 likeable 字段在 userprofile 的模型上访问 likeable 的方法。即:

u = UserAccount.objects.last()
u.likeable.get_content_info()

要动态继承另一个class,我们通常这样做:

def make_class(cls, cls_to_inherit):
    return type(
        cls.__name__,
        (cls, cls_to_inherit),
        {},
    )

要动态继承另一个具有 metaclass=ModelBase 的 Django 模型,我们这样做:

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__},
    )

用法:

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)

您可以在 models.py 的 class 创建时间执行此操作,但它可能导致的问题多于解决的问题。

# 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)

您无法控制已经从您的 AppConfig.ready 创建的模型 class 的继承。 models.py 中的模型已经在 Django 调用 ready 方法的时间点创建。 Django 的模型 metaclass,它执行基本操作以正确形成字段,仅在 class 创建时有效;模型创建后无法编辑。