Django DRF框架用起来还是有一些难度的,需要做的配置,需要导的包很多,所以需要多多练习才能掌握它的使用。此文记录了使用Django rest_famework框架开发用户模块相关接口的流程,需要注意的点,以及源码。期间重写了Django用户模型类,自定义了Django Response消息格式等。此文不过多赘述环境及各种依赖的安装过程,如果后续有需要的话再做进一步的补充。
创建项目: django-admin startproject my_project
创建应用: 进入到manage.py所在目录中,python manage.py startapp user
对项目做初步的配置:
打开项目配置文件settings.py
# 调试开启
DEBUG = True
# 允许所有IP访问
ALLOWED_HOSTS = ['*']
# 应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # rest_framework框架
'user', # 刚刚创建的应用
]
# 把时区先设置好
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
因为是前后端分离的开发,为了不让接口裸奔,所以必须要和配套的安全机制一起使用。Django提供了两种Token,这里使用JWT Token,需要事先安装djangorestframework-jwt
库。
最终目标是通过访问地址: http://localhost:8000/login/
来获取JWT Token信息。
打开项目配置文件settings.py
,在原有基础上添加以下配置:
...
# 应用
INSTALLED_APPS = [
...,
'rest_framework',
'rest_framework.authtoken', # 新添加的
...,
]
# rest_framework配置
REST_FRAMEWORK = {
# 配置默认过滤器
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
# 配置DRF Token
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # JWT认证
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', # 这是接口文档需要用到的
# 配置JWT
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=3), # 过期时间
'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置Token头为 JWT xxxx
'JWT_ALLOW_REFRESH': False, # 禁止刷新
}
打开项目配置目录中的urls.py
,添加以下代码:
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
path('docs/', include_docs_urls(title="my_project接口文档")), # 访问项目接口文档的地址
path('login/', obtain_jwt_token), # 获取JWT token
]
python manage.py migrate
python manage.py createsuperuser
但是这么干了之后,调用login接口返回的只有Token,这显然是不够用的,多数时候我们还需要用户的其他信息,比如电话、用户名等等,这时候就需要通过重写用户模型类+自定义返回认证消息来解决了。
打开user应用下的models.py
,添加以下代码:
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
# 重写用户模型类 需要继承AbstractUser
class MyUser(AbstractUser):
SEX = (
('男', '男'),
('女', '女'),
)
LEVEL = (
('普通会员', '普通会员'),
('银卡会员', '银卡会员'),
('金卡会员', '金卡会员'),
('铂金会员', '铂金会员'),
('钻石卡会员', '钻石卡会员'),
)
STATUS = (
('正常', '正常'),
('异常', '异常'),
)
truename = models.CharField(verbose_name="真实姓名", blank=True, max_length=50)
mobile = models.CharField(verbose_name="手机号码", max_length=11, default="")
sex = models.CharField(max_length=8, verbose_name="性别", choices=SEX, blank=True)
birthday = models.DateField(blank=True, null=True)
user_img = models.ImageField(verbose_name="头像", upload_to='user_img', default="")
level = models.CharField(max_length=8, verbose_name="会员等级", default="普通会员", choices=LEVEL)
status = models.CharField(max_length=8, verbose_name="状态", default="正常", choices=STATUS)
在项目全局配置文件settings.py
中,添加AUTH_USER_MODEL
项:
...
AUTH_USER_MODEL = 'user.MyUser' # 应用名.模型类名
...
在user应用目录下,新建jwt_utils.py
,然后添加以下代码:
"""
自定义JWT返回的认证信息
"""
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'id': user.id,
'username': user.username,
'email': user.email,
'is_active': user.is_active,
'mobile': user.mobile,
}
在项目全局配置文件settings.py
中,添加JWT_RESPONSE_PAYLOAD_HANDLER
项:
# rest_framework配置
REST_FRAMEWORK = {
...,
'JWT_ALLOW_REFRESH': False, # 禁止刷新
'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.jwt_utils.jwt_response_payload_handler', # 自定义认证消息
}
再次请求 http://localhost:8000/login/,输入用户名和密码之后,看到的就是自定义的认证信息:
获取Token的接口至此结束,接下来就是开发用户注册的接口了。
用户注册接口,此处我们假设接口的入参为:用户名、密码、手机号码和邮箱这四个字段,还需要对这几个字段进行校验。
在user应用目录下
,新建serializer.py
,然后添加以下代码:
import re
from pyexpat import model
from rest_framework import serializers
from .models import MyUser
# 用户注册序列化类
class MyUserRegSerializer(serializers.ModelSerializer):
class Meta:
model = MyUser
fields = ('username', 'password', 'email', 'mobile') # 设置显示的字段
def validate_username(self, username):
# 判断用户姓名是否已注册
if MyUser.objects.filter(username=username).count():
raise serializers.ValidationError("用户姓名已存在,请查询")
return username
def validate_mobile(self, mobile):
# 判断手机号码是否已注册
if MyUser.objects.filter(mobile=mobile).count():
raise serializers.ValidationError("手机号码已存在,请查询")
# 判断手机号码格式是否有误
REGEX_MOBILE = '1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError("非法手机号码")
return mobile
def create(self, validated_data):
# 重写create 给密码加密
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
打开user应用目录下的views.py
,添加以下代码:
from django.shortcuts import render
# Create your views here.
from .models import MyUser
from .serializer import MyUserRegSerializer
from .custommodelviewset import CustomModelViewSet
from rest_framework.viewsets import ModelViewSet
# 在此处你可以重写ModelViewSet,然后继承自定义ModelViewSet, 让它能返回标准的自定义消息
class MyUserViewSet(ModelViewSet):
queryset = MyUser.objects.all()
serializer_class = MyUserRegSerializer
配置路由(需要分别在项目全局配置目录中的urls.py与user应用下的urls.py中添加配置项)
:
项目配置目录中的urls.py
(项目的根URLS)中添加:from django.urls import path, include
urlpatterns = [
...,
path('login/', obtain_jwt_token), # 获取JWT token
path('user/', include('user.urls')), # 新引入包含user应用的url
]
user应用下的urls.py
中添加:from django.urls import path, re_path
from .views import MyUserViewSet
user_list = MyUserViewSet.as_view({
'get': 'list',
'post': 'create',
})
urlpatterns = [
path('users/', user_list),
]
访问http://localhost:8000/user/users/:
POST可以创建用户,直接提交到数据库里。那么,接下来就是用户登接口的开发了。
所谓登录,其实不过是一个鉴权的过程。鉴权通过则登录成功,到下一步操作;否则登陆失败,进行注册或其他。前面我们已开发完成的获取Token的接口,当访问http://localhost:8000/login/
的时候,需要输入用户名和密码,然后后端校验,校验成功则返回Token;现在我们想让用户通过手机号和密码也可以登录,那么就需要重写Django自带的ModelBackend来实现。
打开user应用下的views.py
,添加以下代码,增加自定义验证类CustomBackend:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.contrib.auth import authenticate, get_user_model, login, logout
myuser = get_user_model()
class CustomBackend(ModelBackend):
"""自定义用户信息验证类"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
# 此处还可以加别的验证项 如邮箱等
myuser = MyUser.objects.get(Q(username=username) | Q(mobile=username))
if myuser.check_password(password):
return myuser
except Exception as e:
return None
打开项目全局配置文件settings.py
,添加以下代码:
# 自定义用户认证项
AUTHENTICATION_BACKENDS = {
'user.views.CustomBackend',
}
重新访问http://localhost:8000/login/
,输入电话号码与密码:
至此,使用电话和用户名都可以实现鉴权,接下来就是用户信息查询、修改和删除的接口了。
用户注册和用户修改所需的字段是不一样的,所以需要重新定义一个用户修改的序列化类。再进行相关的权限配置即可。
打开user应用下的serializers.py文件
,增加MyUserUpdateSerializer类:
# 用户信息修改序列化类
class MyUserUpdateSerializer(serializers.ModelSerializer):
username = serializers.CharField(
read_only=True,
error_messages={
'required': "请输入用户名",
'blank': "用户名不允许为空",
'min_length': "用户名长度至少3位",
}
)
mobile = serializers.CharField(read_only=True)
class Meta:
model=MyUser
fields=('username', 'truename', 'user_img', 'sex', 'email', 'mobile', 'level', 'status')
打开user应用下的views.py,
修改原有的视图,添加权限,并且让不同的action返回不同的序列化器。增删改需要用户身份认证,注册和登录不需要权限。
下面是整个用户模块视图的所有代码:
from tkinter import E
from django.shortcuts import render
# Create your views here.
from .models import MyUser
from .serializer import MyUserRegSerializer, MyUserUpdateSerializer
from .custommodelviewset import CustomModelViewSet
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
# from notebook.auth.security import set_password
from django.contrib.auth import authenticate, get_user_model, login, logout
from rest_framework_extensions.cache.mixins import CacheResponseMixin
from rest_framework_jwt.authentication import JSONWebTokenAuthentication # jwt用户认证
from rest_framework.authentication import SessionAuthentication
from rest_framework import permissions
myuser = get_user_model()
class MyUserViewSet(CacheResponseMixin, CustomModelViewSet):
queryset = MyUser.objects.all()
serializer_class = MyUserRegSerializer
authentication_classes = (
JSONWebTokenAuthentication,
SessionAuthentication,
)
# 重写get_serializer_class 使不同的action返回不同的序列化器
def get_serializer_class(self):
if self.action == 'create':
return MyUserRegSerializer
elif self.action == 'retrieve':
return MyUserUpdateSerializer
elif self.action == 'update':
return MyUserUpdateSerializer
return MyUserUpdateSerializer
# 重写get_permissions 使不同的action 需要不同的权限
def get_permissions(self):
if self.action == 'retrieve':
return [permissions.IsAuthenticated()]
elif self.action == 'update':
return [permissions.IsAuthenticated()]
else:
return []
class CustomBackend(ModelBackend):
"""自定义用户信息验证类"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
myuser = MyUser.objects.get(Q(username=username) | Q(mobile=username))
if myuser.check_password(password):
return myuser
except Exception as e:
return None
注意: 此处使用了rest_framework的缓存机制,需要pip install drf-extensions
然后在全局配置文件settings.py中
添加以下配置:
# DRF扩展
REST_FRAMEWORK_EXTENSIONS = {
# 缓存时间s
'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
# 缓存存储
'DEFAULT_USE_CACHE': 'default',
}
CustomModelViewSet是一个使用了自定义的消息格式的视图,但是原来书里的代码似乎有BUG,原来的代码:
"""
使用自定义消息格式 需要重写ModelViewSet
也就是自定义ModelViewSet
"""
from rest_framework import status, viewsets
from .customresponse import CustomResponse
class CustomModelViewSet(viewsets.ModelViewSet):
# 重写CreateModelMixin类中的create()方法
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return CustomResponse(
data=serializer.data,
code=201,
msg='OK',
status=status.HTTP_201_CREATED,
headers=headers
)
# 重写ListModelMixin类总的list()方法
def list(self, request, *rags, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return CustomResponse(
data=serializer.data,
code=200,
msg='OK',
status=status.HTTP_200_OK
)
# 重写RetrieveModelmixin类中的retrieve()方法
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return CustomResponse(
data=serializer.data,
code=200,
msg='OK',
status=status.HTTP_200_OK
)
# 重写UpdateModelMixin中的update方法
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return CustomResponse(
data=serializer.data,
code=200,
msg='OK',
status=status.HTTP_200_OK
)
# 重写DestroyModelMixin类中的destroy
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return CustomResponse(
data=[],
code=204,
msg='OK',
status=status.HTTP_204_NO_CONTENT
)
CustomResponse:
"""
自定义消息格式
"""
from rest_framework.response import Response
from rest_framework.serializers import Serializer
class CustomResponse(Response):
def __init__(self, data=None, code=None, msg=None, status=None, template_name=None, headers=None, exception=False, content_type=None, **kwargs):
super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
'测试信息'
)
raise AssertionError(msg)
self.data = {
'code': code,
'msg': msg,
'data': data
}
self.data.update(kwargs)
self.template_name = template_name
self.exception = exception
self.content_type = content_type
if headers:
for name, value in headers.items():
self[name] = value
打开user应用下的urls.py
,添加一下代码:
user_detail = MyUserViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy',
})
urlpatterns = [
...,
path('users//' , user_detail), # 新增的路由
]
在postman中测试的结果(注意此时需要在headers中增加Authentication:JWT):
PUT和DELETE也可以在POSTman中进行测试,大家可以试一试。