web-dev-qa-db-fra.com

Sérialisation des objets de clé étrangère dans Django

Je travaille au développement de certains services RESTful dans Django, destinés à être utilisés avec les applications Flash et Android. 

Le développement de l'interface de services a été assez simple, mais je rencontrais un problème de sérialisation des objets contenant une clé étrangère et de nombreuses relations.

J'ai un modèle comme celui-ci:

class Artifact( models.Model ):
    name                = models.CharField( max_length = 255 )
    year_of_Origin      = models.IntegerField( max_length = 4, blank = True, null = True )
    object_type         = models.ForeignKey( ObjectType, blank = True, null = True )
    individual          = models.ForeignKey( Individual, blank = True, null = True )
    notes               = models.TextField( blank = True, null = True )

Ensuite, j'exécuterais une requête sur ce modèle comme ceci, en utilisant select_related(), pour m'assurer que les relations de clé étrangère sont suivies:

artifact = Artifact.objects.select_related().get(pk=pk)

Une fois que j'ai l'objet, je le sérialise et le repasse à ma vue:

serializers.serialize( "json", [ artifact ] )

C'est ce que je récupère, notez que les clés étrangères (type_objet et individu) ne sont que les identifiants de leurs objets associés.

[
      {
            pk: 1
            model: "artifacts.artifact"
            fields: {
                year_of_Origin: 2010
                name: "Dummy Title"
                notes: ""
                object_type: 1
                individual: 1
            }
      }
]

C’est formidable, mais ce que j’espérais en utilisant select_related(), c’est qu’il remplirait automatiquement les champs de clé étrangère avec l’objet associé, et pas uniquement l’id de cet objet.

Je suis récemment converti à Django, mais j'ai passé pas mal de temps à développer CakePHP. 

Ce que j'ai vraiment aimé dans Cake ORM, c'est qu'il suivait les relations et créait des objets imbriqués par défaut, avec la possibilité de dissocier les relations lorsque vous appeliez votre requête. 

Cela a rendu très facile l'abstraction des services de manière à ne nécessiter aucune intervention au cas par cas.

Je vois que Django ne le fait pas par défaut, mais existe-t-il un moyen de sérialiser automatiquement un objet et tous ses objets associés? Toute astuce ou lecture serait très appréciée.

36
Kieran Lynn

J'avais une exigence similaire, mais pas à des fins de repos. J'ai pu réaliser ce dont j'avais besoin en utilisant un module de sérialisation "complet", dans mon cas Django Full Serializers . Cela fait partie de wadofstuff et est distribué sous la nouvelle licence BSD.

Wadofstuff rend cela assez facile. Par exemple dans votre cas, vous devez procéder comme suit:

Tout d'abord, installez wadofstuff.

Deuxièmement, ajoutez le paramètre suivant à votre fichier settings.py:

SERIALIZATION_MODULES = {
    'json': 'wadofstuff.Django.serializers.json'
}

Troisièmement, modifiez légèrement le code utilisé pour la sérialisation: 

artifact = Artifact.objects.select_related().get(pk=pk)
serializers.serialize( "json", [ artifact ], indent = 4, 
    relations = ('object_type', 'individual',))

Le changement clé est le paramètre de mot clé relations. Le seul problème (mineur) est d'utiliser le nom des champs formant la relation et non les noms des modèles associés.

Caveat

De la documentation :

Les sérialiseurs Wad of Stuff sont 100% compatibles avec les sérialiseurs Django lors de la sérialisation d'un modèle. Lors de la désérialisation d'un flux de données, la classe Deserializer ne fonctionne actuellement qu'avec les données sérialisées renvoyées par les sérialiseurs Django standard}.

(Soulignement ajouté)

J'espère que cela t'aides.

25
Manoj Govindan

MISE À JOUR: En fait, la solution de Manoj est un peu dépassée, le sérialiseur de Wad of Stuff n'a pas été mis à jour depuis un certain temps et lorsque j'ai essayé, il semble qu'il ne supporte plus Django 1.6. 

Cependant, jetez un oeil au le document officiel de Django ici . Il fournit un moyen de contourner l'utilisation de la clé naturelle intégrée. Il semble que le sérialiseur intégré de Django ait un petit problème à prendre en charge l'utilisation d'ImageField dans le cadre de la clé naturelle. Mais cela peut être facilement résolu par vous-même.

10
Shen Haocheng

Vous pouvez trouver plus d'informations sur ce billet:

Autorise la sérialisation approfondie en spécifiant que la profondeur doit suivre la relation https://code.djangoproject.com/ticket/4656

0
guettli

Ajout d'une réponse plus récente à cette question plus ancienne: J'ai créé et publié récemment Django-serializable-model comme moyen facilement extensible de sérialiser des modèles, des gestionnaires et des ensembles de requêtes. Lorsque vos modèles étendent la variable SerializableModel, ils reçoivent une méthode .serialize pouvant être remplacée, qui prend en charge de manière intégrée toutes les relations.

