Je travaille sur une application multi-locataire 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 rendre compte des données. Ce dernier bit 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 le 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:
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 maladroit, en particulier 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é rejetées de manière préventive:
- SQL personnalisé pour modifier les tables à la volée. En partie parce que cela ne va pas à l'échelle et en partie parce que c'est trop un hack.
- Solutions sans schéma comme NoSQL. Je n'ai rien contre eux, mais ils ne conviennent toujours pas. Au final, ces données sont saisies et il est possible d'utiliser une application de reporting tierce.
- JSONField, comme indiqué ci-dessus, car il ne fonctionnera pas correctement avec les requêtes.
Réponses:
À ce jour, quatre approches sont disponibles, dont deux nécessitent un certain backend de stockage:
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:
vous permet d'attacher / détacher efficacement le stockage d'attributs dynamiques au modèle Django avec des commandes simples comme:
S'intègre parfaitement à l'admin Django ;
En même temps, il est vraiment puissant.
Inconvénients:
L'utilisation est assez simple:
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.
Dans le shell de Django, vous pouvez l'utiliser comme ceci:
Vous pouvez émettre des requêtes indexées sur les champs hstore:
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.
Création dans le shell:
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).
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:
Vous pouvez même créer des listes intégrées de tous les modèles Django:
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:
la source
J'ai travaillé sur l'idée de django-dynamo plus loin. Le projet n'est toujours pas documenté mais vous pouvez lire le code sur https://github.com/charettes/django-mutant .
En fait, 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.
Il existe également un support pour les options de modèle telles que unique_together et ordering plus Model bases afin que vous puissiez sous-classer des modèles proxy, abstract ou mixins.
Je travaille actuellement sur un mécanisme de verrouillage non en mémoire pour m'assurer que les définitions de modèle peuvent être partagées entre plusieurs instances en cours d'exécution de django 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 est également de prendre en charge django-nonrel afin que nous puissions tirer parti du pilote mongodb.
la source
Des recherches plus poussées révèlent qu'il s'agit d'un cas quelque peu particulier de modèle de conception de valeur d'attribut d'entité , qui a été implémenté pour Django par quelques packages.
Tout d'abord, il y a le projet original eav-django , qui se trouve sur PyPi.
Deuxièmement, il y a un fork plus récent du premier projet, django-eav, qui est principalement un refactor pour permettre l'utilisation d'EAV avec les propres modèles ou modèles de django dans des applications tierces.
la source