Ответ 1
На сегодняшний день существует четыре доступных подхода, два из которых требуют определенного хранилища:
-
Django-eav (исходный пакет больше не поддерживается, но имеет несколько процветающие вилки)
Это решение основано на модели данных Entity Attribute Value, по сути, она использует несколько таблиц для хранения динамических атрибутов объектов. Большая часть этого решения заключается в том, что он:
- использует несколько чистых и простых моделей Django для представления динамических полей, что упрощает понимание и агностику базы данных;
-
позволяет эффективно прикреплять/отсоединять хранилище динамических атрибутов к модели Django с помощью простых команд, таких как:
eav.unregister(Encounter) eav.register(Patient)
-
В то же время он действительно мощный.
Downsides:
- Не очень эффективно. Это скорее критика самого шаблона 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,\ 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)
-
Поля Hstore, JSON или JSONB в PostgreSQL
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 вы можете использовать его следующим образом:
>>> 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, а не только пары ключ/значение, но также имеют тенденцию быть более быстрыми и (для 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')
-
Или другие адаптеры NoSQL Django - с ними вы можете иметь полностью динамические модели.
Библиотеки Django NoSQL отличные, но имейте в виду, что они не на 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, ...}
Вы даже можете создать встроенные списки любых моделей 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: Динамические модели, основанные на syncdb и South-hooks
Django-mutant реализует полностью динамические поля Foreign Key и m2m. И вдохновляется невероятными, но довольно хакерскими решениями Will Hardy и Michael Hall.
Все они основаны на крючках Django South, которые, согласно Будет ли Hardy говорить на DjangoCon 2011 (смотри!), тем не менее, надежны и протестированы в производстве (соответствующий исходный код).
Сначала реализовать это было Michael Hall.
Да, это волшебство, с помощью этих подходов вы можете достичь полностью динамических приложений, моделей и полей Django с помощью любого реляционного баз данных. Но какой ценой? Будет ли стабильность приложения страдать от интенсивного использования? Это вопросы, которые необходимо учитывать. Вы должны быть уверены, что поддерживаете правильный 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', )