将 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 层次结构的数据库表示。所以我想创建一个像这样的模型(left
和 right
属性允许我们识别实例在节点树中的位置):
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()
先决条件
我想实现抽象继承:
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 层次结构的数据库表示。所以我想创建一个像这样的模型(left
和 right
属性允许我们识别实例在节点树中的位置):
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()