python - Django 动态模型字段

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

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 如何具有指向站点的外键 - 每个站点将具有一组不同的自定义数据字段,但使用相同的数据库。
那么各个具体的数据字段可以定义为:
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,如上所列,因为它不适用于查询。
  • 最佳答案

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

  • Django-eav (原始包不再维护,但有一些 thriving forks )

    此解决方案基于 Entity Attribute Value数据模型,本质上,它使用几个表来存储对象的动态属性。这个解决方案的重要部分是它:
  • 使用几个纯粹简单的 Django 模型来表示动态字段,这使得它易于理解且与数据库无关;
  • 允许您使用简单的命令有效地将动态属性存储附加/分离到 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)
    
  • PostgreSQL 中的 Hstore、JSON 或 JSONB 字段

    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 的 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 字段,包括 django-pgfields ,但从 Django 1.9 开始, JSONField 是使用 JSONB 进行存储的内置。
    JSONField 类似于 HStoreField,并且可能在大型字典中表现更好。它还支持字符串以外的类型,例如整数、 bool 值和嵌套字典。
    #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')
    
  • Django MongoDB

    或其他 NoSQL Django 改编版——通过它们,您可以拥有完全动态的模型。

    NoSQL Django 库很棒,但请记住,它们不是 100% 与 Django 兼容的,例如,迁移到 Django-nonrel在标准 Django 中,您需要将 ManyToMany 替换为 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')]
    )
    
  • Django-mutant: Dynamic models based on syncdb and South-hooks

    Django-mutant实现完全动态的外键和 m2m 字段。并且受到了 Will Hardy 令人难以置信但有点骇人听闻的解决方案的启发和迈克尔·霍尔。

    所有这些都基于 Django South hooks,根据 Will Hardy's talk at DjangoCon 2011 (注意!)尽管如此,它仍然健壮并在生产中进行了测试 ( relevant source code )。

    先到implement this是 Michael Hall .

    是的,这很神奇,通过这些方法您可以实现 完全动态的 Django 应用程序、模型和字段 与任何关系数据库后端。但代价是什么?大量使用会影响应用程序的稳定性吗?这些都是需要考虑的问题。您需要确保保持适当的lock以允许同时更改数据库的请求。

    如果您使用 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',
                   )
    
  • https://stackoverflow.com/questions/7933596/

    相关文章:

    python - python中的assertEquals与assertEqual

    python - 子进程命令的实时输出

    linux - Bash 脚本在空行上打印 "Command Not Found"

    for-loop - 检测 'for'循环中最后一个元素的pythonic方法是什么?

    c - 将输出重定向到文件时 printf() 和 system() 的结果顺序错误

    c# - 在 Linux 上开发 C#

    python - 如何在 Python 中创建 PDF 文件

    linux - 如何在不停止的情况下在 Docker 容器中运行 Nginx?

    c - Linux 中的 PATH_MAX 定义在哪里?

    python - Ruby 相当于 virtualenv?