-
Django-eav (le paquet original n'est plus maintenu mais a quelques fourches prospères )
Cette solution est basée sur Valeur de l'attribut de l'entité modèle de données, essentiellement, il utilise plusieurs tables pour stocker les attributs dynamiques des objets. L'avantage de cette solution est qu'elle :
-
utilise plusieurs modèles Django purs et simples pour représenter les champs dynamiques, ce qui le rend simple à comprendre et indépendant des bases de données ;
-
vous permet d'attacher/détacher efficacement le stockage dynamique des attributs au modèle Django avec des commandes simples comme :
eav.unregister(Encounter)
eav.register(Patient)
-
S'intègre parfaitement à l'administration de Django ;
-
Tout en étant très puissant.
Inconvénients :
- Pas très efficace. Il s'agit plutôt d'une critique du modèle EAV lui-même, qui nécessite de fusionner manuellement les données d'un format de colonne à un ensemble de paires clé-valeur dans le modèle.
- Plus difficile à entretenir. Le maintien de l'intégrité des données nécessite une contrainte de clé unique à plusieurs colonnes, ce qui peut être inefficace sur certaines bases de données.
- Vous devrez sélectionner une des fourches puisque le paquet officiel n'est plus maintenu et qu'il n'y a pas de leader incontesté.
L'utilisation est assez simple :
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)
-
Champs Hstore, JSON ou JSONB dans PostgreSQL
PostgreSQL supporte plusieurs types de données plus complexes. La plupart sont pris en charge par des paquets tiers, mais ces dernières années, Django les a adoptés dans django.contrib.postgres.fields.
HStoreField :
Django-hstore était à l'origine un paquetage tiers, mais Django 1.8 a ajouté la fonction HStoreField comme un élément intégré, ainsi que plusieurs autres types de champs supportés par PostgreSQL.
Cette approche est bonne dans le sens où elle vous permet d'avoir le meilleur des deux mondes : champs dynamiques et base de données relationnelle. Cependant, hstore est pas idéal du point de vue des performances surtout si vous êtes amené à stocker des milliers d'éléments dans un seul champ. Il ne prend également en charge que les chaînes de caractères pour les valeurs.
#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)
Dans le shell de Django, vous pouvez l'utiliser comme ceci :
>>> 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'
Vous pouvez émettre des requêtes indexées sur les champs 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 :
Les champs JSON/JSONB prennent en charge tous les types de données encodables dans JSON, et pas seulement les paires clé/valeur. Ils ont également tendance à être plus rapides et (pour JSONB) plus compacts que Hstore. Plusieurs paquets implémentent les champs JSON/JSONB, notamment django-pgfields mais à partir de Django 1.9, JSONField est un intégré utilisant JSONB pour le stockage. JSONField est similaire à HStoreField, et peut être plus performant avec les grands dictionnaires. Il prend également en charge des types autres que les chaînes de caractères, tels que les entiers, les booléens et les dictionnaires imbriqués.
#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)
Création dans le shell :
>>> instance = Something.objects.create(
name='something',
data={'a': 1, 'b': 2, 'nested': {'c':3}}
)
Les requêtes indexées sont presque identiques à HStoreField, sauf que l'imbrication est possible. Les index complexes peuvent nécessiter une création manuelle (ou une migration scriptée).
>>> Something.objects.filter(data__a=1)
>>> Something.objects.filter(data__nested__c=3)
>>> Something.objects.filter(data__has_key='a')
-
Django MongoDB
Ou d'autres adaptations NoSQL de Django - avec elles, vous pouvez avoir des modèles entièrement dynamiques.
Les bibliothèques Django NoSQL sont géniales, mais gardez à l'esprit qu'elles ne sont pas 100% compatibles avec Django, par exemple, pour migrer vers Django-nonrel à partir de Django standard, vous devrez remplacer ManyToMany par ListField entre autres.
Consultez cet exemple de 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, ...}
Vous pouvez même créer listes intégrées de tout modèle 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 : Modèles dynamiques basés sur syncdb et South-hooks
Django-mutant met en œuvre des clés étrangères et des champs m2m entièrement dynamiques. Elle est inspirée par les solutions incroyables mais quelque peu bricolées de Will Hardy et Michael Hall.
Tous ces éléments sont basés sur les hooks Sud de Django, qui, selon Conférence de Will Hardy à la DjangoCon 2011 (regardez-le !) sont néanmoins robustes et testés en production ( le code source correspondant ).
Premier à mettre en œuvre cette était Michael Hall .
Oui, c'est magique, avec ces approches vous pouvez réaliser des applications, modèles et champs Django entièrement dynamiques avec n'importe quel backend de base de données relationnelle. Mais à quel prix ? La stabilité de l'application va-t-elle souffrir d'une utilisation intensive ? Telles sont les questions qu'il faut se poser. Vous devez vous assurer de maintenir une serrure afin de permettre des demandes simultanées de modification de la base de données.
Si vous utilisez la librairie de Michael Halls, votre code ressemblera à ceci :
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',
)