Django-tastypie: Différents champs pour les actions SHOW et INDEX

Créé le 25 oct. 2010  ·  40Commentaires  ·  Source: django-tastypie/django-tastypie

Salut à tous, est-il possible d'afficher différents champs pour les ressources en fonction de l'action.

La raison pour laquelle je le souhaite est d'éviter de surcharger l'action d'indexation avec tous les champs has_many inclus dans la ressource.

Par exemple, dans la vue Index de l'auteur, je voudrais renvoyer uniquement son nom, mais pour l'action Afficher, je voudrais inclure toutes les ressources de livres.

Merci,
Bojan

documentation feature

Commentaire le plus utile

Une solution de contournement simple que j'utilise consiste à remplacer les méthodes get_detail & get_list pour modifier ces champs.
Cela économise les frais liés à la récupération des données du champ, puis à leur suppression du bundle, mais je ne sais pas si cette méthode est threadsafe, car il semble que les objets Resource ne soient pas créés à chaque appel d'API.
Ce serait super si quelqu'un pouvait commenter ça.

Voici le code :

class ArticleResource(BaseModelResource):
    owner = fields.ToOneField(UserResource, 'owner', full=True)

    class Meta:
        resource_name = "articles"

    def get_list(self, request, **kwargs):
        self.fields.pop("comments", None)
        return super(ArticleResource, self).get_list(request, **kwargs)

    def get_detail(self, request, **kwargs):
        self.fields["comments"] = fields.ToManyField(CommentResource, 'comments', full=True)
        return super(ArticleResource, self).get_detail(request, **kwargs)

Tous les 40 commentaires

+1
J'espère profondément cette fonctionnalité: "champ de détail" et "champ de liste" peuvent être séparés

+1 (via duplicata au #44)

Il me semble qu'il y a deux choses qui pourraient faciliter l'obtention d'un sous-ensemble de données dans les actions d'index :

  1. Faites en sorte que full_dehydrate utilise différents champs pour les méthodes index et get. Peut-être quelque chose comme :

Méta :
index_exclude_fields = ['some_m2m_field']

Cela nous permettrait d'enregistrer une requête de base de données.

  1. Passez le paramètre 'méthode' supplémentaire pour déshydrater. Cela permettrait la personnalisation du bundle comme sauter certains champs qui ne sont pas nécessaires dans l'index et nous voulons économiser de la bande passante.

Je n'ai pas évalué 1. aurait un impact sur la mise en cache.
Qu'est-ce que tu penses?

Également dupé dans le n°48, mais chaque numéro a des aspects intéressants.

Je voulais repousser cette fonctionnalité si je le pouvais, mais je suis bien plus armé. Il devra être là pour une meilleure prise en charge des fichiers de toute façon, il doit donc être traité. Ciblera 1.0 à ce sujet.

Après avoir examiné un peu plus mon cas d'utilisation pour cela, j'aimerais suggérer qu'il soit implémenté comme quelque chose de plus flexible que de simples vues d'affichage/liste. Ce que je dois vraiment pouvoir faire, c'est prendre un indicateur de chaîne de requête comme celui-ci :

&shape=[complet|simple|aucun]

et faire en sorte que la sortie reflète le niveau de détail sélectionné par l'utilisateur. J'ai cherché un moyen de pirater cela, mais comme les champs sont inclus/exclus lors de la création de la classe ModelResource, je n'ai pu trouver aucun moyen de modifier ce qui était disponible plus tard dans le cycle de réponse. De plus, l'objet de la requête n'est pas disponible aux étapes du processus où je souhaiterais prendre une telle détermination.

+1

Je mentionnerai d'abord cette pensée ici, car elle semble pertinente pour ce problème, mais il faudra peut-être la transformer en elle-même.

Si un mécanisme d'affichage des différents champs est développé, il serait utile qu'il puisse également être intégré à l'authentification. Pouvoir exposer plus/moins de champs dépend des autorisations de l'utilisateur serait très puissant. Si c'est déjà possible, je n'en ai trouvé aucune mention.

+1

+1

+1

+1

+1

+1

+1

Je m'attendrais à pouvoir contrôler les champs affichés (en utilisant des champs ou des exclusions) sur une méthode par (POST, PUT, GET) en plus de la liste/détail.

Voici une solution de contournement, voir l'exemple ci-dessous : vous n'excluez rien du modèle de ressource du modèle d'origine (UserResource dans l'exemple) donc il serait plein dans sa vue d'index. Vous devez entrer dans la méthode de déshydratation de la ressource de modèle qui inclut votre sous-modèle (la BlogPostResource inclut l'auteur dedans) et supprimez simplement les éléments du bundle.

