Je travaille sur une application multi-tenanted dans laquelle certains utilisateurs peuvent définir leurs propres champs de données (via l'administrateur) pour collecter des données supplémentaires dans des formulaires et générer des rapports à partir de ces données. Ce dernier élément ne fait pas de JSONField une excellente option. J'ai donc la solution suivante:
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
Notez comment CustomDataField a une clé étrangère vers site - chaque site aura un ensemble différent de champs de données personnalisés, mais utilisera la même base de données. Ensuite, les différents champs de données concrets peuvent être définis comme suit:
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'),)
Cela conduit à l'utilisation suivante:
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?
Mais cela semble très fastidieux, notamment avec la nécessité de créer manuellement les données associées et de les associer au modèle concret. Est-ce qu'il y a une meilleure approche?
Options qui ont été supprimées de manière préventive:
À ce jour, il existe quatre approches, deux d'entre elles nécessitant un certain stockage:
Django-eav (le paquet d'origine n'est plus conservé mais en contient - fourches en plein essor)
Cette solution est basée sur le modèle de données Valeur d'attribut d'entité . Elle utilise essentiellement plusieurs tables pour stocker les attributs dynamiques des objets. Les points forts de cette solution sont les suivants:
vous permet d'attacher/détacher efficacement le stockage d'attributs dynamiques à Django modèle avec des commandes simples comme:
eav.unregister(Encounter)
eav.register(Patient)
Dans le même temps, être vraiment puissant.
Inconvénients:
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 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 ajouté HStoreField intégré, ainsi que plusieurs autres types de champs pris en charge par PostgreSQL.
Cette approche est bonne dans la mesure où elle vous permet d’avoir le meilleur des deux mondes: champs dynamiques et base de données relationnelle. Cependant, hstore est pas la performance idéale , surtout si vous allez stocker des milliers d’éléments dans un seul champ. En outre, il ne prend en charge que les chaînes 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 Django's Shell, 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 en JSON, pas seulement les paires clé/valeur, mais tendent également à être plus rapides et (pour JSONB) plus compacts que Hstore. Plusieurs paquets implémentent des champs JSON/JSONB, notamment Django-pgfields, mais à partir de Django 1.9, JSONField est intégré à JSONB pour le stockage. JSONField est similaire à HStoreField et peut être plus performant avec des dictionnaires volumineux. Il prend également en charge des 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éer 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')
Ou d’autres adaptations NoSQL Django - vous pourrez ainsi disposer de modèles entièrement dynamiques.
Les bibliothèques NoSQL Django sont excellentes, mais gardez à l'esprit qu'elles ne sont pas compatibles à 100% avec Django, par exemple, pour migrer vers Django-nonrel à partir de standard Django vous devrez remplacer ManyToMany par ListField , entre autres.
Checkout this Django Exemple de 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 any Django modèles:
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 entièrement dynamiques de clé étrangère et m2m. Et est inspiré par des solutions incroyables mais quelque peu furtives de Will Hardy et Michael Hall.
Tous ces éléments sont basés sur Django South hooks, ce 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 à implémenter ceci était Michael Hall .
Oui, c'est magique, avec ces approches vous pouvez réaliser entièrement dynamique Django applications, modèles et champs avec n'importe quel backend de base de données relationnelle. Mais à quel prix? La stabilité de l'application sera-t-elle compromise lors d'une utilisation intensive? Ce sont les questions à considérer. Vous devez vous assurer de conserver un correct lock afin de permettre les demandes de modification simultanée de la base de données.
Si vous utilisez Michael Halls lib, 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',
)
Je travaille à pousser plus loin l'idée de Django-dynamo. Le projet n'est toujours pas documenté mais vous pouvez lire le code à l'adresse https://github.com/charettes/Django-mutant .
En réalité, les champs FK et M2M (voir contrib.related) fonctionnent également et il est même possible de définir un wrapper pour vos propres champs personnalisés.
Des options de modèle telles que unique_together et ordering plus Model Model sont également prises en charge afin que vous puissiez sous-classer des modèles de proxy, de résumé ou de mixine.
Je travaille actuellement sur un mécanisme de verrouillage qui ne fonctionne pas en mémoire pour m'assurer que les définitions de modèle peuvent être partagées entre plusieurs Django instances en cours, tout en les empêchant d'utiliser une définition obsolète.
Le projet est toujours très alpha mais c'est une technologie de base pour l'un de mes projets, je vais donc devoir le mettre en production. Le grand plan soutient également Django-nonrel afin que nous puissions utiliser le pilote mongodb.
Des recherches ultérieures révèlent qu'il s'agit d'un cas un peu particulier du modèle de conception valeur d'attribut d'entité , qui a été implémenté pour Django par quelques paquets.
Premièrement, il y a le projet original eav-Django , qui est sur PyPi.
Deuxièmement, il y a un fork plus récent du premier projet, Django-eav , qui est principalement un refactor permettant d'utiliser EAV avec les propres modèles de Django ou des modèles dans des applications tierces.