Django多个型号,同桌

use*_*926 10 django django-models

我有一个遗留数据库,其中一个表代表文件系统中的节点.节点类型很少,例如A,B,C,不同类型的节点具有不同的属性.在当前的数据库设计中,有一个表保存有关节点的信息.如果节点是类型A,则仅设置与类型A相关的字段.现在我想将类型A,B,C表示为模型.出现的问题是:

  1. 我想有这样的行为,所有这三种类型都有一个名称属性.我想通过name属性过滤文件系统中的所有节点,并获得良好类型的对象列表.

  2. 每个节点作为父链接,在数据库中表示为外键,因此可能会发生某种形式的继承.

django有可能吗?

D.A*_*D.A 5

对的,这是可能的。这是一个例子:

models.py

from django.db import models

# Create your models here.
class NodeA(models.Model):

    name_a = models.CharField(max_length=75, blank=True, null=True)

    class Meta:
        db_table = 'Nodes'
        managed = False

class NodeB(models.Model):

    name_b = models.CharField(max_length=75, blank=True, null=True)

    class Meta:
        db_table = 'Nodes'
        managed = False

class NodeC(models.Model):

    name_c = models.CharField(max_length=75, blank=True, null=True)

    class Meta:
        db_table = 'Nodes'
        managed = False
Run Code Online (Sandbox Code Playgroud)

数据库架构(SQLITE)

 Nodes {
    id        integer   primary key
    name_a    TEXT
    name_b    TEXT
    name_c    TEXT }
Run Code Online (Sandbox Code Playgroud)

概念证明

import NodeA, NodeB, NodeC

a = NodeA()
a.name_a = 'Node A'
a.save()

b = NodeB()
b.name_b = 'Node B'
b.save()

c = NodeC()
c.name_c = 'Node C'
c.save()
Run Code Online (Sandbox Code Playgroud)

这将产生:

id        name_a        name_b        name_c
1         Node A
2                       Node B
3                                     Node C
Run Code Online (Sandbox Code Playgroud)

  • 谢谢@andybak。我出于档案目的更新了答案。 (3认同)
  • 将 Managed=False 添加到第二个和第三个表定义的 Meta 将修复sync​​db 的问题。 (2认同)

Edg*_*rks 5

我使用了一种稍微不同的方法,它通过创建一个视角来很好地与南方配合。透视图是一个代理,它重命名模型中的某些字段,但保留列的名称。

对我来说,这是展示 django ORM 灵活性的一个例子。我不确定您是否想在生产代码中使用它。因此它没有经过足够的测试,但它会给你一些想法。

想法

透视图让用户为一张表创建不同的模型,可以有自己的方法和不同的字段名称,但共享底层模型和表。

它可以在同一个表中存储不同的类型,这对于日志记录或事件系统来说非常方便。每个透视图只能看到它自己的条目,因为它是根据字段名称action_type过滤的。

这些模型是不受管理的,但有一个自定义管理器,所以 South 不会为其创建新表。

用法

实现是一个类装饰器,它修改django模型的元数据。它接受一个“基本”模型和一个别名字段的字典。

先来看一个例子:

class UserLog(models.Model):
"""
A user action log system, user is not in this class, because it clutters import
"""
date_created = models.DateTimeField(_("Date created"), auto_now_add=True)

# Action type is obligatory

action_type = models.CharField(_("Action Type"), max_length=255)
integer_field1 = models.IntegerField()
integer_field2 = models.IntegerField()
char_field1 = models.CharField(max_length=255)
char_field2 = models.CharField(max_length=255)


@ModelPerspective({
    'x': 'integer_field1',
    'y': 'integer_field2',
    'target': 'char_field1'
}, UserLog)
class UserClickLog(models.Model):
    pass
Run Code Online (Sandbox Code Playgroud)

这将创建一个模型,该模型将属性 x 映射到 integer_field1,将 y 映射到 integer_field2,并将目标映射到 char_field1,其中基础表与 UserLog 表相同。

