Django多种型号,同一表格

Django多种型号,同一表格,django,django-models,Django,Django Models,我有一个遗留数据库,其中一个表表示文件系统中的节点。节点类型很少,例如A、B、C,不同类型的节点具有不同的属性。在当前的数据库设计中,有一个表保存有关节点的信息。如果节点属于类型A,则仅设置与类型A相关的字段。 现在我想把A、B、C型表示为模型。出现的问题是: 我希望有这样的行为,这三种类型都有一个name属性。我想按名称属性过滤文件系统中的所有节点,并获得良好类型的对象列表 每个节点都作为父链接,在数据库中表示为外键,因此可能需要进行某种形式的继承 在django有可能吗 是的,这是可能的。以

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

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

  • 每个节点都作为父链接,在数据库中表示为外键,因此可能需要进行某种形式的继承


  • 在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
    
    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)

    概念证明

    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
    

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

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

    想法

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

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

    这些模型是非托管的,但有一个自定义管理器,因此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
    
    这将创建一个模型,该模型将属性x映射到integer_field1,y映射到integer_field2,并将目标映射到char_field1,其中基础表与UserLog表相同

    用法与任何其他模型没有区别,south将只创建UserLog表

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

    实施

    它是如何工作的

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

    添加别名

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

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

    设置属性

    现在我们有了所有的字段,我们必须设置一些属性。该属性将欺骗south忽略该表,但它有一个副作用。这个班没有经理。(我们稍后会解决这个问题)。我们还从基本模型复制表名(),并将action_type字段默认为类名

    我们需要做的最后一件事是提供一名经理。必须注意一些,因为django指出只有一个QuerySet管理器。我们通过使用deepcopy复制管理器来解决这个问题,然后添加一个filter语句,该语句根据类名进行过滤

    deepcopy(QuerySet()).filter(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是否足够稳定


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

    是否要使用当前数据库模式(旧模式)?我从未见过以这种方式使用db_表。它与syncdb和/或South配合得好吗?我可能会尝试使用代理模型。它与syncdb不兼容。它将尝试为使用的每个模型创建表。它需要手动创建。我不能谈论与南方的兼容性。我甚至不能谈论整体兼容性。我从未有过这样的模式,但在我为测试它而创建的小测试项目中,它工作得很好。代理模型可能也适用于在MasterNode模型中定义的所有所需字段,并且代理模型仅使用它们想要的字段。将managed=False添加到第二个和第三个表定义的元将解决syncdb的问题。