Django dynamic model fields
我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后一位使JSONField不是一个很好的选择,所以我有以下解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 - 每个站点将具有一组不同的自定义数据字段,但使用相同的数据库。
然后,各种具体数据字段可以定义为:
1 2 3 4 5 6 7 8 9 | 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'),) |
这导致以下用途:
1 2 3 4 | 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,如上所列,因为它不能很好地处理查询。
截至今天,有四种可用的方法,其中两种需要一定的存储后端:
Django-eav(最初的包裹不再保留,但有一些蓬勃发展的叉子)
此解决方案基于实体属性值数据模型,实质上,它使用多个表来存储对象的动态属性。关于这个解决方案的重要部分是它:
允许您使用以下简单命令有效地将动态属性存储附加/分离到Django模型:
1 2 | eav.unregister(Encounter) eav.register(Patient) |
很好地与Django管理员集成;
同时真的很强大。
缺点:
用法非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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在性能方面并不理想,特别是如果您最终要在一个字段中存储数千个项目。它也只支持值的字符串。
1 2 3 4 5 | #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中你可以像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> 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字段发出索引查询:
1 2 3 4 5 6 7 8 9 10 11 | # 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可编码的数据类型,不仅仅是键/值对,而且往往更快,并且(对于JSONB)比Hstore更紧凑。
几个包实现了JSON / JSONB字段,包括django-pgfields,但是从Django 1.9开始,JSONField是一个内置的,使用JSONB进行存储。
JSONField类似于HStoreField,并且对于大型词典可能表现更好。它还支持字符串以外的类型,例如整数,布尔值和嵌套字典。
1 2 3 4 5 | #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中创建:
1 2 3 4 | >>> instance = Something.objects.create( name='something', data={'a': 1, 'b': 2, 'nested': {'c':3}} ) |
索引查询几乎与HStoreField完全相同,但嵌套是可能的。复杂索引可能需要手动创建(或脚本化迁移)。
1 2 3 | >>> 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,你需要用ListField替换ManyToMany等等。
查看这个Django MongoDB示例:
1 2 3 4 5 6 7 8 9 | 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模型的嵌入式列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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:基于syncdb和South-hooks的动态模型
Django-mutant实现了完全动态的外键和m2m字段。威尔·哈迪和迈克尔·霍尔的灵感来自于令人难以置信但有些神秘的解决方案。
所有这些都基于Django South钩子,根据Will Hardy在DjangoCon 2011上的讲话(观看它!),它们在生产中都是强大的并经过测试(相关的源代码)。
首先实施这个是Michael Hall。
是的,这很神奇,通过这些方法,您可以使用任何关系数据库后端实现完全动态的Django应用程序,模型和字段。但是以什么代价?应用的稳定性会在大量使用时受到影响吗这些是需要考虑的问题。您需要确保维护正确的锁定,以便同时允许数据库更改请求。
如果您使用的是Michael Halls lib,您的代码将如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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', ) |
好。
我一直在努力进一步推动django-dynamo的想法。该项目仍未记录,但您可以在https://github.com/charettes/django-mutant上阅读代码。
实际上FK和M2M字段(参见contrib.related)也可以工作,甚至可以为您自己的自定义字段定义包装器。
还支持模型选项,例如unique_together和排序以及模型库,因此您可以子类化模型代理,抽象或混合。
我实际上正在研究一种非内存中的锁定机制,以确保模型定义可以在多个django运行实例中共享,同时防止它们使用过时的定义。
该项目仍然是非常阿尔法,但它是我的一个项目的基石技术,所以我将不得不把它准备好生产。最大的计划是支持django-nonrel,这样我们就可以利用mongodb驱动程序了。
进一步的研究表明,这是实体属性值设计模式的一个特例,它已经通过几个包为Django实现。
首先,有一个原始的eav-django项目,它位于PyPi上。
其次,第一个项目django-eav是一个更新的分支,它主要是一个允许在第三方应用程序中使用django自己的模型或模型的EAV的重构。