【问题标题】:Mapping a class hierarchy to a database将类层次结构映射到数据库
【发布时间】:2021-04-04 08:16:13
【问题描述】:

先决条件

我要实现抽象继承:

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

出于多种原因,我需要这些类的层次结构的 db 表示。所以我想创建一个像这样的模型(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()

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

其他问题

我不确定整个想法是否合理。我以前使用多表继承,但在某些时候 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

我猜这不是应该使用管理器的方式,所以我不确定它是否可以。 我不想专注于此,主要是为了更好地了解我的目标。但是,如果您让我知道您是否认为这很好,我将不胜感激。

【问题讨论】:

    标签: python django database-design


    【解决方案1】:

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

    您需要处理 AppConfig 类定义的应用就绪方法。问题是您需要为每个应用处理 App ready 方法,而不是将此代码添加到每个应用中,您只需将其添加为基类即可。

    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)
    

    这仅处理当你有一个单一层次模型的情况,当一个模型从多个抽象模型扩展,留给操作处理的情况下呢。

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

    class CoreConfig(BaseAppConfig):
        name = 'Core'
    
        def ready(self):
            super(CoreConfig, self).ready()
    

    【讨论】:

      猜你喜欢
      • 2011-09-03
      • 1970-01-01
      • 2017-10-17
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-02
      • 1970-01-01
      相关资源
      最近更新 更多