我使用了一種有點不同的方法,它通過創建一個透視圖與南方很好地玩。透視圖是重命名模型中某些字段的代理,但保留列的名稱。
對我來說,這是一個顯示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