Django 动态模型字段

2024-12-27 08:47:00
admin
原创
128
摘要:问题描述:我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后者使得 JSONField 不是一个很好的选择,因此我有以下解决方案:class CustomDataField(models.Model): """...

问题描述:

我正在开发一个多租户应用程序,其中一些用户可以定义自己的数据字段(通过管理员)以收集表单中的其他数据并报告数据。后者使得 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 如何具有指向 Site 的 ForeignKey - 每个 Site 将具有一组不同的自定义数据字段,但使用相同的数据库。然后可以将各种具体数据字段定义为:

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,如上所列,因为它不能很好地用于查询。


解决方案 1:

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

  1. Django-eav(原始软件包不再维护,但有一些蓬勃发展的分支

该解决方案基于实体属性值数据模型,本质上,它使用多个表来存储对象的动态属性。该解决方案的优点在于:

* 使用几个纯粹而简单的 Django 模型来表示动态字段,这使得它易于理解且与数据库无关;
* 允许你使用简单的命令有效地将动态属性存储附加/分离到 Django 模型,例如:


eav.unregister(Encounter)
eav.register(Patient)
* **与 Django 管理员很好地集成**;
* 同时也非常强大。缺点:
* 效率不高。这更多的是对 EAV 模式本身的批评,它需要手动将数据从列格式合并到模型中的一组键值对中。
* 维护起来比较困难。维护数据完整性需要多列唯一键约束,这在某些数据库上可能效率低下。
* 您需要选择其中一个分支,因为官方包不再维护,也没有明确的领导者。使用方法非常简单:
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,\n                                       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)
  1. 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 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')    

JSON字段

JSON/JSONB 字段支持任何 JSON 可编码数据类型,而不仅仅是键/值对,而且往往比 Hstore 更快且(对于 JSONB)更紧凑。包括django-pgfields在内的几个包都实现了 JSON/JSONB 字段,但从 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, 'nested': {'c':3}}
           )

索引查询与 HStoreField 几乎相同,只是可以嵌套。复杂索引可能需要手动创建(或脚本迁移)。

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

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

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

您甚至可以创建任何 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')]
)
  1. Django-mutant:基于 syncdb 和 South-hooks 的动态模型

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

所有这些都基于 Django South 钩子,根据Will Hardy 在 2011 年 DjangoCon 上的演讲 (观看!),这些钩子仍然是健壮的并且经过了生产测试(相关源代码)。

第一个实现这一目标的人是迈克尔·霍尔 (Michael Hall)。

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

如果您使用 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',
               )

解决方案 2:

我一直在努力进一步推动 django-dynamo 理念。该项目仍未记录,但您可以在https://github.com/charettes/django-mutant上阅读代码。

实际上 FK 和 M2M 字段(参见 contrib.related)也有效,甚至可以为您自己的自定义字段定义包装器。

还支持模型选项,例如 unique_together 和 ordering 以及模型基础,以便您可以子类化模型代理、抽象或混合。

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

该项目仍处于 alpha 阶段,但它是我其中一个项目的基石技术,因此我必须将其投入生产。大计划是支持 django-nonrel,以便我们可以利用 mongodb 驱动程序。

解决方案 3:

进一步的研究表明,这是一个实体属性值设计模式的特殊情况,该模式已由一些包为 Django 实现。

首先,有原始的eav-django项目,它位于 PyPi 上。

其次,第一个项目有一个较新的分支,django-eav,它主要是重构,以允许将 EAV 与 django 自己的模型或第三方应用程序中的模型一起使用。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用