[Django] 19 - DRF: User API & Auth
基本流程
- 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,是不是如此明文,不太安全?