Django 动态模型字段
Posted
技术标签:
【中文标题】Django 动态模型字段【英文标题】:Django dynamic model fields 【发布时间】:2011-12-17 12:42:27 【问题描述】:我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后一点使 JSONField 不是一个很好的选择,所以我有以下解决方案:
class CustomDataField(models.Model):
"""
Abstract specification for arbitrary data fields.
Not used for holding data itself, but metadata about the fields.
"""
site = models.ForeignKey(Site, default=settings.SITE_ID)
name = models.CharField(max_length=64)
class Meta:
abstract = True
class CustomDataValue(models.Model):
"""
Abstract specification for arbitrary data.
"""
value = models.CharField(max_length=1024)
class Meta:
abstract = True
请注意 CustomDataField 如何具有指向站点的 ForeignKey - 每个站点将具有一组不同的自定义数据字段,但使用相同的数据库。 那么各种具体的数据字段可以定义为:
class UserCustomDataField(CustomDataField):
pass
class UserCustomDataValue(CustomDataValue):
custom_field = models.ForeignKey(UserCustomDataField)
user = models.ForeignKey(User, related_name='custom_data')
class Meta:
unique_together=(('user','custom_field'),)
这导致了以下用途:
custom_field = UserCustomDataField.objects.create(name='zodiac', site=my_site) #probably created in the admin
user = User.objects.create(username='foo')
user_sign = UserCustomDataValue(custom_field=custom_field, user=user, data='Libra')
user.custom_data.add(user_sign) #actually, what does this even do?
但这感觉非常笨拙,尤其是需要手动创建相关数据并将其与具体模型相关联。有更好的方法吗?
已被抢先丢弃的选项:
自定义 SQL 以即时修改表。部分原因是它无法扩展,部分原因是它太老套了。 NoSQL 等无模式解决方案。我没有反对他们,但他们仍然不合适。最终这些数据是类型化的,并且存在使用第三方报告应用程序的可能性。 JSONField,如上所述,因为它不适用于查询。【问题讨论】:
先发制人,这不是这些问题中的任何一个:***.com/questions/7801729/…***.com/questions/2854656/… 【参考方案1】:截至目前,有四种可用的方法,其中两种需要特定的存储后端:
Django-eav(原包不再托管,但有一些thriving forks)
该方案基于Entity Attribute Value数据模型,本质上是使用多个表来存储对象的动态属性。这个解决方案的优点在于它:
使用几个纯粹简单的 Django 模型来表示动态字段,这使其易于理解且与数据库无关;允许您使用以下简单命令有效地将动态属性存储附加/分离到 Django 模型:
eav.unregister(Encounter)
eav.register(Patient)
Nicely integrates with Django admin;
同时非常强大。
缺点:
效率不高。这更像是对 EAV 模式本身的批评,它需要手动将列格式的数据合并到模型中的一组键值对。 更难维护。维护数据完整性需要多列唯一键约束,这在某些数据库上可能效率低下。 您需要选择one of the forks,因为官方包不再维护,也没有明确的领导者。用法很简单:
import eav
from app.models import Patient, Encounter
eav.register(Encounter)
eav.register(Patient)
Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT)
Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='city', datatype=Attribute.TYPE_TEXT)
Attribute.objects.create(name='country', datatype=Attribute.TYPE_TEXT)
self.yes = EnumValue.objects.create(value='yes')
self.no = EnumValue.objects.create(value='no')
self.unkown = EnumValue.objects.create(value='unkown')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.enums.add(self.yes)
ynu.enums.add(self.no)
ynu.enums.add(self.unkown)
Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM,\
enum_group=ynu)
# When you register a model within EAV,
# you can access all of EAV attributes:
Patient.objects.create(name='Bob', eav__age=12,
eav__fever=no, eav__city='New York',
eav__country='USA')
# You can filter queries based on their EAV fields:
query1 = Patient.objects.filter(Q(eav__city__contains='Y'))
query2 = Q(eav__city__contains='Y') | Q(eav__fever=no)
PostgreSQL 中的 Hstore、JSON 或 JSONB 字段
PostgreSQL 支持几种更复杂的数据类型。大多数都通过第三方包支持,但近年来 Django 已将它们纳入 django.contrib.postgres.fields。
HStoreField:
Django-hstore 最初是一个第三方包,但 Django 1.8 添加了 HStoreField 作为内置,以及其他几个 PostgreSQL 支持的字段类型。
从某种意义上说,这种方法很好,它可以让您拥有两全其美:动态字段和关系数据库。但是,hstore 是not ideal performance-wise,尤其是当您最终要在一个字段中存储数千个项目时。它也只支持值的字符串。
#app/models.py
from django.contrib.postgres.fields import HStoreField
class Something(models.Model):
name = models.CharField(max_length=32)
data = models.HStoreField(db_index=True)
在 Django 的 shell 中你可以这样使用它:
>>> instance = Something.objects.create(
name='something',
data='a': '1', 'b': '2'
)
>>> instance.data['a']
'1'
>>> empty = Something.objects.create(name='empty')
>>> empty.data
>>> empty.data['a'] = '1'
>>> empty.save()
>>> Something.objects.get(name='something').data['a']
'1'
您可以针对 hstore 字段发出索引查询:
# equivalence
Something.objects.filter(data='a': '1', 'b': '2')
# subset by key/value mapping
Something.objects.filter(data__a='1')
# subset by list of keys
Something.objects.filter(data__has_keys=['a', 'b'])
# subset by single key
Something.objects.filter(data__has_key='a')
JSONField:
JSON/JSONB 字段支持任何可 JSON 编码的数据类型,不仅是键/值对,而且往往比 Hstore 更快且(对于 JSONB)更紧凑。 几个包实现了 JSON/JSONB 字段,包括 django-pgfields,但在 Django 1.9 中,JSONField 是使用 JSONB 进行存储的内置包。 JSONField 与 HStoreField 类似,可能在大型字典中表现更好。它还支持字符串以外的类型,例如整数、布尔值和嵌套字典。
#app/models.py
from django.contrib.postgres.fields import JSONField
class Something(models.Model):
name = models.CharField(max_length=32)
data = JSONField(db_index=True)
在 shell 中创建:
>>> instance = Something.objects.create(
name='something',
data='a': 1, 'b': 2, 'nested': 'c':3
)
索引查询几乎与 HStoreField 相同,但可以嵌套。复杂的索引可能需要手动创建(或脚本化迁移)。
>>> Something.objects.filter(data__a=1)
>>> Something.objects.filter(data__nested__c=3)
>>> Something.objects.filter(data__has_key='a')
Django MongoDB
或其他 NoSQL Django 改编——使用它们,您可以拥有完全动态的模型。
NoSQL Django 库很棒,但请记住,它们并非 100% 兼容 Django,例如,要从标准 Django 迁移到 Django-nonrel,您需要将 ManyToMany 替换为 ListField 等。
查看这个 Django MongoDB 示例:
from djangotoolbox.fields import DictField
class Image(models.Model):
exif = DictField()
...
>>> image = Image.objects.create(exif=get_exif_data(...))
>>> image.exif
u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...
您甚至可以创建任何 Django 模型的 embedded lists:
class Container(models.Model):
stuff = ListField(EmbeddedModelField())
class FooModel(models.Model):
foo = models.IntegerField()
class BarModel(models.Model):
bar = models.CharField()
...
>>> Container.objects.create(
stuff=[FooModel(foo=42), BarModel(bar='spam')]
)
Django-mutant: Dynamic models based on syncdb and South-hooks
Django-mutant 实现完全动态的外键和 m2m 字段。并受到Will Hardy 和 Michael Hall 的令人难以置信但有些骇人听闻的解决方案的启发。
所有这些都基于 Django South 钩子,根据Will Hardy's talk at DjangoCon 2011 (看它!),这些钩子仍然很健壮并且在生产中经过测试 (relevant source code)。
implement this 的第一个是 Michael Hall。
是的,这很神奇,通过这些方法,您可以使用任何关系数据库后端实现完全动态的 Django 应用程序、模型和字段。但代价是什么?大量使用会影响应用程序的稳定性吗?这些都是需要考虑的问题。您需要确保保持正确的lock 以允许同时进行数据库更改请求。
如果您使用的是 Michael Halls lib,您的代码将如下所示:
from dynamo import models
test_app, created = models.DynamicApp.objects.get_or_create(
name='dynamo'
)
test, created = models.DynamicModel.objects.get_or_create(
name='Test',
verbose_name='Test Model',
app=test_app
)
foo, created = models.DynamicModelField.objects.get_or_create(
name = 'foo',
verbose_name = 'Foo Field',
model = test,
field_type = 'dynamiccharfield',
null = True,
blank = True,
unique = False,
help_text = 'Test field for Foo',
)
bar, created = models.DynamicModelField.objects.get_or_create(
name = 'bar',
verbose_name = 'Bar Field',
model = test,
field_type = 'dynamicintegerfield',
null = True,
blank = True,
unique = False,
help_text = 'Test field for Bar',
)
【讨论】:
这个话题最近在 DjangoCon 2013 Europe 上被讨论过:slideshare.net/schacki/… 和 youtube.com/watch?v=67wcGdk4aCc 可能还值得注意的是,在 Postgres >= 9.2 上使用 django-pgjson 允许直接使用 postgresql 的 json 字段。在 Django >= 1.7 上,查询的过滤器 API 相对健全。 Postgres >= 9.4 还允许具有更好索引的 jsonb 字段以实现更快的查询。 今天更新说明 Django 将 HStoreField 和 JSONField 纳入 contrib。它包括一些不太棒的表单小部件,但如果您需要在管理员中调整数据,它确实可以工作。【参考方案2】:进一步的研究表明,这是Entity Attribute Value 设计模式的一个特例,已经由几个包为 Django 实现。
首先,有原始的 eav-django 项目,它位于 PyPi 上。
其次,第一个项目有一个更新的分支django-eav,它主要是一个重构,允许将 EAV 与 django 自己的模型或第三方应用程序中的模型一起使用。
【讨论】:
我会把它收录到维基中。 我会反过来说,EAV 是动态建模的一个特例。它在“语义网”社区中被大量使用,如果它包含唯一 ID,则称为“三元组”或“四元组”。但是,它不可能像可以动态创建和修改 SQL 表的机制那样高效。 @GDom 是你的首选吗?我的意思是你选择了上面的哪个选项? @Moreno 正确的选择很大程度上取决于您的具体用例。出于不同的原因,我同时使用了 EAV 和 JsonFields。后者现在由 Django 直接支持,所以对于一个新项目,我会首先使用它,除非我有特定的需要能够查询 EAV 表。请注意,您也可以查询 JsonFields。【参考方案3】:我一直致力于进一步推动 django-dynamo 的想法。该项目仍未记录,但您可以在https://github.com/charettes/django-mutant 阅读代码。
实际上 FK 和 M2M 字段(请参阅 contrib.related)也可以工作,甚至可以为您自己的自定义字段定义包装器。
还支持模型选项,例如 unique_together 和 ordering 以及 Model bases,因此您可以子类化模型代理、抽象或 mixins。
我实际上正在研究一种非内存锁定机制,以确保模型定义可以在多个 django 运行实例之间共享,同时防止它们使用过时的定义。
该项目仍处于初期阶段,但它是我的一个项目的基石技术,因此我必须将其投入生产。大计划也是支持 django-nonrel,因此我们可以利用 mongodb 驱动程序。
【讨论】:
嗨,西蒙!在您在 github 上创建项目之后,我在 my wiki answer 中包含了指向您项目的链接。 :))) 很高兴在 *** 上见到你!以上是关于Django 动态模型字段的主要内容,如果未能解决你的问题,请参考以下文章