上一章我们改写了django的认证机制,本章对用户api进行丰富,即用户的增删改查。
用户管理
用户管理涉及到对密码的操作,因此新写一个序列化器,覆写def create(...)
,def update(...)
方法:
# user_info/serializers.py
class UserRegisterSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='user-detail', lookup_field='username')
class Meta:
model = User
fields = [
'url',
'id',
'username',
'password'
]
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
return super().update(instance, validated_data)
- 注意
def update(...)
时,密码需要单独拿出来通过set_password
进行加密,而不能直接保存明文。 - 超链接字段有一个
lookup_fields
,这是指定了他解析超链接关系的字段,只管来说,指定其后,连接后面不再是外键id,而是用户名。
用户管理同样涉及的权限问题,因此新建permissions.py
,写入以下代码:
# user_info/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsSelfOfReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj == request.user
这个权限和之前写过的差不多,非安全方法都是只有本人能操作。
到这里很多代码出现重复,介于多个app之间,可以写一个大的Base类进行继承,减少代码量。
铺垫工作做好了,我们在写视图集。
from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly
from .permissions import IsSelfOfReadOnly
from .serializers import UserRegisterSerializer
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserRegisterSerializer
lookup_field = 'username'
def get_permissions(self):
if self.request.method == 'POST':
self.permission_classes = [AllowAny]
else:
self.permission_classes = [IsAuthenticatedOrReadOnly, IsSelfOfReadOnly]
return super().get_permissions()
- 注册用户的POST请求时允许所有人都可以操作的,但其他类型的请求(修改,删除)就必须是本人,因此可以覆写
def get)permissions
定义不同情况下所允许的权限。permissions_classes
接受列表,因此可以同时定义多个权限,权限之间是and。 - 注意这里的
lookup_field
属性,必须和序列化器的对应起来。
注册路由:
from user_info.views import UserViewset
···
router.register(r'user', UserViewset)
···
用户的增删改查就完成了,课件DRF封装层级很高,常规功能完全不需要你去处理。
测试
发送一个get请求:
>http http://127.0.0.1:8000/api/user/
HTTP/1.1 200 OK
...
{
"count": 3,
"next": "http://127.0.0.1:8000/api/user/?page=2",
"previous": null,
"results": [
{
"id": 1,
"url": "http://127.0.0.1:8000/api/user/xianwei/",
"username": "xianwei"
},
{
"id": 2,
"url": "http://127.0.0.1:8000/api/user/Obama/",
"username": "Obama"
}
]
}
我么会发现,url最后面不再是用户外键id而是username。这就是lookup_field
发挥的作用。
其他工作可自行测试。
补充
自定义动作和丰富序列化器:
为了测试,首先修改用户序列化器,即fileds字段:
# user_info/serializers.py
# 新建
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'username',
'last_name',
'first_name',
'email',
'last_login',
'date_joined'
]
修改视图集,添加自定义动作:
# user_info/views.py
from rest_framework.decorators import action
from rest_framework.response import Response
from .serializers import User
from .serializers import UserDetailSerializer
class UserViewset(viewsets.ModelViewSet):
...
@action(detail=True, methods=['get'])
def info(self, request, username=None):
queryset = User.objects.get(username=username)
serializer = UserDetailSerializer(queryset, many=False)
return Response(serializer.data)
@action(detail=False)
def sorted(self, request):
users = User.objects.all().order_by('-username')
page = self.paginate_queryset(users)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(users, many=True)
return Response(serializer.data)
魔法都在装饰器 @action 里,它的参数可以定义是否为详情的动作、请求类型、url 地址、url 解析名等常规需求。
发两个请求试试:
>http http://127.0.0.1:8000/api/user/xianwei/info/
HTTP/1.1 200 OK
...
{
"date_joined": "2021-06-13T14:58:00",
"email": "",
"first_name": "",
"last_login": null,
"last_name": "",
"username": "xianwei"
}
>http http://127.0.0.1:8000/api/user/sorted/
HTTP/1.1 200 OK
...
{
"count": 3,
"next": "http://127.0.0.1:8000/api/user/sorted/?page=2",
"previous": null,
"results": [
{
"url": "http://127.0.0.1:8000/api/user/xianwei/",
"username": "xianwei"
},
{
"url": "http://127.0.0.1:8000/api/user/root/",
"username": "root"
}
]
}
这里我把id都去掉了。
默认情况下,方法名就是此动作的路由路径。返回的 Json 也正确显示为方法中所封装的数据。
关于自定义动作详见官方文档。
公告
后面一段时间我们以更新pyhotn scrapy框架和分布式爬虫为主,vue前端学习与部属会穿插在里面,感谢支持!!!