Exemple:

class BlogPostResource(ModelResource):
     author = fields.ForeignKey(UserResource, 'author', full=True)
     ....
     class Meta:
         ...

def dehydrate(self, bundle):
         del bundle.data['author'].data['field_you_dont_wanna_show_here']
         del bundle.data['author'].data['field_you_dont_wanna_show_here']
         return bundle

Ainsi, lorsque vous souhaitez répertorier les utilisateurs, vous obtenez toujours tous les champs, mais lorsque vous répertoriez les articles de blog, vous pouvez par exemple simplement obtenir le prénom et le nom de l'auteur.

Qu'est-ce que tu penses?

Exemple de solution de contournement très, très sale : n'affiche pas les champs du projet dans list_view, mais il le fait dans detail_view, sans avoir à accéder à la ressource elle-même, obtenir l'URL elle-même sans coder en dur, cela devrait être possible mais je n'ai pas eu le temps de le vérifier :

class CompanyResource(ModelResource):
       """
       Tastypie resource for Company
      """
       projects = fields.ToManyField('api.resources.ProjectResource',
                                  'projects',full=True)
       class Meta:
           queryset = Company.objects.all()
           resource_name = 'companies'

       def dehydrate(self, bundle):
           if bundle.request.path == "/api/v1/companies/":
               del bundle.data['projects']
           return bundle

+1

+1

+1

Je monterai aussi à bord, ce serait bien d'avoir un train, mais ce que @ashwoods a souligné est suffisant pour me débrouiller. Je l'ai légèrement modifié pour être un peu plus dynamique.

    def dehydrate(self, bundle):
        if self.get_resource_uri(bundle) == bundle.request.path:
            print "Detail"

        if self.get_resource_uri(bundle) != bundle.request.path:
            print "Not Detail - Could be list or reverse relationship."

        return bundle

J'ai donc réfléchi un peu plus à cela et j'ai trouvé quelque chose qui me permet de faire ce que je pense que @bmihelac recherchait.

En utilisant l'exemple @ashwoods fourni, supposons que nous voulions seulement afficher le champ des projets s'il s'agissait d'une réponse détaillée :

class CompanyResource(ModelResource):
    """
    Tastypie resource for Company
    """

    class Meta:
        queryset = Company.objects.all()
        resource_name = 'companies'
        additional_detail_fields = {'projects': fields.ToManyField('api.resources.ProjectResource', 'projects',full=True)}

    def dehydrate(self, bundle):
        # detect if detail
        if self.get_resource_uri(bundle) == bundle.request.path:
            # detail detected, include additional fields
            bundle = self.detail_dehydrate(bundle)

        return bundle

    # detail_dehydrate is basically full_dehydrate
    # except we'll loop over the additional_detail_fields
    # and we won't want to do the dehydrate(bundle) at the end
    def detail_dehydrate(self, bundle):
        """
        Given a bundle with an object instance, extract the information from it
        to populate the resource.
        """
        # Dehydrate each field.
        # loop over additional_detail_fields instead
        #for field_name, field_object in self.fields.items():
        for field_name, field_object in self._meta.additional_detail_fields.items():
            # A touch leaky but it makes URI resolution work.
            if getattr(field_object, 'dehydrated_type', None) == 'related':
                field_object.api_name = self._meta.api_name
                field_object.resource_name = self._meta.resource_name

            bundle.data[field_name] = field_object.dehydrate(bundle)

            # Check for an optional method to do further dehydration.
            method = getattr(self, "dehydrate_%s" % field_name, None)

            if method:
                bundle.data[field_name] = method(bundle)

        # dehydrating the bundle will create an infinite loop
        #bundle = self.dehydrate(bundle)
        return bundle

+1, en utilisant le correctif de @dericcrago pour le moment.

+1

+1

Implémentation partielle dans #526, je ne suis pas sûr d'avoir tout vendu et il manque des tests/docs.

Je viens de voir ce billet... et j'aime aussi l'approche "forme" mentionnée par onyxfish ci-dessus...

