Django-eav (le paquet d'origine n'est plus maintenu mais a quelques fourches prospères )
Cette solution est basée sur le modèle de données Entity Attribute Value , essentiellement, elle utilise plusieurs tables pour stocker les attributs dynamiques des objets. Les avantages de cette solution sont 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 de la base de données;
vous permet d'attacher / détacher efficacement le stockage d'attributs dynamiques au modèle Django avec des commandes simples comme:
eav.unregister(Encounter)
eav.register(Patient)
S'intègre parfaitement à l'admin Django ;
En même temps, il est vraiment puissant.
Inconvénients:
- Pas très efficace. Il s'agit davantage 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 l' une des fourches , car le package officiel n'est plus maintenu et il n'y a pas de leader clair.
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 prend en charge plusieurs types de données plus complexes. La plupart sont pris en charge via des packages tiers, mais ces dernières années, Django les a adoptés dans django.contrib.postgres.fields.
HStoreField :
Django-hstore était à l'origine un package tiers, mais Django 1.8 a ajouté HStoreField en tant que module intégré, ainsi que plusieurs autres types de champs pris en charge par PostgreSQL.
Cette approche est bonne en ce sens qu'elle vous permet d'avoir le meilleur des deux mondes: champs dynamiques et base de données relationnelle. Cependant, hstore n'est pas idéal en termes de performances , en particulier si vous allez finir par stocker des milliers d'éléments dans un seul champ. Il ne prend également en charge que les chaînes de 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 JSON, pas seulement les paires clé / valeur, mais ont également tendance à être plus rapides et (pour JSONB) plus compacts que Hstore. Plusieurs packages implémentent des champs JSON / JSONB, y compris 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 de gros dictionnaires. Il prend également en charge les types autres que les chaînes, 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 par script).
>>> 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 Django - avec elles, vous pouvez avoir des modèles entièrement dynamiques.
Les bibliothèques NoSQL Django sont excellentes, mais gardez à l'esprit qu'elles ne sont pas 100% compatibles avec Django, par exemple, pour migrer vers Django-nonrel depuis Django standard, vous devrez remplacer ManyToMany par ListField entre autres.
Découvrez cet exemple 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 des listes intégrées de tous les modèles 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 implémente des champs de clé étrangère et m2m entièrement dynamiques. Et s'inspire des solutions incroyables mais quelque peu hackers de Will Hardy et Michael Hall.
Tous sont basés sur les hooks Django South, qui, selon le discours de Will Hardy à DjangoCon 2011 (regardez-le!) Sont néanmoins robustes et testés en production ( code source pertinent ).
Le premier à mettre en œuvre ce fut Michael Hall .
Oui, c'est magique, avec ces approches, vous pouvez créer des applications, des modèles et des champs Django entièrement dynamiques avec n'importe quel backend de base de données relationnelle. Mais à quel prix? La stabilité de l'application souffrira-t-elle d'une utilisation intensive? Telles sont les questions à considérer. Vous devez vous assurer de maintenir un verrou approprié afin d'autoriser les demandes de modification simultanées de la base de données.
Si vous utilisez la librairie 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',
)