1
votes

Personnalisation de la réponse de retour de l'ensemble de requêtes

Je suis assez nouveau dans Django restframework, ce que j'essaye maintenant est de renvoyer l'objet avec une clé étrangère.

{ "collection": {
  "data": {
    "id": 31,
    "source_id": "55",
    "latitude": "24654",
    "longitude": "454654",     
    "date_created": "2019-02-08T17:10:09.318644Z",
    "date_modiefied": "2019-02-08T17:10:09.318714Z",
    "area": "54546",
    "user": {
        "id": 1,
        "name": "Dormy",
        "date_created": "1992-01-18T03:29:53.388000Z",
        "date_modiefied": "2018-02-19T05:17:00.164000Z",
        "serverTime": "",
        "fcmTokenId": ""
      }
  },
    "statusCode": 200,
    "version": "1.0"
 }

J'utilise def get_queryset (self):

class SyncIndexLastDataViewSet(viewsets.ModelViewSet):
serializer_class = LocationDataSerializer

def get_queryset(self):

    userid = self.request.query_params.get('user_id', None)
    userExist = User.objects.filter(id=userid)
    if userExist.exists():
        # call the original 'list' to get the original response
        queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
        lastSourceId = queryset[0]['source_id']
        response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
        json = JSONRenderer().render(response)
        # customize the response data
        if response is not None:
            return json
    else:
        # return response with this custom representation
        response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
        return response

Pour le moment, le résultat est à l'intérieur de la réponse est ci-dessous et immédiatement il lance cette erreur

Mais je veux que cet ensemble de requêtes retourne comme ci-dessous, donc je peux lire ceux valeurs de paire de clés dans Android

class User(models.Model):
    name = models.CharField(max_length=255,blank=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modiefied = models.DateTimeField(auto_now=True)
    area = models.CharField(max_length=255,blank=True)
    uuid = models.CharField(max_length=255)
    home = models.CharField(max_length=255,blank=True)
    work = models.CharField(max_length=255,blank=True)
    mobileNo = models.CharField(max_length=255,blank=True)
    email = models.CharField(max_length=255,blank=True)
    appVersionCode = models.CharField(max_length=255,blank=True)
    photoUrl = models.CharField(max_length=255,blank=True)
    serverTime = models.CharField(max_length=255,blank=True)
    fcmTokenId = models.CharField(max_length=255,blank=True)
   def __str__(self):
    return self.name

class LocationData(models.Model):
   user = models.ForeignKey(
     User, related_name='user', on_delete=models.DO_NOTHING)
    source_id = models.CharField(max_length=255)
    latitude = models.CharField(max_length=255)
    longitude = models.CharField(max_length=255)
    speed = models.CharField(max_length=255)
    kms = models.CharField(max_length=255)
    date_created = models.DateTimeField(auto_now=True)
    date_modiefied = models.DateTimeField(auto



class UserSerializer(serializers.ModelSerializer):
  class Meta:
    model = User
    fields = '__all__'

class LocationDataSerializer(serializers.ModelSerializer): 

class Meta:
    model = LocationData
    fields = '__all__'
    depth = 1

Maintenant, l'erreur se produit

AttributeError: Obtenu AttributeError lors de la tentative d'obtention d'une valeur pour le champ source_id sur le sérialiseur LocationDataSerializer . Le champ du sérialiseur peut être nommé de manière incorrecte et ne correspondre à aucun attribut ou clé de l'instance int . Le texte de l'exception d'origine était: l'objet 'int' n'a pas d'attribut 'source_id'.

Merci !


6 commentaires

Voulez-vous toute votre réponse de cette manière ou simplement un type spécial de demande - réponse?


Par la manière ci-dessus, je l'ai mentionné!


vous pouvez ajouter une annotation dans votre requête. Grâce à cela, vous pouvez ajouter un champ personnalisé à votre requête


@Sarang avez-vous un échantillon! Veuillez le poster


docs.djangoproject.com/en/2.1/ref/models/querysets / # annoter vérifier ce lien. Il existe de nombreuses fonctionnalités que vous pouvez utiliser avec annotate.


J'ai mis à jour le problème et plus de code


4 Réponses :


5
votes

La réponse à cela dépend du type de vue que vous utilisez, mais l'essentiel est que vous ne le faites pas dans get_queryset , vous le faites dans la méthode du type de demande.

Par exemple, si vous utilisez un RetrieveAPIView a> vous devez remplacer la méthode retrieve de RetrieveModelMixin comme ceci:

class MyAPIView(RetrieveAPIView):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = {
            "collection": {
                "data": serializer.data
            },
            "statusCode": 200,
            "version": "1.0"
        }
        return Response(data)

Si vous utilisez autre chose comme un ListAPIView, vous voulez voir ce qui est utilisé par cela dans le et remplacez-la pour envelopper vos données.

La chose principale à réaliser ici est que cela n'a rien à voir avec l'obtention du jeu de requêtes - qui consiste simplement à obtenir des données de la base de données. Il s'agit de transformer les données dans le format correct lors du renvoi d'une réponse. En conséquence, le travail doit être effectué au moment où la réponse est faite.


3 commentaires

Que diriez-vous du queryset = LocationData.objects.filter (user__id = userid) .order_by ('- sour‌ ce_id') [: 1]? J'ai besoin de filtrer l'ensemble de requêtes et de fournir ces données à la réponse!


Vous pouvez soit filtrer dans get_queryset , soit appeler get_queryset dans la méthode que j'ai mentionnée dans ma réponse et filtrer la valeur qu'elle renvoie.


un exemple serait génial !, je ne trouve aucun indice pour faire ça!



2
votes

Il existe deux solutions possibles à ce problème. NDevox mentionne déjà comment nous pouvons écraser notre fonction retrive et obtenir notre réponse attendue. Mais si nous voulons que cela soit fait avec chaque réponse pour chaque point de terminaison api et si nous procédons de cette façon, nous devons écraser chaque fonction, alors son lourd fardeau et son DRY nous devons éviter cela autant que possible. Un des moyens possibles d'introduire un middleware ou d'écraser la Réponse afin que nous puissions obtenir notre réponse générique pour chaque point de terminaison api sans écraser explicitement chaque fonctionnalité.

Solution possible une

Comme nous utilisons DRF ici, nous pouvons ajouter nos propres réponses de retour avec différents types de médias, par exemple pour application / json .

Nous devons d'abord ajouter dans nos settings.py

from collections import OrderedDict

class OurParentViewset(serializer.ModelSerializer):

    ......
    def to_representation(self, instance):
        data = super(serializers.ModelSerializer, self).to_representation(instance)
        result = OrderedDict()
        result['data'] = data
        result['version'] = '1.0'
        result['statusCode'] = '2xx' # i am not fully sure how to customize this
        return result

    class A(OurParentViewset):
        ........

    class B(OurParentViewset):
        ........


    class C(OurParentViewset):
        ........

Et dans notre middleware de rendu personnalisé

class A(serializer.ModelSerializer):
    ........

class B(serializer.ModelSerializer):
    ........


class C(serializer.ModelSerializer):
    ........

Référence Lien

Possible Solution Deux

Si nous utilisons ModelViewset , il existe un autre moyen d'y parvenir. Dites que nos vues.py sont comme suit

from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json


class ApiRenderer(BaseRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):
        our_response_dict = {
            'version': '1.0'
            'data': {},
            'message': '',
        }
        if data.get('data'):
            our_response_dict['data'] = data.get('data')
        if data.get('status'):
            our_response_dict['statusCode'] = data.get('status')
        if data.get('message'):
            our_response_dict['message'] = data.get('message')
        data = our_response_dict
        return json.dumps(data)

Notre objectif est d'écraser la fonction to_representation de ModelViewset et de renvoyer notre résultat personnalisé. Ce sera comme suit

REST_FRAMEWORK = {
    ...
    'DEFAULT_RENDERER_CLASSES': (
        'app_name.renderers.ApiRenderer',  # our own render middleware
    ),
    ...
}


1 commentaires

@Tot monsieur, vous ne pouvez pas simplement changer la définition / réponse de quest_set. Comme vous utilisez ModelViewset , veuillez passer par RetrieveModelMixin

Implémenter un moteur de rendu personnalisé ici semble être une solution.

Vous pouvez demander à votre client Android d'inclure dans l'en-tête Accept un moyen d'identifier le client auprès du moteur de rendu. 1 eg

# ./formatters/android_format.py

from rest_framework.renderers import JSONRenderer, BaseRenderer
from django.http.multipartparser import parse_header

class AndroidV1FormatRenderer(BaseRenderer):
    media_type = 'application/json'
    format = 'json'

    json_renderer = JSONRenderer()

    def android(self, accepted_media_type):
        base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
        return 'android' in params

    def render(self, data, accepted_media_type=None, renderer_context=None):
        response = renderer_context['response']
        android = self.android(accepted_media_type)
        if android:
            data = {
                "collection": {"data": data},
                "statusCode": response.status_code,
                "version": "1.0"
            }

        return json_renderer.render(
            wrapped_data, accepted_media_type, renderer_context)

Ensuite, composez un moteur de rendu en utilisant la classe JSONRenderer pour fournir le format de votre client Android.

Accept: application/json; android=true

Ceci peut ensuite être utilisé lorsque vous avez besoin d'une réponse formatée de cette façon en utilisant l'attribut renderer_classes de votre APIView . 2 sup >


0 commentaires

1
votes

Puisque get_queryset ne vous permettra pas de personnaliser les données de réponse. Je décide de prendre la valeur de requête qui est importante pour moi.

http: // localhost / api / users /? user_id = 1 a > -> changé en ... api / users / 1

 def retrieve(self, request, *args, **kwargs):
    """ userid = self.request.query_params.get('user_id', None) """

    userid = kwargs.get('pk')
    userExist = User.objects.filter(id=userid)
    if userExist.exists():
        # call the original 'list' to get the original response
        queryset =  LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
        lastSourceId = queryset[0]['source_id']
        response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
        # customize the response data
        if response is not None:
            return Response(response)
    else:
        # return response with this custom representation
        response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
        return response


2 commentaires

Donc, quelques conseils. 1) Vous devez mettre à jour votre docstring, actuellement il ne s'agit que d'un exemple de code sans rapport avec la méthode. 2) la réponse ne peut jamais être aucune telle que vous la définissez immédiatement au-dessus, donc si la réponse n'est pas Aucune est redondant. 3) vous ne devriez vraiment pas renvoyer le code d'état dans le corps de la réponse. Vous devez définir le statut dans la réponse comme return Response (response, status = 404) .


Oui, pris soin de toutes les choses énoncées !. Merci