大道至简,知行合一。

Django rest framwork 小技巧总结

不得不说Django Rest FrameWork使用还是很方便的,但是在日常中,经常遇到一些小问题让人头痛,索性专门开个文章,来一一记录日常遇到的一些问题和解决方法:

serializer的字符串数组问题

在Django中,如果你的Model有一个ForeignKey的关联,并且你在Api做查询时将这个关联以prefetch_related形式全部查询出来了,最典型的就是图集,举例如下:

在model中定义:

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中:

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就像这样:

{
    "name":"",
    "pics":[
        {"pic":"xxxx.jpg"},{"pic":"oooo.jpg"}
    ]
}

通常来说,这样就已经已经可以使用了,但是如果pic只是传一个图片地址的话,为什么不直接通过json的数组传递过来,还要通过数组-对象的形式传递呢。
通过查找Rest FrameWork,发现可以改写ModelASerializer为:

class ModelASerializer(serializers.ModelSerializer):
    pics = serializers.StringRelatedField(many=True)
    class Meta:
        model = ModelA
        fields = ('name', 'pics')

这样,pics序列化出来就不是数组-对象而变成了数组,同时将使用对象的 unicode 方法。 之后json会变为:

{
    "name":"",
    "pics":[
        "xxxx.jpg","oooo.jpg"
    ]
}

Token的失效问题

在Rest FrameWork中,TokenAuthentication是通过数据库表来存储的。 同时,它只记录了Token,Created, 如果,我们需要给一个Token设置过期时间,可以这样办:

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中配置:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'apps.common.authentication.ExpiringTokenAuthentication',
    )
}
SESSION_COOKIE_AGE=3600

这样,在每次请求过来之后,就能够去刷新token的时间,并保证过期的token不被认证。 当然, 以上代码也可以改造成使用redis的方式。

prefetch_related的条件搜索

当使用prefetch_related时,Django默认是进行了所有关联对象的取出操作,以一个相册为例:

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的所有图片时,一般使用

PhotoAlbum.objects.filter(author=”Davey Jones”).prefetch_related(“photo_set”)

此时,如果需要筛选出所有有效的图片,一般不得不进行一次循环或者表推导。
当Django版本大于1.7时,有一个推荐的对象Prefetch,进行如下搜索

PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related(
    Prefetch(
        "photo_set",
        queryset=Photo.objects.filter(status=True)
    )
)

APIView中进行分页

如果使用generics.ListAPIView进行分页,详细很多人都应该知道直接实现对应的get_queryset方法以及实现对应的Serializer对象就可以了。但是如果要使用APIView呢?首先,实现一个类:

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对结果进行分页处理

赞(0)
未经允许不得转载:北凉柿子 » Django rest framwork 小技巧总结
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址