Django动态模型字段 [英] Django dynamic model fields

查看:759
本文介绍了Django动态模型字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一种多租户应用程序,其中一些用户可以通过管理员定义自己的数据字段,以收集表单中的其他数据并报告数据。后一位使JSONField不是一个很好的选择,所以我有以下解决方案:

  class CustomDataField(models.Model): 

任意数据字段的抽象规范
不用于保存数据本身,而是用于字段的元数据

site = models。 ForeignKey(Site,default = settings.SITE_ID)
name = models.CharField(max_length = 64)

class Meta:
abstract = True

类CustomDataValue(models.Model):

任意数据的抽象规范

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 )#可能在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,这甚至是甚么?

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



先占用的选项:




  • 要修改的自定义SQL桌上飞行。部分原因是这不会扩展,部分原因是因为它太多了。

  • 无数据解决方案如NoSQL。我没有反对他们,但他们还不是很好。最终这个数据键入,并且可能存在使用第三方报告应用程序。

  • JSONField,如上所列,因为它不会很好带有查询。


解决方案

截至今天,方法,其中两个需要一定的存储后端:


  1. Django-eav (原始包不再是mantained,但有一些 繁荣的叉子



    此解决方案基于实体属性值数据模型,基本上是使用几个表来存储对象的动态属性。关于这个解决方案的很大的部分是:




    • 使用几个纯粹简单的Django模型来表示动态字段,这使得它很容易理解,数据库无关的;

    • 允许您使用简单的命令有效地将动态属性存储附加/分离到Django模型,例如:

        eav.unregister(Encounter)
      eav.register(Patient)


    • 与Django管理员完美整合 ;


    • 同时真的很强大。




    下降:




    • 效果不是很好。这更多地是对EAV模式本身的批评,这需要手动将列格式的数据合并到模型中的一组键值对。

    • 难以维护。维护数据完整性需要一个多列唯一的密钥约束,这在一些数据库上可能是低效的。

    • 您将需要选择其中一个叉子,因为官方包不再维护,没有明确的领导者。



    用法非常简单:

     从应用程序导入eav 
    。模型进口病人,遇到

    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)
    属性.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 =枚举组.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)

    #当您在EAV中注册模型时,
    #可以访问所有EAV属性:

    Patient.objects.create(name ='Bob',eav__age = 12,
    eav__fever = no,eav__city ='New York',
    eav__country ='USA')
    #您可以根据其EAV字段过滤查询:

    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支持更复杂的数据类型。大多数都是通过第三方软件包得到支持的,但是近年来,Django已将它们引入到django.contrib.postgres.fields中。



    HStoreField



    Django-hstore 原本是第三方软件包,但Django 1.8添加了 HStoreField ,以及其他几个PostgreSQL支持的字段类型。



    这种方法在某种意义上是很好的你有两个世界最好的:动态领域和关系数据库。然而,hstore是不理想的性能方面的,特别是如果您将最终在一个字段中存储数千个项目。它也只支持字符串的值。

     #app / models.py 
    from django.contrib.postgres.fields导入HStoreField
    class Something(models.Model):
    name = models.CharField(max_length = 32)
    data = models.HStoreField(db_index = True)

    在Django的shell中,您可以使用它:

     code>>>> instance = Something.objects.create(
    name ='something',
    data = {'a':'1','b':'2'}

    >>> instance.data ['a']
    '1'
    >>>空= Something.objects.create(name ='empty')
    >>> empty.data
    {}
    >>> empty.data ['a'] ='1'
    >>> empty.save()
    >>>> Something.objects.get(name ='something')。data ['a']
    '1'

    您可以针对hstore字段发出索引查询:

     #equivalentence 
    Something.objects.filter data = {'a':'1','b':'2'})

    #通过键/值映射子集
    Something.objects.filter(data__a ='1' )

    #个子列表的键
    Something.objects.filter(data__has_keys = ['a','b'])


    Something.objects.filter(data__has_key ='a')

    JSONField



    JSON / JSONB字段支持任何JSON编码数据类型,而不仅仅是键/值对,但也往往更快,(对于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)

    在shell中创建:

     >>> instance = Something.objects.create(
    name ='something',
    data = {'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')


  3. Django MongoDB



    NoSQL Django库是伟大的,但请记住,他们不是100%例如,与Django兼容,可以从标准的Django中迁移到 Django-nonrel 将需要使用 ListField 等等。



    查看Django MongoDB示例:

     从djangotoolbox.fields导入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的嵌入式列表模型:

      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基于syncdb和南钩的模型



    Django-mutant 实现了完全动态的外键和m2m字段。并且受到 Will Hardy 和Michael的令人难以置信但有些黑客的解决方案的启发Hall。



    所有这些都是基于Django South的钩子,根据 Will Hardy在DjangoCon 2011的谈话 (观看!)在生产中非常健壮和测试(相关源代码)。



    首先实施这个 Michael Hall



    是的,这是魔术,通过这些方法,您可以实现完全动态的Django应用程序,模型d字段与任何关系数据库后端。但是在什么成本?应用程序的稳定性会受到大量使用吗?这些是要考虑的问题。您需要确保按顺序维护正确的锁定允许同时进行数据库更改请求。



    如果您使用的是Michael Halls 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 ='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 ='Bar'的测试字段,



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.

解决方案

As of today, there are four available approaches, two of them requiring a certain storage backend:

  1. 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:

    • uses several pure and simple Django models to represent dynamic fields, which makes it simple to understand and database-agnostic;
    • allows you to effectively attach/detach dynamic attribute storage to Django model with simple commands like:

      eav.unregister(Encounter)
      eav.register(Patient)
      

    • Nicely integrates with Django admin;

    • At the same time being really powerful.

    Downsides:

    • Not very efficient. This is more of a criticism of the EAV pattern itself, which requires manually merging the data from a column format to a set of key-value pairs in the model.
    • Harder to maintain. Maintaining data integrity requires a multi-column unique key constraint, which may be inefficient on some databases.
    • You will need to select one of the forks, since the official package is no longer maintained and there is no clear leader.

    The usage is pretty straightforward:

    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 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.

    #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)
    

    In Django's shell you can use it like this:

    >>> 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'
    

    You can issue indexed queries against hstore fields:

    # 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 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.

    #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)
    

    Creating in the shell:

    >>> instance = Something.objects.create(
                     name='something',
                     data={'a': 1, 'b': 2, 'nested': {'c':3}}
               )
    

    Indexed queries are nearly identical to HStoreField, except nesting is possible. Complex indexes may require manually creation (or a scripted migration).

    >>> Something.objects.filter(data__a=1)
    >>> Something.objects.filter(data__nested__c=3)
    >>> Something.objects.filter(data__has_key='a')
    

  3. 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:

    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, ...}
    

    You can even create embedded lists of any Django models:

    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 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:

    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动态模型字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