我正在开发一个multi-tenanted版本的应用程序,其中一些用户可以定义他们自己的数据字段(通过管理员)来收集表单中的附加数据和数据报告.后一位使JSONField不是很好的 Select ,因此我有以下解决方案:

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这样的无模式解决方案.我对他们没什么意见,但他们还是不合适.最终,该数据is被键入,并且存在使用第三方报告应用程序的可能性.
  • JSONField,如上所述,因为它不能很好地处理查询.

推荐答案

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

  1. 100(原来的套餐不再受管理,但有大约101个)

    该解决方案基于Entity Attribute Value数据模型,本质上,它使用多个表来存储对象的动态属性.这个解决方案的优点在于:

    • 使用几个纯简单的Django模型来表示动态字段,这使得它易于理解,并且与数据库无关;
    • 使用以下简单命令,可以有效地将动态属性存储附加/分离到Django模型:

      eav.unregister(Encounter)
      eav.register(Patient)
      
    • 100;

    • 同时又非常强大.

    缺点:

    • 效率不是很高.这更多的是对EAV模式本身的批评,它需要手动将数据从列格式合并到模型中的一组键-值对.
    • 更难维护.维护数据完整性需要多列唯一键约束,这在某些数据库上可能效率低下.
    • 您将需要 Select 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 or JSONB fields in PostgreSQL

    PostgreSQL支持几种更复杂的数据类型.大多数都是通过第三方软件包来支持的,但是近年来Django已经将它们采用到了django.contri.postgres.field中.

    HStoreField:

    Django-hstore最初是一个第三方包,但Django 1.8增加了101作为内置包,以及其他几种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的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编码的数据类型,不仅键/值对,而且往往比Hstore更快(对于JSONB)更紧凑. 有几个包实现了JSON/JSONB字段,包括100,但是在Django 1.9中,101是内置的,使用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, '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. 100

    或其他NoSQL Django改编--使用它们,您可以拥有完全动态的模型.

    NoSQL Django库非常好,但请记住,它们不是Django-nonrel%与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, ...}
    

    您甚至可以创建embedded lists个任意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. 100

    Django-mutant实现完全动态的外键和M2M字段.灵感来自Will Hardy和迈克尔·霍尔令人难以置信但有点老套的解决方案.

    所有这些都以Django South hooks为基础,根据Will Hardy's talk at DjangoCon 2011 (watch it!)的说法,Django South hooks仍然很 Solidity ,并在生产中经过了测试(relevant source code).

    从第一到implement thisMichael Hall.

    是的,这很神奇,通过这些方法,您可以在任何关系数据库后端实现fully dynamic Django apps, models and fields.但代价是什么?大量使用会影响应用的 solidity 吗?这些是需要考虑的问题.您需要确保保持适当的lock,以便允许同时更改数据库请求.

    如果您使用的是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 = '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相关问答推荐

如果密码在Django中未被散列,则对其进行散列

Django ORM ForeignKey查询使用注释设置输出

如何在Django模板中有条件地传递值给with变量?

django 的 Manager.create() 方法有什么作用?

Django 如何知道我的数据库的路径?

在用例图中建模前端和后端

PyCharm:强制 Django 模板语法突出显示

Django PositiveIntegerField 中的 0 值?

Django 1.9:字段与父模型中不存在的字段的字段冲突

如何使用自定义 AdminSite 类?

relation "django_site" does not exist

Django 管理命令参数

django - 如何在验证之前处理/清理字段

在基于类的通用视图 CreateView 中访问 request.user 以便在 Django 中设置 FK 字段

使用 XMLHttpRequest 提示下载文件

在 Django 中获取下一个和上一个对象

在 Django 中使用邮箱地址或用户名登录用户

在 Django 开发服务器中关闭静态文件的缓存

找不到 msguniq.确保您安装了 GNU gettext 工具 0.15 或更新版本. (Django 1.8 和 OSX ElCapitan)

在 Django 中使用 select_related Select 特定字段