Djangodynamic模型领域
我正在开发一个多租户应用程序,在这个应用程序中,一些用户可以定义自己的数据字段(通过pipe理员)来收集表单中的其他数据并报告数据。 后者使得JSONField不是一个好的select,所以相反我有以下的解决scheme:
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?
但这感觉非常笨重,特别是需要手动创build相关数据并将其与具体模型相关联。 有更好的方法吗?
已被先发制人抛弃的选项:
- 自定义SQL可以即时修改表。 部分是因为这不会扩展,部分是因为它太多了。
- 像NoSQL这样的无模式解决scheme。 我对他们没有任何反应,但他们仍然不太合适。 最终这些数据是input的,而且可能存在使用第三方报告应用程序的可能性。
- JSONField,如上所列,因为它不会很好地处理查询。
截至今天,有四种可用的方法,其中两种需要一定的存储后端:
-
Django-eav (原始包裹不再被修饰 ,但有一些欣欣向荣的叉子 )
该解决scheme基于实体属性值数据模型,本质上,它使用多个表来存储对象的dynamic属性。 关于这个解决scheme的重要部分是它:
- 使用了几个纯粹和简单的Django模型来表示dynamic字段,这使得它易于理解和与数据库无关;
-
允许您使用简单的命令来有效地附加/分离dynamic属性存储到Django模型,如:
eav.unregister(Encounter) eav.register(Patient)
-
很好地集成了Djangopipe理员 ;
-
同时真的很强大。
缺点:
- 效率不高 这更多的是对EAV模式本身的批评,这需要手动将数据从列格式合并到模型中的一组键值对中。
- 更难维护。 保持数据完整性需要多列唯一键约束,这在某些数据库中可能是低效的。
- 您将需要select其中一个叉子 ,因为官方包裹不再保持,并且没有明确的领导者。
用法非常简单:
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支持几种更复杂的数据types。 大多数都是通过第三方软件包来支持的,但是近些年来,Django已经将它们引入了django.contrib.postgres.fields。
HStoreField :
Django-hstore最初是第三方软件包,但是Django 1.8将HStoreField作为一个内置的,以及其他一些支持PostgreSQL的字段types。
这种方法在某种意义上说是好的,它可以让你拥有两全其美的领域:dynamic领域和关系数据库。 但是,hstore在性能方面并不理想 ,尤其是如果你打算在一个字段中存储数千个项目的话。 它也只支持string的值。
#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可编码的数据types,而不仅仅是键/值对,但是也往往比Hstore更快(对于JSONB)更紧凑。 有几个包实现了JSON / JSONB字段,包括django-pgfields ,但是从Django 1.9开始, JSONField是一个使用JSONB进行存储的内置程序。 JSONField类似于HStoreField,并且对于大型字典可能会更好。 它还支持string以外的types,如整数,布尔值和嵌套字典。
#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中创build:
>>> instance = Something.objects.create( name='something', data={'a': 1, 'b': 2, 'nested': {'c':3}} )
索引查询几乎与HStoreField完全相同,除了嵌套是可能的。 复杂索引可能需要手动创build(或脚本迁移)。
>>> Something.objects.filter(data__a=1) >>> Something.objects.filter(data__nested__c=3) >>> Something.objects.filter(data__has_key='a')
-
Django的MongoDB
或者其他的NoSQL Django改编 – 与他们你可以有完全dynamic的模型。
NoSQL Django库非常棒,但请记住,它们不是100%兼容Django,例如,从标准Django迁移到Django-nonrel ,您需要用ListField替代ManyToMany。
签出这个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, ...}
你甚至可以创build任何Django模型的embedded式列表 :
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的dynamic模型
Django-mutant实现了完全dynamic的Foreign Key和m2m字段。 威尔·哈代 ( Will Hardy)和迈克尔·霍尔(Michael Hall)的创作灵感来源于令人难以置信的但有些骇人的解决scheme
所有这些都是基于Django South的钩子,根据Will Hardy在DjangoCon 2011上的讲话 (注意!)仍然强大并且在生产( 相关源代码 )中进行了testing。
首先执行这个是迈克尔·霍尔 。
是的,这是神奇的,通过这些方法,您可以实现具有任何关系数据库后端的完全dynamic的Django应用程序,模型和字段 。 但费用是多less? 应用程序的稳定性受重用? 这些是需要考虑的问题。 您需要确保保持正确的locking以允许同时发生数据库更改请求。
如果您使用的是Michael Halls库,您的代码将如下所示:
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等模型选项的支持,还有对模型基础进行sorting,以便可以对模型代理,抽象或mixin进行子类化。
实际上,我正在研究一个不在内存中的locking机制,以确保可以跨多个django运行实例共享模型定义,同时防止它们使用过时的定义。
该项目仍然是非常的阿尔法,但它是我的一个项目的基石技术,所以我必须把它带到生产准备。 大计划也支持django-nonrel,所以我们可以利用mongodb驱动程序。
进一步的研究表明,这是一个实体属性值devise模式的一个有些特殊的例子,这个模式已经被Django用几个包实现了。
首先是PyPi上的原始eav-django项目。
其次,第一个项目django-eav有一个更新的分支,它主要是一个重构,允许在第三方应用中使用django自己的模型或模型的EAV。
- Django相当于PHP的表单值数组/关联数组
- Python + Django页面redirect
- 如何更改ModelForm中所有Djangodate字段的默认控件?
- 如何创build一个Django查询集filter,比较同一模型中的两个date字段
- django.core.exceptions.ImproperlyConfigured:加载MySQLdb模块时出错:没有名为MySQLdb的模块
- 如何为基本authentication发送正确的授权标头
- Django与模型视图控制器
- 如何在Django中检测浏览器types?
- 如果不创build表单,我可以在Django中创build一个不需要的pipe理域吗?