Django RESRframework
Mixins, ViewSet和router配合使用
Mixins
的类共有五种
CreateModelMixin
ListModelMixin
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
他们分别对应了数据库的增、查、改、删的相应操作,使用它们的好处就是不需要再去写重复的相同的代码逻辑了,因为每个mixins
内部都写好了对应的逻辑,只需要设置一下queryset
和serializer_class
就可以了
ViewSet
也是有五种,分别是
ViewSetMixin
ViewSet
GenericViewSet
ReadOnlyModelViewSet
ModelViewSet
一般的话来说只需要使用GenericViewSet
就够了,它继承了ViewSetMixin
和generics.GenericAPIView
,后者的功能大家都知道,有了它才能设置queryset
和serializer_class
属性,重点是ViewSetMixin
它重写了as_view
方法,这个能让我们注册url
变得非常的简单,还有一个使用的方法就是initialize_request
,这个方法主要是给action
属性赋值,这个属性在设置动态serializer
和permission
的时候有巨大的好处!
So ~ 写上一个APIView
的代码如下
from rest_framework import mixins
from rest_framework import viewsets
class XXXViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
接下来就是配置url
from appname.views import XXXViewSet
models = XXXViewSet.asview({
'get': 'list',
'post': 'create'
})
urlpattrtns = [
url(r'apiAddress/$',models, name="models"),
这样就可以把set
请求绑定到list
的方法之上了,post
请求就绑定到了creat
方法,不需要再去重写它们了,其实上面配置url
的方法还可以更加简便,于是router
就登场啦~
from rest_framework.routers import DefaultRouter
from appname.views import XXXViewSet
router = DefaultRouter()
router.register(r'apiAddress', XXXViewSet, base_name='apiAddress')
urlpattrtns = [
# 这个已经不需要了
# url(r'apiAddress/$',models, name="models"),
url(r'^', include(router.urls)),
]
以后再添加url
的时候只需要在router
里面注册就行了,urlpattrtns
列表不需要做任何改动.这样就完成了一个RESTful API
的创建,能够合理搭配mixins
,ViewSet
和router
s三者的话,就可以超快速地开发大量的RESTful API
!
使用Django RESTframework的过滤功能
一个简单的过滤功能,例如查询用户列表,只返回粉丝数大于100的:
class XXXViewSet(mixins.ListModelMixin,mixins.CreateModelMixin,viewsets.GenericViewSet):
serializer_class = ModelSerializer
def get_query(self):
fans_nums = self.request.quer_params.get("fans_min",0)
if fans_nums:
return User.object.filter(fans_num__gt=int(fans_min))
return User.objects.all()
上面的这种方法如果需要写过多的过滤字段的话就需要写一大堆重复冗余的代码,于是乎就需要使用到django-filter
来完成
代码如下:
from rest_framework import mixins
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django.contrib.auth import get_user_model
User = get_user_model()
class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = ModelSerializer
filter_backends = (DjangoFilterBackend,)
# 设置过滤字段,这里设置过滤用户的名字和粉丝数
filter_fields = ('name', 'fans_num')
然后用浏览器打开127.0.0.1:8000
就可以看到相应的路由视图了,(这种需要使用的路由注册的方法)
这样你就会在过滤的界面里看到filter_fields = ('name', 'fans_num')
里面设置的过滤条件的字段了
新建filter.py
import django_filters
from django.contrib.auth import get_user_model
User = get_user_model()
class UserFilter(django_filters.rest_framework.FilterSet):
min_fans_num = django_filter.NumberFilter(name='fans_num', lookup_expr='gte')
max_fans_num = django_filters.NumberFilter(name='fans_num', lookup_expr='lt')
name = django_filters.CharFilter(name='name',lookup_expr='icontains')
class Meta:
model = User
fields = ['name', 'min_fans_num', 'max_fans_num']
然后之前的代码改为
from .filter import UserFilter
class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = ModelSerializer
filter_backends = (DjangoFilterBackend,)
filter_class = UserFilter
过滤用户名的话其实用SearchFilter
也可以实现,用两行代码就够了
filter_backends = (DjangoFilterBackend, SearchFilter)
serch_fields = ("name",)
还有一些更强大的配置
The search behavior may be restricted by prepending various characters to the search_fields.
'^' Starts-with search.
'=' Exact matches.
'@' Full-text search. (Currently only supported Django's MySQL backend.)
'$' Regex search.
For example:
search_fields = ('=username', '=email')
还有一个排序的filter
,例如我们想按照用户的粉丝数量进行排序(升序和降序):
自定义分页
from rest_framework.pagination import PageNumberPagination
class UsersPagination(PageNumberPagination):
# 指定每一页的个数
page_size = 10
# 可以让前端来设置page_szie参数来指定每页个数
page_size_query_param = 'page_size'
# 设置页码的参数
page_query_param = 'page'
class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
# 设置分页的class
pagination_class = UsersPagination
就这样几行代码就搞定了,而且在返回的json
中加了总数,下一页的链接和上一页的链接.
回顾我们之前的代码,在UserViewSet
这个类里面,才写了7行代码,就已经完成了
- 获取用户列表
create
一个用户- 分页
- 搜索
- 顾虑
- 排序
这些功能,如果想要获取指定用户的具体信息,直接继承mixins.RetrieveModelMixin
就直接做好了...''
权限认证
比如有一些API功能,是需要用户登录才能使用可以的
或者比如我要删除我这篇博客,也要验证我是作者才能删除
验证用户是否登录
from rest_framework.permissions import IsAuthenticated
class XXXViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin):
permission_classes = (IsAuthenticated,)
验证操作时本人需要permission
permission.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, object):
if request.method in permissions.SAFE_METHODS:
return True
return object.user == request.user
permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
## 使用JWT的用户认证模式
- 安装
pip install djangorestframework-jwt
- 在
url.py
中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
...
url(r'^api-token-auth/', obtain_jwt_token),
...
]
- 在需要的
jwt
认证的ViewSet
的类里面设置
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import SessionAuthentication
class XXXViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
加上SessionAuthentication是为了在网站上调试方便
现在注册登录有两种方式:
- 用户注册之后跳转到登录页面让其登录
- 用户注册之后自动帮他登录了
第一种情况的话我们无需再做其他操作,第二种情况我们应该在用户注册之后返回jwttoken
的字段给前台,所以要做两步:
因为返回字段是mixins
帮我们做好了,所以我们要重写对应的方法来修改返回字段
需要查看djangorestframework-jwt
的源码找到生成jwt token
的方法
from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler
class UserViewSet(CreateModelMixin, RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet):
# 重写create方法
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
# 在新建用户保存到数据库之后
tmp_dict = serializer.data
# 生成JWT Token
payload = jwt_payload_handler(user)
tmp_dict['token'] = jwt_encode_handler(payload)
headers = self.get_success_headers(serializer.data)
return Response(tmp_dict, status=status.HTTP_201_CREATED, headers=headers)
更多jwt的相关操作可以查看文档
动态serializers
这个使用之前说过的action
属性就可以很方便的实现
class UserViewSet(CreateModelMixin, RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet):
# 这个就不需要了
#serializer_class = XXXSerializer
def get_serializer_class(self):
if self.action == 'create':
return XXXSerializer
elif self.action == 'list':
return XXXSerializer
return XXXSerializer
一些实用的Serializer fields
比如说我要发布这篇文章,需要上传我(用户)的id
才能和这篇文章建立关联,但我们这个可以不用前台来上传
serializer.py
class XXXSerializer(serializers.ModelSerializer):
# user默认是当前登录的user
user = serializers.HiddenField(
default = serializers.CurrentUserDefault()
)
还有如果返回的字段逻辑比较复杂,可以用serializer.SerializerMethodField()来完成,例如:
class XXXSerializer(serializers.ModelSerializer):
xxx = serializer.SerializerMethodField()
# 把逻辑写在get_的前缀加xxx(字段名),然后返回
def get_xxx(self, obj):
# 完成你的业务逻辑
return
自定义用户认证Django
自带的登录是通过username
和password
来做登录的,但是现在很多网站或者app
用手机号来来当做账号,这个时候就需要自定义用户认证:
from django.contrib.auth.backends import ModelBackend
class CustomBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username)|Q(mobile=username))
# 验证密码是否正确
if user.check_password():
return user
except Exception as e:
return None
用户注册的时候,如果你在后台查看的是明文,这是因为ModelSerializer在保存的时候直接明文保存了, 解决问题:
serializer.py
class UserRegSerializer(serializers.ModelSerializer):
#重写create方法
def create():
user = super(UserRegSerializer,self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
或者也可以用django的信号量也可以解决
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.set_password(password)
instance.save()
然后还要app.py里面配置
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals