Django多个型号,同桌

时间:2012-12-11 09:20:58

标签: django django-models

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

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

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

  3. django有可能吗?

2 个答案:

答案 0 :(得分:4)

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

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

想法

透视图允许用户为一个表创建不同的模型,这些表可以有自己的方法并具有不同的字段名称,但共享底层模型和表。

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

模型是非托管的,但有一个自定义管理器,因此南方不会为它创建新表。

<强>用法

该实现是一个类装饰器,它修改了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

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

用法与任何其他模型没有区别,而南只会创建UserLog表。

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

<强>实施

它是如何运作的?

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

添加别名

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

然后,所有没有别名的属性都会添加到模型中。这不是必须的,但我选择添加它们。

设置属性

现在我们有了所有的字段,我们必须设置几个属性。 managed属性将忽略该表而向南移动,但它有副作用。班上没有经理。 (我们稍后会修复)。我们还从基础模​​型中复制表名(db_table),并将action_type字段默认为类名。

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

deepcopy(QuerySet())。filter(action_type = cls。 class name

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

这是代码:

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

这是否可以投入生产?

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

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

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

答案 1 :(得分:3)

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

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

数据库架构(SQLITE)

 Nodes {
    id        integer   primary key
    name_a    TEXT
    name_b    TEXT
    name_c    TEXT }

概念证明

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

这会产生:

id        name_a        name_b        name_c
1         Node A
2                       Node B
3                                     Node C