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,如上所列,因为它不会很好地处理查询。

截至今天,有四种可用的方法,其中两种需要一定的存储后端:

  1. 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) 
  2. 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') 
  3. 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')] ) 
  4. 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。