我是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:尝试在序列化程序
source_id
上获取字段LocationDataSerializer
的值时,出现AttributeError。 序列化程序字段的名称可能不正确,并且与int
实例上的任何属性或键都不匹配。 原始异常文本为:'int'对象没有属性'source_id'。
谢谢!
答案 0 :(得分:3)
此问题的答案取决于您所使用的视图类型,但最重要的是,您没有在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之类的东西,那么您想查看相关方法中该东西使用了什么,并覆盖它来包装数据。
在这里要实现的主要事情是,它与获取查询集无关-仅仅是从数据库获取数据。这是关于在发送回响应时将数据转换为正确的格式。因此,应在做出响应时完成工作。
答案 1 :(得分:1)
有两种解决方案可以解决此问题。 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):
........
答案 2 :(得分:1)
在此处实现自定义渲染器似乎是一种方法。
您可以将来自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)
然后可以在需要使用renderer_classes
的{{1}}属性以这种方式设置格式的响应时使用。 2
答案 3 :(得分:0)
由于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