不得不说Django Rest FrameWork使用还是很方便的,但是在日常中,经常遇到一些小问题让人头痛,索性专门开个文章,来一一记录日常遇到的一些问题和解决方法:
serializer的字符串数组问题
在Django中,如果你的Model有一个ForeignKey的关联,并且你在Api做查询时将这个关联以prefetch_related形式全部查询出来了,最典型的就是图集,举例如下:
在model中定义:
1 2 3 4 5 6 |
class ModelA(models.Model): name = models.CharField(u'xxxx', max_length=10) class ModelB(models.Model): modela = models.ForeignKey(ModelA, verbose_name=u'oooo', related_name='pics') pic = models.ImageField(u'图', upload_to='', max_length=100,blank=True, null=True) |
在Api中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class ModelAApiView(generics.RetrieveAPIView): serializer_class = ModelASerializer queryset = ModelA.objects.all().prefetch_related("pics") class ModelBSerializer(serializers.ModelSerializer): class Meta: model = ModelB fields = ('pic') class ModelASerializer(serializers.ModelSerializer): pics = ModelBSerializer(many=True) class Meta: model = ModelA fields = ('name', 'pics') |
这是通常的作坊,将ForeignKey的关联对象全部转换到主对象的json里面去,对应会生成的json就像这样:
1 2 3 4 5 6 |
{ "name":"", "pics":[ {"pic":"xxxx.jpg"},{"pic":"oooo.jpg"} ] } |
通常来说,这样就已经已经可以使用了,但是如果pic只是传一个图片地址的话,为什么不直接通过json的数组传递过来,还要通过数组-对象的形式传递呢。
通过查找Rest FrameWork,发现可以改写ModelASerializer为:
1 2 3 4 5 |
class ModelASerializer(serializers.ModelSerializer): pics = serializers.StringRelatedField(many=True) class Meta: model = ModelA fields = ('name', 'pics') |
这样,pics序列化出来就不是数组-对象而变成了数组,同时将使用对象的 unicode 方法。 之后json会变为:
1 2 3 4 5 6 |
{ "name":"", "pics":[ "xxxx.jpg","oooo.jpg" ] } |
Token的失效问题
在Rest FrameWork中,TokenAuthentication是通过数据库表来存储的。 同时,它只记录了Token,Created, 如果,我们需要给一个Token设置过期时间,可以这样办:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from datetime import datetime,timedelta from rest_framework.authentication import TokenAuthentication from rest_framework import exceptions from django.conf import settings class ExpiringTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): model = self.get_model() try: token = model.objects.get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token.') if not token.user.is_active: raise exceptions.AuthenticationFailed('User inactive or deleted.') now = datetime.now() if token.created < now - timedelta(seconds=settings.SESSION_COOKIE_AGE): raise exceptions.AuthenticationFailed('Token has expired') if token.created + timedelta(seconds=settings.SESSION_UPDATE_AGE) < now: token.created = now token.save(update_fields=['created']) return (token.user, token) |
同时,在setting中配置:
1 2 3 4 5 6 |
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'apps.common.authentication.ExpiringTokenAuthentication', ) } SESSION_COOKIE_AGE=3600 |
这样,在每次请求过来之后,就能够去刷新token的时间,并保证过期的token不被认证。 当然, 以上代码也可以改造成使用redis的方式。
当使用prefetch_related时,Django默认是进行了所有关联对象的取出操作,以一个相册为例:
1 2 3 4 5 6 7 |
class PhotoAlbum(models.Model): title = models.CharField(max_length=128) author = models.CharField(max_length=128) class Photo(models.Model): album = models.ForeignKey('PhotoAlbum') status = models.BooleanField(default=False) |
当需要筛选出author为Davey Jones的所有图片时,一般使用
1 |
PhotoAlbum.objects.filter(author=”Davey Jones”).prefetch_related(“photo_set”) |
此时,如果需要筛选出所有有效的图片,一般不得不进行一次循环或者表推导。
当Django版本大于1.7时,有一个推荐的对象Prefetch,进行如下搜索
1 2 3 4 5 6 |
PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related( Prefetch( "photo_set", queryset=Photo.objects.filter(status=True) ) ) |
APIView中进行分页
如果使用generics.ListAPIView进行分页,详细很多人都应该知道直接实现对应的get_queryset方法以及实现对应的Serializer对象就可以了。但是如果要使用APIView呢?首先,实现一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class PaginationAPIView(APIView): pagination_class = LimitOffsetPagination @property def paginator(self): if not hasattr(self, '_paginator'): if self.pagination_class is None: self._paginator = None else: self._paginator = self.pagination_class() return self._paginator def paginate_queryset(self, queryset): if self.paginator is None: return None return self.paginator.paginate_queryset(queryset, self.request, view=self) def get_paginated_response(self, data): assert self.paginator is not None return self.paginator.get_paginated_response(data) class MyView(APIView): def get(self,request,*args,**kwargs): queryset = .... page = self.paginate_queryset(queryset) serializer = Serializer(page, many=True) return self.get_paginated_response(serializer.data) |
这样,就可以在使用APIView对结果进行分页处理
转载请注明:北凉柿子 » Django rest framwork 小技巧总结