Skip to content

Latest commit

 

History

History
248 lines (188 loc) · 6.84 KB

180-用户管理.md

File metadata and controls

248 lines (188 loc) · 6.84 KB

上一章搞定了 JWT 登录,本章接着来实现用户信息的增删改查。

用户管理

用户管理涉及到对密码的操作,因此新写一个序列化器,覆写 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_field,这是指定了解析超链接关系的字段。直观来说,将其配置为 username 后,用户详情接口的地址表示为用户名而不是主键。

用户管理同样涉及的权限问题,因此新建 permissions.py,写入代码:

# 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

这个权限类和之前写过的类似,确保非安全方法只能由本人操作。

前面写过一个类似的权限类,请读者自己试着合并代码吧。

铺垫工作做好了,最后就是写视图集:

# 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 属性,和序列化器中对应起来。

接着注册路由:

# drf_vue_blog/urls.py

...

from user_info.views import UserViewSet

router.register(r'user', UserViewSet)

...

用户的增删改查就完成了。可见 DRF 封装层级很高,常规功能完全隐藏在框架之中了。

试着发送一个 get 请求:

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 发挥的作用。

其他测试工作就由读者自己完成了。

自定义动作

视图集除了默认的增删改查外,还可以有其他的自定义动作。

为了测试,首先写一个信息更加丰富的用户序列化器:

# user_info/serializers.py

...

class UserDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            'id',
            '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 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 解析名等常规需求。

发两个请求试试:

> 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 也正确显示为方法中所封装的数据。

关于自定义动作详见官方文档