我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后一位使得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 to Site - 每个站点将具有一组不同的自定义数据字段,但使用相同的数据库。 然后,各种具体数据字段可以定义为:
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?
但这感觉非常笨重,特别是需要手动创建相关数据并将其与具体模型相关联。有更好的方法吗?
先发制人地放弃的选项:
答案 0 :(得分:261)
截至今天,有四种可用的方法,其中两种方法需要特定的存储后端:
Django-eav (原始包不再保留,但有一些 thriving forks )
此解决方案基于Entity Attribute Value数据模型,实际上,它使用多个表来存储对象的动态属性。关于这个解决方案的重要部分是它:
允许您使用以下简单命令有效地将动态属性存储附加/分离到Django模型:
eav.unregister(Encounter)
eav.register(Patient)
同时真的很强大。
缺点:
使用非常简单:
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和迈克尔·霍尔的惊人解决方案的启发。
所有这些都基于Django South钩子,根据Will Hardy's talk at DjangoCon 2011 (观察它!),它们仍然健壮并在生产中进行了测试(relevant source code)。< / p>
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',
)
答案 1 :(得分:13)
我一直致力于进一步推动django-dynamo的想法。该项目仍未记录,但您可以在https://github.com/charettes/django-mutant阅读代码。
实际上FK和M2M字段(参见contrib.related)也可以工作,甚至可以为您自己的自定义字段定义包装器。
还支持模型选项,例如unique_together和排序以及模型库,因此您可以子类化模型代理,抽象或混合。
我实际上正在开发一种非内存中锁机制,以确保模型定义可以在多个django运行实例中共享,同时防止它们使用过时的定义。
该项目仍然非常阿拉伯,但它是我项目之一的基石技术,因此我必须将其投入生产准备。最大的计划是支持django-nonrel,这样我们就可以利用mongodb驱动程序了。
答案 2 :(得分:3)
进一步的研究表明,这是Entity Attribute Value设计模式的一个特例,已经通过几个包为Django实现。
首先,有一个原始的eav-django项目,它位于PyPi上。
其次,第一个项目django-eav有一个更新的分支,它主要是一个允许在第三方应用程序中使用django自己的模型或模型的EAV的重构。