将 class 层次结构映射到数据库

Mapping a class hierarchy to a database

先决条件

我想实现抽象继承:

class Base(models.Model):
    title = models.CharField()
    slug = models.SlugField()
    description = models.TextField()

    class Meta: 
        abstract = True


class ChildA(Base):
    foo = models.CharField()


class ChildB(Base):
    bar = models.CharField()

出于多种原因,我需要这些 classes 层次结构的数据库表示。所以我想创建一个像这样的模型(leftright 属性允许我们识别实例在节点树中的位置):

class Node(models.Model):
    app_label = models.CharField()
    model_name = models.CharField()
    parent = models.ForeignKey('self', blank=True, null=True)
    right = models.PositiveIntegerField() 
    left = models.PositiveIntegerField()

问题

我需要类似这样的东西:

class Base(models.Model):
    ...
    def __init_subclass__(cls):
        app_label = cls._meta.app_label
        model_name = cls._meta.model_name
        parent_id = ? # I am not sure how do we get parent's id for now, but it should be manageable 
        obj = Node.objects.create('app_label'=app_label, 'model_name'=model_name, 'parent'=parent_id)
        obj.save()

因此,当我们子class 一个抽象模型时,会创建一个新节点来表示层次结构树中的这个新模型。不幸的是,它不起作用。似乎 __init_subclass__Model class 正确初始化之前被调用,因此 cls._meta.model_name 将 return 不正确的值(parent 的 model_name, 实际上)。我们可以绕过这个(或使用其他钩子)吗?

其他问题

我不确定这整个想法是否明智。我以前使用 multi-table 继承,但在某些时候 SQL 查询变得非常丑陋,所以我试图通过使用抽象模型来修复它。但是我们仍然需要将模型相互联系起来,节点树似乎很有吸引力。这样我就得到了一个具体的模型来同时管理多个表,像这样:

class NodeManager(models.Manager):
    def get_queryset(self):
        root = self._get_root_node()
        fields = root._meta.get_fields() 
        field_names = [field.name for field in fields]
        descendants = list(self._get_descendant_models(root))
        queryset_list = []
        for model in descendants:
            qs = model.objects.values(*field_names)
            annotated_qs = qs.annotate(
                resource_model=models.Value(
                    root._meta.model_name,
                    models.CharField(max_length=120)
                )
            )
            queryset_list.append(annotated_qs)

        if len(queryset_list) > 1:
            merged_queryset = queryset_list[0].union(*queryset_list[1:])
        elif len(queryset_list) == 1:
            merged_queryset = queryset_list[0]
        else:
            merged_queryset = None 

        return merged_queryset

我想这不是管理人员应该使用的方式,所以我不确定这样是否合适。 我不想专注于此,主要是为了更好地了解我的目标。但如果你告诉我你觉得好不好,我将不胜感激。

Django 有 ContentType 模块可以直接用于此目的,但您需要做一些额外的事情才能获得所需的东西。

您需要处理由 AppConfig class 定义的应用就绪方法。问题是您需要为每个应用程序处理应用程序就绪方法,而不是将此代码添加到每个应用程序,您只需将其添加为基础 class.

class BaseAppConfig(AppConfig):
    def add_or_update_node(self, model, super_classes):
        # assuming Node model is defined in a separate app called node
        from node.models import Node
        super_classes = super_classes[::-1]
        super_classes.append(model)
        prev_node = None
        for super_class in super_classes:
            node, _ = Node.objects.get_or_create(app_label=super_class._meta.app_label,
                                                 model_name=super_class._meta.model_name)
            if node.parent is None and prev_node is not None:
                node.parent = prev_node
                node.save()
            prev_node = node

    def ready(self):
        import inspect
        // if variable is not set than do not do anything 
        if os.getenv('CREATE_NODE') is None:
            return
        for model_name, model in self.models.items():
            super_classes = []
            for clazz in inspect.getmro(model):
                if clazz == Model or clazz == object or clazz == model:
                    continue
                super_classes.append(clazz)
            # no super class
            if len(super_classes) == 0:
                continue

            self.add_or_update_node(model, super_classes)

这仅处理当您拥有单个层次结构模型时的情况,当模型从多个抽象模型扩展时的情况如何,留给 op 处理。

您需要在 apps.py 文件中扩展此 class。

class CoreConfig(BaseAppConfig):
    name = 'Core'

    def ready(self):
        super(CoreConfig, self).ready()