我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后一点使得 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 如何具有 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?
但这感觉非常笨重,特别是需要手动创建相关数据并将其与具体模型关联起来。有更好的方法吗?
已被预先丢弃的选项:
- 自定义 SQL 来即时修改表。部分原因是这无法扩展,部分原因是它太过于黑客行为。
- 无模式解决方案,例如 NoSQL。我没有反对他们,但他们仍然不合适。最终输入该数据,并且可以使用第三方报告应用程序。
- JSONField,如上所述,因为它不能很好地处理查询。
I'm working on a multi-tenanted application in which some users can define their own data fields (via the admin) to collect additional data in forms and report on the data. The latter bit makes JSONField not a great option, so instead I have the following solution:
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
Note how CustomDataField has a ForeignKey to Site - each Site will have a different set of custom data fields, but use the same database.
Then the various concrete data fields can be defined as:
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'),)
This leads to the following use:
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?
But this feels very clunky, particularly with the need to manually create the related data and associate it with the concrete model. Is there a better approach?
Options that have been pre-emptively discarded:
- Custom SQL to modify tables on-the-fly. Partly because this won't scale and partly because it's too much of a hack.
- Schema-less solutions like NoSQL. I have nothing against them, but they're still not a good fit. Ultimately this data is typed, and the possibility exists of using a third-party reporting application.
- JSONField, as listed above, as it's not going to work well with queries.
发布评论
评论(3)
截至今天,有四种可用的方法,其中两种需要特定的存储后端:
Django-eav (原始包不再维护,但有一些蓬勃发展的分叉)
该解决方案基于实体属性值数据模型,本质上,它使用多个表来存储对象的动态属性。该解决方案的重要部分在于:
允许您使用以下简单命令有效地将动态属性存储附加/分离到 Django 模型:
与 Django admin 很好地集成< /a>;
同时又非常强大。
缺点:
用法非常简单:
<前><代码>导入eav
从 app.models 导入患者、遭遇
eav.register(遭遇)
eav.register(患者)
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='国家', 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='是/否/未知')
ynu.enums.add(self.yes)
ynu.enums.add(self.no)
ynu.enums.add(self.unkown)
Attribute.objects.create(name='发烧', datatype=Attribute.TYPE_ENUM,\
枚举组=ynu)
# 当您在 EAV 中注册模型时,
# 您可以访问所有 EAV 属性:
Patient.objects.create(name='Bob', eav__age=12,
eav__fever=否, eav__city='纽约',
eav__country='美国')
# 您可以根据 EAV 字段过滤查询:
query1 = Patient.objects.filter(Q(eav__city__contains='Y'))
query2 = Q(eav__city__contains='Y') | Q(eav__发烧=否)
PostgreSQL 中的 Hstore、JSON 或 JSONB 字段
PostgreSQL 支持多种更复杂的数据类型。大多数都是通过第三方包支持的,但近年来 Django 已将它们采用到 django.contrib.postgres.fields 中。
HStoreField:
Django-hstore原本是第三方包,但是Django 1.8添加了 HStoreField 作为内置的,以及其他几种 PostgreSQL 支持的字段类型。
从某种意义上说,这种方法很好,它可以让您两全其美:动态字段和关系数据库。然而,hstore 在性能方面并不理想,特别是如果您最终将在一个字段中存储数千个项目。它还仅支持值的字符串。
<前><代码>#app/models.py
从 django.contrib.postgres.fields 导入 HStoreField
类 Something(models.Model):
名称 = models.CharField(max_length=32)
数据 = models.HStoreField(db_index=True)
在 Django 的 shell 中你可以像这样使用它:
<前><代码>>>>实例 = Something.objects.create(
名称='某事',
数据={'a':'1','b':'2'}
)
>>>>>实例.data['a']
‘1’
>>>>>空 = Something.objects.create(name='空')
>>>>>空数据
{}
>>>>>空.data['a'] = '1'
>>>>>清空.save()
>>>>> Something.objects.get(name='something').data['a']
‘1’
您可以对 hstore 字段发出索引查询:
JSONField:
JSON/JSONB 字段支持任何 JSON 可编码的数据类型,不仅仅是键/值对,而且往往比 Hstore 更快且(对于 JSONB)更紧凑。
几个包实现了 JSON/JSONB 字段,包括 django-pgfields< /strong>,但从 Django 1.9 开始,JSONField是内置的使用JSONB进行存储。
JSONField 与 HStoreField 类似,并且对于大型字典可能表现更好。它还支持字符串以外的类型,例如整数、布尔值和嵌套字典。
<前><代码>#app/models.py
从 django.contrib.postgres.fields 导入 JSONField
类 Something(models.Model):
名称 = models.CharField(max_length=32)
数据 = JSONField(db_index=True)
在 shell 中创建:
<前><代码>>>>实例 = Something.objects.create(
名称='某事',
数据={'a': 1, 'b': 2, '嵌套': {'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-nonrel 来自标准 Django,您需要将 ManyToMany 替换为 ListField 等等。
查看这个 Django MongoDB 示例:
您甚至可以创建任何 Django 模型的嵌入列表:
Django-mutant:基于syncdb和的动态模型南钩
Django-mutant 实现完全动态的外键和 m2m 字段。其灵感来自 Will Hardy 和 Michael Hall 的令人难以置信但有些黑客的解决方案.
所有这些都基于 Django South hooks,根据 威尔·哈迪 (Will Hardy) 在 DjangoCon 2011 上的演讲 (观看!) 仍然很强大,并且在生产中经过了测试(相关源代码)。
首先实现此的是迈克尔·霍尔。
是的,这很神奇,通过这些方法,您可以使用任何关系数据库后端实现完全动态的 Django 应用程序、模型和字段。但代价是什么?大量使用会影响应用程序的稳定性吗?这些都是需要考虑的问题。您需要确保按顺序保持适当的锁定允许同时更改数据库请求。
如果您使用 Michael Halls lib,您的代码将如下所示:
As of today, there are four available approaches, two of them requiring a certain storage backend:
Django-eav (the original package is no longer mantained but has some thriving forks)
This solution is based on Entity Attribute Value data model, essentially, it uses several tables to store dynamic attributes of objects. Great parts about this solution is that it:
allows you to effectively attach/detach dynamic attribute storage to Django model with simple commands like:
Nicely integrates with Django admin;
At the same time being really powerful.
Downsides:
The usage is pretty straightforward:
Hstore, JSON or JSONB fields in PostgreSQL
PostgreSQL supports several more complex data types. Most are supported via third-party packages, but in recent years Django has adopted them into django.contrib.postgres.fields.
HStoreField:
Django-hstore was originally a third-party package, but Django 1.8 added HStoreField as a built-in, along with several other PostgreSQL-supported field types.
This approach is good in a sense that it lets you have the best of both worlds: dynamic fields and relational database. However, hstore is not ideal performance-wise, especially if you are going to end up storing thousands of items in one field. It also only supports strings for values.
In Django's shell you can use it like this:
You can issue indexed queries against hstore fields:
JSONField:
JSON/JSONB fields support any JSON-encodable data type, not just key/value pairs, but also tend to be faster and (for JSONB) more compact than Hstore.
Several packages implement JSON/JSONB fields including django-pgfields, but as of Django 1.9, JSONField is a built-in using JSONB for storage.
JSONField is similar to HStoreField, and may perform better with large dictionaries. It also supports types other than strings, such as integers, booleans and nested dictionaries.
Creating in the shell:
Indexed queries are nearly identical to HStoreField, except nesting is possible. Complex indexes may require manually creation (or a scripted migration).
Django MongoDB
Or other NoSQL Django adaptations -- with them you can have fully dynamic models.
NoSQL Django libraries are great, but keep in mind that they are not 100% the Django-compatible, for example, to migrate to Django-nonrel from standard Django you will need to replace ManyToMany with ListField among other things.
Checkout this Django MongoDB example:
You can even create embedded lists of any Django models:
Django-mutant: Dynamic models based on syncdb and South-hooks
Django-mutant implements fully dynamic Foreign Key and m2m fields. And is inspired by incredible but somewhat hackish solutions by Will Hardy and Michael Hall.
All of these are based on Django South hooks, which, according to Will Hardy's talk at DjangoCon 2011 (watch it!) are nevertheless robust and tested in production (relevant source code).
First to implement this was Michael Hall.
Yes, this is magic, with these approaches you can achieve fully dynamic Django apps, models and fields with any relational database backend. But at what cost? Will stability of application suffer upon heavy use? These are the questions to be considered. You need to be sure to maintain a proper lock in order to allow simultaneous database altering requests.
If you are using Michael Halls lib, your code will look like this:
我一直致力于进一步推动 django-dynamo 的想法。该项目仍然没有文档记录,但您可以在 https://github.com/charettes/django-mutant 阅读代码。
实际上,FK 和 M2M 字段(请参阅 contrib.lated)也可以工作,甚至可以为您自己的自定义字段定义包装器。
还支持模型选项,例如 unique_together 和排序以及模型基础,因此您可以对模型代理、抽象或混合进行子类化。
我实际上正在研究一种非内存锁机制,以确保模型定义可以在多个 django 运行实例之间共享,同时防止它们使用过时的定义。
该项目仍处于 alpha 阶段,但它是我的一个项目的基石技术,因此我必须将其投入生产。最大的计划是支持 django-nonrel,这样我们就可以利用 mongodb 驱动程序。
I've been working on pushing the django-dynamo idea further. The project is still undocumented but you can read the code at https://github.com/charettes/django-mutant.
Actually FK and M2M fields (see contrib.related) also work and it's even possible to define wrapper for your own custom fields.
There's also support for model options such as unique_together and ordering plus Model bases so you can subclass model proxy, abstract or mixins.
I'm actually working on a not in-memory lock mechanism to make sure model definitions can be shared accross multiple django running instances while preventing them using obsolete definition.
The project is still very alpha but it's a cornerstone technology for one of my project so I'll have to take it to production ready. The big plan is supporting django-nonrel also so we can leverage the mongodb driver.
进一步的研究表明,这是 Entity Attribute Value 的特殊情况 设计模式,已通过几个包为 Django 实现。
首先,有原始的 eav-django 项目,位于 PyPi 上。
其次,第一个项目有一个更新的分支 django-eav 它主要是对允许将 EAV 与 django 自己的模型或第三方应用程序中的模型一起使用。
Further research reveals that this is a somewhat special case of Entity Attribute Value design pattern, which has been implemented for Django by a couple of packages.
First, there's the original eav-django project, which is on PyPi.
Second, there's a more recent fork of the first project, django-eav which is primarily a refactor to allow use of EAV with django's own models or models in third-party apps.