[Django] 19 - DRF: User API & Auth

Section 12, Build User API

基本流程

  • Test
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse

from rest_framework.test import APIClient
from rest_framework import status


CREATE_USER_URL = reverse('user:create')  # --> 要提前设置URL
TOKEN_URL = reverse('user:token')
ME_URL = reverse('user:me')


def create_user(**params):
    """Create and return a new user."""
    return get_user_model().objects.create_user(**params)


class PublicUserApiTests(TestCase):
    """Test the public features of the user API."""

    def setUp(self):
        self.client = APIClient()

    def test_create_user_success(self):
        """Test creating a user is successful."""
        payload = {
            'email': 'test@example.com',
            'password': 'testpass123',
            'name': 'Test Name',
        }
        res = self.client.post(CREATE_USER_URL, payload)

        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
user
= get_user_model().objects.get(email=payload['email']) self.assertTrue(user.check_password(payload['password'])) self.assertNotIn('password', res.data)
  •  Serializer

validated_data说明是已经通过了验证,也就是验证的过程后台已经自动处理了。

class UserSerializer(serializers.ModelSerializer):
    """Serializer for the user object."""

    class Meta:
        model = get_user_model()
        fields = ['email', 'password', 'name']
        extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}

    def create(self, validated_data):
        """Create and return a user with encrypted password."""
        return get_user_model().objects.create_user(**validated_data)

    def update(self, instance, validated_data):
        """Update and return user."""
        password = validated_data.pop('password', None)
        user = super().update(instance, validated_data)  # 父类的update也是返回 user

        if password:
            user.set_password(password)
            user.save()

        return user  # 子类重写了update,当然也要返回 user
  • View
from user.serializers import (
    UserSerializer,
    AuthTokenSerializer,
)


classCreateUserView(generics.CreateAPIView):
    """Create a new user in the system."""
    serializer_class = UserSerializer
  • URL
app_name = 'user'

urlpatterns = [ path('create/', views.CreateUserView.as_view(), name='create'), ]

 

 

Permission & Token

  • 登录访问后,获得一个”有效的“ token [TEST]
class PublicUserApiTests(TestCase):
    """Test the public features of the user API."""

   ... ...
def test_create_token_for_user(self): """Test generates token for valid credentials.""" user_details = { 'name': 'Test Name', 'email': 'test@example.com', 'password': 'test-user-password123', } create_user(**user_details) payload = { 'email': user_details['email'], 'password': user_details['password'], } res = self.client.post(TOKEN_URL, payload) self.assertIn('token', res.data) self.assertEqual(res.status_code, status.HTTP_200_OK) def test_create_token_bad_credentials(self): """Test returns error if credentials invalid.""" create_user(email='test@example.com', password='goodpass') payload = {'email': 'test@example.com', 'password': 'badpass'} res = self.client.post(TOKEN_URL, payload) self.assertNotIn('token', res.data) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) def test_create_token_email_not_found(self): """Test error returned if user not found for given email.""" payload = {'email': 'test@example.com', 'password': 'pass123'} res = self.client.post(TOKEN_URL, payload) self.assertNotIn('token', res.data) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) def test_create_token_blank_password(self): """Test posting a blank password returns an error.""" payload = {'email': 'test@example.com', 'password': ''} res = self.client.post(TOKEN_URL, payload) self.assertNotIn('token', res.data) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) def test_retrieve_user_unauthorized(self): """Test authentication is required for users.""" res = self.client.get(ME_URL) self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
  • 序列化验证 [SERIALIZER]
class AuthTokenSerializer(serializers.Serializer):
    """Serializer for the user auth token."""
    email = serializers.EmailField()
    password = serializers.CharField(
        style={'input_type': 'password'},
        trim_whitespace=False,
    )

    def validate(self, attrs):
        """Validate and authenticate the user."""
        email = attrs.get('email')
        password = attrs.get('password')
        user = authenticate(
            request=self.context.get('request'),
            username=email,
            password=password,
        )
        if not user:
            msg = _('Unable to authenticate with provided credentials.')  # ----> 
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user  # 返回有用的 user,赋值 attrs
        return attrs
    • 自动翻译 
from django.contrib.auth import (
    get_user_model,
    authenticate,
)
from django.utils.translation import gettext as _  # --> 自动翻译,牛

from rest_framework import serializers
  •  [VIEW]
"""
Views for the user API.
"""
from rest_framework import generics, authentication, permissions
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.settings import api_settings

from user.serializers import (
    UserSerializer,
    AuthTokenSerializer,
)


class CreateUserView(generics.CreateAPIView):
    """Create a new user in the system."""
serializer_class = UserSerializer class CreateTokenView(ObtainAuthToken): """Create a new auth token for user."""
serializer_class = AuthTokenSerializer renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES class ManageUserView(generics.RetrieveUpdateAPIView): """Manage the authenticated user.""" serializer_class = UserSerializer authentication_classes = [authentication.TokenAuthentication] permission_classes = [permissions.IsAuthenticated]
# Rule: 定义了上述的三个成员变量,下面自然也就能返回一个合格的user
def get_object(self): """Retrieve and return the authenticated user.""" return self.request.user

  

 之前是测试token,这里是测试permission的一个样例。

 

    def test_post_me_not_allowed(self):
        """Test POST is not allowed for the me endpoint."""
        res = self.client.post(ME_URL, {})

        self.assertEqual(res.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

 

 简而言之,为了让这个测试成功,针对user而言,要调用合适的库和系统类,按照规则设置,是比较机械化的过程。

Swagger: 直接调用 ./api/user/me会发生”无法验证“的错误。

首先登录后能获得一个 token,一般在前端会自动保存起来。

 

Swagger内置设置token的功能。

 

However, $curl,是不是如此明文,不太安全?

 

Session不香吗,为什么还要Token?
posted @ 2022-06-19 14:40  郝壹贰叁  阅读(65)  评论(0编辑  收藏  举报