Je pensais que ma solution dans #526 était un peu limitée, au cas où les gens voudraient des "formes" différentes dans d'autres cas...

aux suggestions pour supprimer les champs après la déshydratation ... toute ma raison est d'éviter de calculer les valeurs en premier lieu.

Cependant, l'idée du crochet detail_dehydrate pour permettre l'ajout conditionnel de plus de détails, j'aime bien.

On dirait que deux implémentations possibles, y compris les tests et les documents, sont disponibles. J'en ai écrit un dans #569 et #538 exécute également des fonctionnalités similaires (#538 permettant un peu plus de flexibilité puisque use_in peut être un appelable). Mon implémentation ajoute des attributs meta pour contrôler cette fonctionnalité (ce qui est cohérent avec l'attribut fields actuel) tandis que #538 ajoute un attribut aux champs. Les deux semblent valables, juste une décision de conception quant à la voie à suivre. L'ajout à la méta me semble cohérent et est plus simple à utiliser étant donné que certains champs peuvent être générés automatiquement et que la modification de leurs paramètres d'initialisation peut ne pas être possible. Une autre alternative serait de combiner les deux demandes d'extraction et de permettre au paramètre use_in d'être automatiquement défini en fonction de l'attribut meta , mais cela semble ajouter plus de complexité à l'API que nécessaire. Merci à @issackelly de

[interprétant car j'étais la cause initiale du #538, c'est un nettoyage de mon #526]
Cela a beaucoup de sens... l'approche Meta serait, en effet, gelée avec la liste des exclusions pour ModelResource, etc...

Comme je l'ai dit dans un autre ticket, une solution "simple" comme celle-ci serait, à mon humble avis, suffisante pour une version 1.0... .

@funkybob D'accord, la solution la plus complexe serait certainement utile du côté client, mais ce serait bien d'avoir cette fonctionnalité incluse dès que possible afin qu'elle puisse commencer à être utilisée avant la sortie de la version 1.0.

En fait, en utilisant le PR dans une application de production, j'aime beaucoup la flexibilité du rappel fourni par #538. J'ai quelques cas d'utilisation où je dois masquer une ressource au moment de l'exécution en fonction de l'autorisation.

Ce ne serait pas possible pour moi en utilisant #569

Salut, des nouvelles ? Ce PR rend la vie tellement plus facile, merci !

Aller avec hack fourni par @dericcrago

+1

Une solution de contournement simple que j'utilise consiste à remplacer les méthodes get_detail & get_list pour modifier ces champs.
Cela économise les frais liés à la récupération des données du champ, puis à leur suppression du bundle, mais je ne sais pas si cette méthode est threadsafe, car il semble que les objets Resource ne soient pas créés à chaque appel d'API.
Ce serait super si quelqu'un pouvait commenter ça.

Voici le code :

class ArticleResource(BaseModelResource):
    owner = fields.ToOneField(UserResource, 'owner', full=True)

    class Meta:
        resource_name = "articles"

    def get_list(self, request, **kwargs):
        self.fields.pop("comments", None)
        return super(ArticleResource, self).get_list(request, **kwargs)

    def get_detail(self, request, **kwargs):
        self.fields["comments"] = fields.ToManyField(CommentResource, 'comments', full=True)
        return super(ArticleResource, self).get_detail(request, **kwargs)

+1

une autre solution consiste à disposer de différentes ressources pour les vues de détail et de liste :

from tastypie.resources import ModelResource
from django.contrib.auth.models import User

# detail will show everything except password
class UserResourceDetail(ModelResource):
    class Meta:
        queryset = User.objects.all()
    excludes = ('password',)
    resource_name = 'user'

# list will only show username & date_joined (and exclude password)
class UserResource(UserResourceDetail):
    class Meta(UserResourceDetail.Meta):
        fields = ('username', 'date_joined')
    get_detail = UserResourceDetail().get_detail

# ... register & use UserResource

+1

+1 pour la solution de contournement @dnozay

+1 @dnozay , génial

Notez que si vous voulez que get_resource_uri fonctionne correctement avec la vue détaillée de l'utilisateur, vous devrez ajouter ce qui suit après la définition de UserResource .

UserResourceDetail.get_resource_uri = UserResource().get_resource_uri

Sinon, resource_uri sera vide dans toutes les réponses détaillées.

Peut-être lié : #1265

Cette page vous a été utile?
0 / 5 - 0 notes