【问题标题】:Queryset return response customize查询集返回响应自定义
【发布时间】:2019-07-23 15:58:19
【问题描述】:

我对 Django restframework 很陌生,我现在正在尝试使用外键返回对象。

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

我正在使用 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

现在结果在下面的响应中并立即抛出此错误

但我希望该查询集返回如下,因此我可以在 android 中读取这些密钥对值

{ "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"
 }

现在抛出错误

AttributeError:尝试在序列化程序 LocationDataSerializer 上获取字段 source_id 的值时出现 AttributeError。 序列化程序字段可能命名不正确,并且与 int 实例上的任何属性或键都不匹配。 原始异常文本是:“int”对象没有属性“source_id”。

谢谢!

【问题讨论】:

  • 您想要所有的响应都以这种方式,还是只需要特殊类型的请求 - 响应?
  • 按照我上面提到的方式!
  • 您可以在查询中添加注释。使用它,您可以将自定义字段添加到您的查询中
  • @Sarang 你有样品吗!请发帖
  • docs.djangoproject.com/en/2.1/ref/models/querysets/#annotate 检查此链接。有许多功能可以与 annotate 一起使用。

标签: python django django-rest-framework


【解决方案1】:

这个问题的答案取决于您使用的视图类型,但最重要的是您不会在 get_queryset 中执行此操作,而是在请求类型的方法中执行此操作。

例如,如果您使用的是RetrieveAPIView,您应该像这样覆盖RetrieveModelMixin 中的retrieve 方法:

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)

如果您使用的是 ListAPIView 之类的其他东西,那么您想查看相关方法中使用的内容并覆盖它以包装您的数据。

这里要意识到的主要事情是它与获取查询集无关——它只是从数据库中获取数据。这是关于在发回响应时将数据转换为正确的格式。因此,工作应该在做出响应时完成。

【讨论】:

  • queryset = LocationData.objects.filter(user__id=userid).order_by('-source_id')[:1] 怎么样?我需要过滤查询集并将该数据提供给响应!
  • 你可以在get_queryset中过滤,也可以按照我回答中提到的方法调用get_queryset,过滤返回的值。
  • 举个例子就好了!我找不到任何线索!
【解决方案2】:

这个问题有几种可能的解决方案。 NDevox 已经提到我们如何覆盖我们的retrive 函数并获得我们预期的响应。但是,如果我们希望对每个 api 端点的每个响应都执行此操作,并且如果我们这样做,我们需要覆盖每个函数,那么它的负担很大,它的 DRY 我们应该尽可能避免这种情况。引入中间件或覆盖Response 的一种可能方式,这样我们就可以在不显式覆盖每个功能的情况下获得每个 api 端点的通用响应。

可能的解决方案一

当我们在这里使用DRF 时,我们可以添加我们自己的各种媒体类型的返回响应,比如application/json

首先我们需要在我们的settings.py中添加

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

在我们的自定义渲染中间件中

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)

参考Link

可能的解决方案二

如果我们使用ModelViewset,那么我们可以通过另一种方式实现这一目标。说我们的 Views.py 如下所示

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

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


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

我们的目标是覆盖ModelViewset 的 to_representation 函数并返回我们的自定义结果。这将如下所示

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):
        ........

【讨论】:

  • @Tot 先生,您不能只更改 quest_set 的定义/响应。当您使用 ModelViewset 请通过 RetrieveModelMixin link ,在这里您会发现在 ModelViewset 的 retrive 下实际发生的事情,您可以看到在 query_set 传递给 Serializer 并且您使用 ModelSerializer 时没有 @ 987654337@ 那里的部分。
【解决方案3】:

在这里实现自定义渲染器似乎是一种方法。

您可以让来自 Android 客户端的请求在 Accept 标头中包含一种向渲染器标识客户端的方法。 1例如

Accept: application/json; android=true

然后使用 JSONRenderer 类编写一个渲染器,为您的 Android 客户端提供格式。

# ./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)

然后可以在您需要使用APIViewrenderer_classes 属性以这种方式格式化响应的情况下使用它。 2

【讨论】:

    【解决方案4】:

    由于 get_queryset 不允许您自定义响应数据。我决定采用对我很重要的查询值。

    http://localhost/api/users/?user_id=1 --> 改为 ...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
    

    【讨论】:

    • 所以一些指针。 1)您应该更新您的文档字符串,目前它只是一个与方法无关的代码示例。 2) 响应永远不会是你在上面定义的那样,所以if response is not None 是多余的。 3)您真的不应该将状态代码作为响应正文的一部分返回。您应该在响应中设置状态,例如return Response(response, status=404)
    • 是的,已处理好所有声明!谢谢
    猜你喜欢
    • 1970-01-01
    • 2021-05-10
    • 2019-10-31
    • 1970-01-01
    • 2016-04-15
    • 2021-06-22
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多