2011-10-28 265 views
130

我正在研究多租户应用程序,其中一些用户可以定义他们自己的数据字段(通过管理员)来收集表单中的其他数据并报告数据。后者位使得JSONField不是一个很好的选择,所以不是我有以下解决方案:Django动态模型字段

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? 

但这种感觉很笨重,尤其是需要手动创建相关数据和将其与具体模型相关联。有更好的方法吗?

选项已经先发制人地丢弃:

  • 自定义SQL来修改即时表。部分原因是这不会扩展,部分是因为它太过分了。
  • 无架构解决方案,如NoSQL。我对他们没有任何反应,但他们仍然不太合适。最终,该数据键入的,并且存在使用第三方报告应用程序的可能性。
  • JSONField,如上所列,因为它不会很好地处理查询。
+6

先发制人,这不是任何一个问题: http://stackoverflow.com/questions/7801729/django-model-with-dynamic-attributes http://stackoverflow.com/questions/ 2854656/per-instance-dynamic-fields-django-model – GDorn

回答

242

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

  1. Django-eav(原包装不再是编程和维持,但有一些thriving forks

    该解决方案基于Entity Attribute Value数据模型,本质上它使用几个表来存储对象的动态属性。这个解决方案的很大部分是它:

    • 使用几个纯粹和简单的Django模型来表示动态字段,这使得它易于理解和与数据库无关;
    • 允许你能有效附接/拆卸的动态属性存储在模块中使用的模型与像简单的命令:

      eav.unregister(Encounter) 
      eav.register(Patient) 
      
    • Nicely integrates with Django admin;

    • 同时真的很强大。

    缺点:

    • 效率不高。这更多的是对EAV模式本身的批评,这需要手动将数据从列格式合并到模型中的一组键值对中。
    • 难以维护。保持数据完整性需要多列唯一键约束,这在某些数据库中可能效率低下。
    • 您将需要选择one of the forks,因为官方套餐不再保留,并且没有明确的领导者。

    用法是非常简单的:

    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. Hstore,JSON或JSONB PostgreSQL中

    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的壳,你可以使用它像这样:

    >>> 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可编码的数据类型,而不仅仅是键/值e对,但也往往更快,并且(对于JSONB)比Hstore更紧凑。 几个包实现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) 
    

    创建的外壳:

    >>> 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') 
    
  3. Django MongoDB

    或者其他NoSQL的Django的调整 - 他们可以有充分的动态模型。

    的NoSQL的Django库是伟大的,但要记住,他们是不是100%Django的兼容,例如,从标准的Django迁移到Django-nonrel你需要除其他事情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')] 
    ) 
    
  4. Django-mutant: Dynamic models based on syncdb and South-hooks

    Django-mutant工具完全动态的外键和M2M领域。受到Will Hardy和迈克尔霍尔令人难以置信但有点骇人的解决方案的启发。

    所有这些都是基于Django的南钩,其中,根据Will Hardy's talk at DjangoCon 2011(看它!)仍然是稳健和生产(relevant source code)进行测试。

    首先到implement thisMichael Hall

    是的,这是神奇的,通过这些方法您可以实现完全动态的Django应用程序,模型和字段与任何关系数据库后端。但费用是多少?应用程序的稳定性是否会受到重用?这些是需要考虑的问题。您需要确保保持适当的lock以允许同时发生数据库变更请求。

    如果您使用的迈克尔·霍尔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', 
           ) 
    
+31

这是相当该死的彻底。 – Edwin

+0

@GDorn,谢谢你的更新! –

+2

最近在DjangoCon 2013 Europe上讨论了这个话题:http://www.slideshare.net/schacki/django-dynamic-models20130502?from_search=2和http://www.youtube.com/watch?v=67wcGdk4aCc –

3

进一步的研究表明,这是Entity Attribute Value设计模式,已Django的实施中有些特殊情况下,通过几个包装。

首先,有原始eav-django项目,它在PyPi上。其次,第一个项目django-eav的更新分支主要是允许在第三方应用中使用django自己的模型或模型使用EAV的重构。

+0

我会将它包含在wiki中。 –

+1

我会反过来说,EAV是动态建模的一个特例。它主要用于“语义网络”社区,如果它包含一个唯一的ID,则称其为“三重”或“四重”。但是,它不可能像动态创建和修改SQL表的机制一样高效。 – Cerin

+0

@GDom是eav-django的首选吗?我的意思是你选择了上面的哪个选项? – Moreno

13

我一直在努力进一步推动django-dynamo的想法。该项目仍然没有记录,但您可以通过https://github.com/charettes/django-mutant阅读代码。

其实FK和M2M字段(请参阅contrib.related)也可以工作,甚至可以为自己的自定义字段定义封装。

还支持模型选项,如unique_together和订购加模型基础,所以你可以继承模型代理,抽象或mixin。

我实际上正在研究一个不在内存中的锁定机制,以确保模型定义可以跨多个django运行实例共享,同时防止它们使用过时的定义。

该项目仍然非常的alpha,但它是我的一个项目的基石技术,所以我必须把它带到生产准备。大计划也支持django-nonrel,所以我们可以利用mongodb驱动程序。

+1

嗨,西蒙!我刚刚在github上创建了它,在[我的wiki答案](http://stackoverflow.com/a/7934577/497056)中包含了一个到您的项目的链接。 :)))很高兴看到你在stackoverflow! –