Я работаю над приложением multi-tenanted, в котором некоторые пользователи могут определять свои собственные поля данных (через администратора) для сбора дополнительных данных в формах и отчета по данным. Последний бит делает 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 имеет ExternalKey для сайта - каждый сайт будет иметь другой набор настраиваемых полей данных, но использовать одну и ту же базу данных. Затем различные конкретные поля данных можно определить как:
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?
Но это кажется очень неуклюжим, особенно с необходимостью вручную создавать связанные данные и связывать их с конкретной моделью. Есть ли лучший подход?
Параметры, предварительно упущенные:
На сегодняшний день существует четыре доступных подхода, два из которых требуют определенного хранилища:
Django-eav (исходный пакет больше не поддерживается, но имеет несколько процветающие вилки)
Это решение основано на модели данных Entity Attribute Value, по сути, она использует несколько таблиц для хранения динамических атрибутов объектов. Большая часть этого решения заключается в том, что он:
позволяет эффективно прикреплять/отсоединять хранилище динамических атрибутов к модели Django с помощью простых команд, таких как:
eav.unregister(Encounter)
eav.register(Patient)
В то же время он действительно мощный.
Downsides:
Использование довольно просто:
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',
)
Я работаю над продвижением идеи джанго-динамо. Проект по-прежнему недокументирован, но вы можете прочитать код https://github.com/charettes/django-mutant.
Собственно, поля FK и M2M (см. contrib.related) также работают, и даже можно определить оболочку для собственных пользовательских полей.
Там также поддерживаются параметры модели, такие как unique_together и ordering плюс базы моделей, поэтому вы можете подклассифицировать прокси-объекты модели, абстрактные или mixins.
Я на самом деле работаю над механизмом блокировки без памяти, чтобы убедиться, что определения моделей могут использоваться совместно с несколькими экземплярами запуска django, не допуская при этом использования устаревшего определения.
Проект по-прежнему очень альфа, но это краеугольная технология для одного из моих проектов, поэтому я должен буду довести его до готовой продукции. Большой план поддерживает django-nonrel, поэтому мы можем использовать драйвер mongodb.
Дальнейшие исследования показывают, что это несколько частный случай значение атрибута Entity, который был реализован для Django несколькими пакетами.
Во-первых, существует оригинальный проект eav-django, который находится на PyPi.
Во-вторых, есть более новая вилка первого проекта, django-eav, которая в основном является рефактором, позволяющим использовать EAV с собственным django моделей или моделей в сторонних приложениях.