用法与任何其他模型没有什么不同,而 south 只会创建 UserLog 表。

现在让我们看看如何实现这一点。

执行

它是如何工作的?

如果对类进行评估,则装饰器接收该类。这将对类进行猴子修补,因此它的实例将反映您提供的基表。

添加别名

如果我们更深入地研究代码。读取别名字典并为每个字段查找基本字段。如果我们在基表中找到该字段,则名称会更改。这有一个小的副作用,它也会改变列。所以我们必须从基本字段中检索字段列。然后使用contribute_to_class方法将该字段添加到类中,该方法负责所有簿记。

然后将所有未别名的属性添加到模型中。这本身不需要,但我选择添加它们。

设置属性

现在我们有了所有的字段,我们必须设置几个属性。该管理属性将无视表南方欺骗,但它有一个副作用。班级将没有经理。(我们稍后会解决这个问题)。我们还从基础模型中复制表名 ( db_table ) 并使 action_type 字段默认为类名。

我们需要做的最后一件事是提供一个经理。必须小心,因为 django 声明只有一个 QuerySet 管理器。我们通过用 deepcopy 复制管理器来解决这个问题,然后添加一个过滤器语句,该语句过滤类名。

deepcopy的(查询集())。过滤器(ACTION_TYPE = CLS。

这让我们的表只返回相关记录。现在把它包装成一个装饰器就完成了。

这是代码:

from django.db import models
from django.db.models.query import QuerySet

def ModelPerspective(aliases, model):
  """
  This class decorator creates a perspective from a model, which is
  a proxy with aliased fields.

  First it will loop over all provided aliases
  these are pairs of new_field, old_field.
  Then it will copy the old_fields found in the
  class to the new fields and change their name,
  but keep their columnnames.

  After that it will copy all the fields, which are not aliased.

 Then it will copy all the properties of the model to the new model.

  Example:
    @ModelPerspective({
        'lusername': 'username',
        'phonenumber': 'field1'
    }, User)
    class Luser(models.Model):
        pass

  """
  from copy import deepcopy

  def copy_fields(cls):

    all_fields = set(map(lambda x: x.name, model._meta.fields))
    all_fields.remove('id')
    # Copy alias fields

    for alias_field in aliases:

        real_field = aliases[alias_field]

        # Get field from model
        old_field = model._meta.get_field(real_field)
        oldname, columnname = old_field.get_attname_column()
        new_field = deepcopy(old_field)

        # Setting field properties
        new_field.name = alias_field
        new_field.db_column = columnname
        new_field.verbose_name = alias_field

        new_field.contribute_to_class(cls, "_%s" % alias_field)
        all_fields.remove(real_field)

    for field in all_fields:
        new_field = deepcopy(model._meta.get_field(field))
        new_field.contribute_to_class(cls, "_%s" % new_field.name)

  def copy_properties(cls):
    # Copy db table
    cls._meta.db_table = model._meta.db_table


  def create_manager(cls):
    from copy import deepcopy
    field = cls._meta.get_field('action_type')
    field.default = cls.__name__
    # Only query on relevant records
    qs = deepcopy(cls.objects)
    cls.objects = qs.filter(action_type=cls.__name__)

  def wrapper(cls):

    # Set it unmanaged
    cls._meta.managed = False

    copy_properties(cls)
    copy_fields(cls)
    create_manager(cls)

    return cls
  return wrapper
Run Code Online (Sandbox Code Playgroud)

这是准备生产了吗?

我不会在生产代码中使用它,对我来说这是一个展示 django 灵活性的练习,但是如果有足够的测试,它可以在代码中使用。

反对在生产中使用的另一个论点是代码使用了大量 django ORM 的内部工作。我不确定 api 是否足够稳定。

而这个解决方案并不是您能想到的最佳解决方案。有更多的可能性来解决在数据库中存储动态字段的问题。