Appearance
上一章搞定了 JWT 登录,本章接着来实现用户信息的增删改查。
用户管理
用户管理涉及到对密码的操作,因此新写一个序列化器,覆写 def create(...)
和 def update(...)
方法:
python
# 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_field
,这是指定了解析超链接关系的字段。直观来说,将其配置为username
后,用户详情接口的地址表示为用户名而不是主键。
用户管理同样涉及的权限问题,因此新建 permissions.py
,写入代码:
python
# user_info/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsSelfOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj == request.user
这个权限类和之前写过的类似,确保非安全方法只能由本人操作。
前面写过一个类似的权限类,请读者自己试着合并代码吧。
铺垫工作做好了,最后就是写视图集:
python
# user_info/views.py
from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly
from user_info.serializers import UserRegisterSerializer
from user_info.permissions import IsSelfOrReadOnly
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, IsSelfOrReadOnly]
return super().get_permissions()
- 注册用户的 POST 请求是允许所有人都可以操作的,但其他类型的请求(比如修改、删除)就必须是本人才行了,因此可以覆写
def get_permissions(...)
定义不同情况下所允许的权限。permission_classes
接受列表,因此可以同时定义多个权限,权限之间是 and 关系。 - 注意这里的
lookup_field
属性,和序列化器中对应起来。
接着注册路由:
python
# drf_vue_blog/urls.py
...
from user_info.views import UserViewSet
router.register(r'user', UserViewSet)
...
用户的增删改查就完成了。可见 DRF 封装层级很高,常规功能完全隐藏在框架之中了。
试着发送一个 get 请求:
python
PS C:\Users\Dusai> http get http://127.0.0.1:8000/api/user/
HTTP/1.1 200 OK
...
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://127.0.0.1:8000/api/user/dusai/",
"username": "dusai"
},
{
"id": 2,
"url": "http://127.0.0.1:8000/api/user/Obama/",
"username": "Obama"
},
{
"id": 4,
"url": "http://127.0.0.1:8000/api/user/diudiu/",
"username": "diudiu"
}
]
}
可以看到详情地址不是主键值而是用户名了,这就是 lookup_field
发挥的作用。
其他测试工作就由读者自己完成了。
自定义动作
视图集除了默认的增删改查外,还可以有其他的自定义动作。
为了测试,首先写一个信息更加丰富的用户序列化器:
python
# user_info/serializers.py
...
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'username',
'last_name',
'first_name',
'email',
'last_login',
'date_joined'
]
接着就可以在视图集中新增代码,自定义动作了:
python
# user_info/views.py
...
from rest_framework.decorators import action
from rest_framework.response import Response
from user_info.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 解析名等常规需求。
发两个请求试试:
python
> http get http://127.0.0.1:8000/api/user/dusai/info/
HTTP/1.1 200 OK
...
{
"date_joined": "2020-06-15T09:23:27.417440Z",
"email": "dusaiphoto@foxmail.com",
"first_name": "du",
"id": 1,
"last_login": "2020-12-29T07:20:34.588221Z",
"last_name": "sai",
"username": "dusai"
}
> http get http://127.0.0.1:8000/api/user/sorted/
HTTP/1.1 200 OK
...
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://127.0.0.1:8000/api/user/dusai/",
"username": "dusai"
},
{
"id": 4,
"url": "http://127.0.0.1:8000/api/user/diudiu/",
"username": "diudiu"
},
{
"id": 2,
"url": "http://127.0.0.1:8000/api/user/Obama/",
"username": "Obama"
}
]
}
默认情况下,方法名就是此动作的路由路径。返回的 Json 也正确显示为方法中所封装的数据。
关于自定义动作详见官方文档。