我使用了一种有点不同的方法,它通过创建一个透视图与南方很好地玩。透视图是重命名模型中某些字段的代理,但保留列的名称。
对我来说,这是一个显示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足够稳定。
而这种解决方案并不是您能想到的最佳解决方案。解决这个在数据库中存储动态字段的问题有更多的可能性。
你想使用当前数据库模式(传统模式)吗? – borges