2012-12-11 54 views
9

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

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

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

是否有可能在Django?

+0

你想使用当前数据库模式(传统模式)吗? – borges

回答

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 
+0

我从来没有见过以这种方式使用db_table。它与syncdb和/或South搭配很好吗?也许我会尝试使用代理模型。 –

+0

它不能很好地与syncdb配合使用。它会尝试为每个使用的模型创建表格。它需要手动创建。我不能说与南方的兼容性。我甚至不能说整体兼容性。我从来没有像这样的模式,但在我创建的小测试项目中测试它,它工作正常。代理模型可能也适用于在MasterNode模型中定义的所有必需字段,而代理模型只使用他们想要的字段。 –

+0

将managed = False添加到第二个和第三个表定义的元将修复sync​​db的问题。 –

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和其中底层表相同的表作为用户日志。

用法与其他型号没有什么不同,南方只会创建用户日志表。

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

实施

它是如何工作的?

如果评估类,装饰器将接收该类。这将猴子补丁类,所以它的实例将反映基表提供。

添加别名

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

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

设置属性

现在我们拥有所有的领域,我们要设置几个属性。该属性将欺骗南方忽略表,但它有副作用。班级不会有经理。 (我们稍后会解决这个问题)。我们还从基础模​​型复制表名(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 

这是为生产做好准备?

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

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

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