En utilisant votre exemple, une fois tous les modèles impliqués étendez SerializableModel:

joins = ['object_type', 'individual']
artifact = Artifact.objects.select_related(*joins).get(pk=pk)
artifact.serialize(*joins)

Si vous appelez .serialize avec les relations comme arguments, la bibliothèque recurse sur les objets associés, en appelant aussi .serialize. Ceci retourne un dictionnaire qui ressemble à:

{
  'id': 1,
  'year_of_Origin': 2010,
  'name': 'Dummy Title',
  'notes': '',
  'object_type_id': 1,
  'individual_id': 1,
  'object_type': { ... nested object here ... },
  'individual': { ... nested object here ... }
}

Vous pouvez ensuite appeler json.dumps dans ce dictionnaire pour le transformer en JSON.

Par défaut, l'extension SerializableModel définira également le gestionnaire du modèle sur SerializableManager (vous pouvez l'étendre vous-même si vous utilisez un gestionnaire personnalisé) qui utilise SerializableQuerySet. Cela signifie que vous pouvez également appeler .serialize sur un gestionnaire ou un ensemble de requêtes:

artifacts = Artifact.objects.select_related(*joins).all()
artifacts.serialize(*joins)

Ceci appelle simplement .serialize sur chaque objet de modèle dans le jeu de requêtes, renvoyant une liste de dictionnaires dans le même format que ci-dessus.

Django-serializable-model vous permet également de remplacer facilement le comportement par défaut modèle par modèle, ce qui vous permet d'effectuer des opérations telles que: ajouter des listes blanches ou des listes noires appliquées au .serialize de chaque modèle, toujours sérialiser certaines jointures ne pas avoir à les ajouter comme arguments tout le temps), et plus encore!

0
agilgur5

Je suis conscient que ce sujet a plusieurs années. Cependant, je partage ma solution pour les personnes qui cherchent toujours une réponse (lors de ma recherche, je me suis retrouvé ici). 

Veuillez noter que je cherchais une fonction simple qui me donnerait des objets/dictionnaires imbriqués (clé étrangère) (pouvant également contenir des objets/dictionnaires imbriqués (clé étrangère)) que je pourrais ensuite convertir en JSON.

Dans ma models.py, j'ai une fonction personnalisée (pas dans une classe de modèle):

Models.py

def Django_sub_dict(obj):
    allowed_fields = obj.allowed_fields() # pick the list containing the requested fields
    sub_dict = {}
    for field in obj._meta.fields: # go through all the fields of the model (obj)
        if field.name in allowed_fields: # be sure to only pick fields requested
            if field.is_relation: # will result in true if it's a foreign key
                sub_dict[field.name] = Django_sub_dict(
                    getattr(obj, field.name)) # call this function, with a new object, the model which is being referred to by the foreign key.
            else: # not a foreign key? Just include the value (e.g., float, integer, string)
                sub_dict[field.name] = getattr(obj, field.name)
    return sub_dict # returns the dict generated

Cette fonction parcourt tous les champs d'un objet models.Model, si l'objet models.Model est fourni. J'appelle la fonction dans un modèle comme suit (par souci de complétude, y compris un modèle complet):

les mêmes modèles.py

class sheet_categories(models.Model):
    id = models.AutoField(primary_key=True, unique=True)
    create_date = models.DateField(auto_now_add=True)
    last_change = models.DateField(auto_now=True)
    name = models.CharField(max_length=128)
    sheet_type = models.ForeignKey(
        sheet_types, models.SET_NULL, blank=False, null=True)
    balance_sheet_sort = models.IntegerField(unique=True)

    def allowed_fields(self):
        return [
                'name',
                'sheet_type',
                'balance_sheet_sort',
                ]

    def natural_key(self):
        return Django_sub_dict(self) # call the custom function (which is included in this models.py)

Remarque: Les objets JSON imbriqués ne contiendront que des champs inclus dans les allowed_fields d'un modèle. Donc, sans inclure les informations sensibles.

Pour générer finalement un JSON, j'ai la vue suivante dans mon view.py.

views.py

class BalanceSheetData(ListView): # I believe this doesn't have to **be** a ListView.
    model = models.sheet_categories

    def get_queryset(self):
        return super().get_queryset().filter() # the filter is for future purposes. For now, not relevant

    def get(self, request, *args, **kwargs):
        context = {
            'queryset': serializers.serialize("json",
                                          self.get_queryset(),
                                          use_natural_foreign_keys=True, # this or the one below makes Django include the natural_key() within a model. Not sure.
                                          use_natural_primary_keys=True, # this or the one above makes Django include the natural_key() within a model. Not sure.
                                          ),
        }
        return JsonResponse(context)

Cela m'a finalement fourni tous les détails imbriqués dont j'avais besoin dans une réponse JSON. Bien que je ne partage pas la réponse JSON, celle-ci est à peine lisible.

N'hésitez pas à commenter.

0
Jens van Hellemondt