python français - Champs de modèles dynamiques Django





tutorial apprendre (4)


À ce jour, il existe quatre approches disponibles, dont deux nécessitent un certain backend de stockage:

  1. Django-eav (le paquet original n'est plus entretenu 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 grandes parties de cette solution sont les suivantes:

    • utilise plusieurs modèles Django purs et simples pour représenter les champs dynamiques, ce qui le rend simple à comprendre et agnostique pour les bases de données;
    • vous permet d'attacher ou de détacher efficacement le stockage dynamique d'attributs au modèle Django avec des commandes simples comme:

      eav.unregister(Encounter)
      eav.register(Patient)
      
    • S'intègre bien avec l'administrateur Django ;

    • En même temps, être vraiment puissant.

    Les inconvénients:

    • Pas très efficace. Ceci est plus 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 à maintenir. Le maintien de l'intégrité des données nécessite une contrainte de clé unique sur plusieurs colonnes, ce qui peut s'avérer inefficace sur certaines bases de données.
    • Vous devrez sélectionner l' une des fourchettes , car le paquet 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)
    
  2. Champs Hstore, JSON ou JSONB dans PostgreSQL

    PostgreSQL supporte plusieurs types de données plus complexes. La plupart sont supportés via 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é HStoreField comme un type intégré, avec 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: les champs dynamiques et la base de données relationnelle. Cependant, hstore n'est pas idéal en termes de performances , surtout si vous allez stocker des milliers d'éléments dans un même champ. Il prend également en charge uniquement 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 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 tout type de données codable JSON, pas seulement les paires clé / valeur, mais ont aussi tendance à être plus rapides et (pour JSONB) plus compacts que Hstore. Plusieurs paquets implémentent des champs JSON / JSONB incluant django-pgfields , mais à partir de Django 1.9, JSONField est un built-in utilisant JSONB pour le stockage. JSONField est similaire à HStoreField et peut mieux fonctionner avec les grands 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éer dans la coquille:

    >>> 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')
    
  3. Django MongoDB

    Ou d'autres adaptations de NoSQL Django - avec elles vous pouvez avoir des modèles complètement 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 notamment remplacer ManyToMany par ListField .

    Commander 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')]
    )
    
  4. Django-mutant: Modèles dynamiques basés sur syncdb et South-hooks

    Django-mutant implémente des champs Foreign Key et m2m entièrement dynamiques. Et est inspiré par des solutions incroyables mais quelque peu hackish par Will Hardy et Michael Hall.

    Tout cela est basé sur les hooks de Django South, qui, selon le discours de Will Hardy à DjangoCon 2011 (watch it!) Sont néanmoins robustes et testés en production ( code source pertinent ).

    Le premier à mettre en œuvre ceci était Michael Hall .

    Oui, c'est magique, avec ces approches, vous pouvez obtenir des applications, des modèles et des champs Django dynamiques avec n'importe quel backend de base de données relationnelle. Mais à quel prix? La stabilité de l'application va-t-elle souffrir lors d'une utilisation intensive? Ce sont les questions à considérer. Vous devez vous assurer de maintenir un lock approprié afin de permettre des modifications simultanées 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 sur une application multi-locataire dans laquelle certains utilisateurs peuvent définir leurs propres champs de données (via l'admin) pour collecter des données supplémentaires dans des formulaires et générer des rapports sur les données. Ce dernier bit rend JSONField pas une bonne option, donc j'ai 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 un ForeignKey sur 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éemptive:

  • SQL personnalisé pour modifier les tables à la volée. En partie parce que cela ne sera pas à l'échelle et en partie parce que c'est trop un hack.
  • Des solutions sans schéma comme NoSQL. Je n'ai rien contre eux, mais ils ne vont toujours pas bien. En fin de compte, 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 bien avec les requêtes.



J'ai travaillé sur pousser l'idée 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 une prise en charge des options de modèle, telles que le modèle unique_together et la commande, ainsi que les bases du modèle, ce qui vous permet de sous-classer le modèle proxy, le résumé ou les mixins.

Je travaille actuellement sur un mécanisme de verrouillage non en mémoire pour m'assurer que les définitions de modèles peuvent être partagées à travers plusieurs instances en cours d'exécution de django tout en les empêchant d'utiliser une définition obsolète.

Le projet est encore très alpha mais c'est une technologie de base pour un de mes projets, je vais donc devoir le mettre en production. Le grand plan supporte également django-nonrel afin que nous puissions tirer parti du pilote mongodb.




D'autres recherches révèlent qu'il s'agit d'un cas assez particulier de modèle de conception de l' attribut d'attribut d'entité , qui a été implémenté pour Django par quelques paquets.

Tout d'abord, il y a le projet original eav-django , qui est sur PyPi.

Deuxièmement, il y a une Django-eav plus récente 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.




Python 3 range() est le Python 2 xrange() . Si vous voulez simuler la range() Python 2 range() dans le code Python 3, vous devez utiliser la list(range(num) . Plus le nombre est grand, plus la différence sera grande avec votre code original.

L'indexation doit être indépendante de ce qui est stocké dans la liste, car la liste ne stocke que les références aux objets cibles. Les références sont non typées et toutes de même nature. Le type de liste est donc une structure de données homogène - techniquement. Indexation signifie transformer la valeur d'index en adresse de départ + offset. Le calcul du décalage est très efficace avec au plus une soustraction. Ceci est une opération supplémentaire très bon marché par rapport aux autres opérations.







python django dynamic django-models django-custom-manager