项目搭建
环境配置
a、找个位置运行:python -m django startproject tkplat 项目名。
- 先搞个
虚拟环境
, 再安装django - 创建项目之后,用pycharm打开,settings——project:tkplat——python interpreter解析器选到你的虚拟环境
b、gitee中,创建仓库, 名称和项目名字一样,叫tkplat (用gitee进行代码管理
)
c、项目中git init ,commit,push等推到gitee。
d、生成requirements.txt文件,进行包管理(pip freeze >> requirements.txt生成,pip install -r requirements.txt),文章后面我把它贴出来
- 先执行一次,看看效果,是否是新虚拟环境,git使用是否正常,
后续有装新的包,就重新生成一次requirements.txt
项目配置
settings中改时区
LANGUAGE_CODE = 'zh-hans' # 中文
TIME_ZONE = 'Asia/Shanghai' # 时区
USE_I18N = True # internationalization
USE_TZ = False # true 使用UTC时间,false 采用本地时区
django连接mysql
a、mysql环境(略)
b.创建库
CREATE DATABASE `tkplat` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
c .配置setting
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'tkplat', # 数据库名
'USER': 'root', # 用户名
'PASSWORD': 'sq', # 密码
'HOST': '127.0.0.1',
'PORT': '3306',
'TEST':{
'CHARSET': 'utf8',
'COLLATION': 'utf8_general_ci', }
}
}
执行迁移(往mysql中生成基础表)Tools- run manage.py task ,后, migrate (或者 python manage.py migrate)
运行时发现,需要装python库mysqlclient ,用时对python库进行管理 pip freeze >> requirements.txt
执行成功后,
- 可以启动项目了看见
django火箭
了,(点右上角启动按钮 或者python manage.py runserver) - 基本表也生成了
跨域问题
当使用浏览器访问时,会提示跨域
解决:settings中进行配置
首先要安装:
'corsheaders.middleware.CorsMiddleware', # 跨域
# 配置跨域,如果为True,表示允许所有的域名跨域访问
CORS_ORIGIN_ALLOW_ALL = True
# 配置跨域, 允许所有的请求头
CORS_ALLOW_HEADERS = ('*')
配置之后,就成功了
app应用
使用python manage.py startapp system (后续使用这个时我直接省略前面的)
注意: 生成的一些新的文件,pycharm中是红色的,都给他git add,别忘记了,不然gitee中代码不全,我就犯了这个错误~~
settings中INSTALLED_APPS注册应用(略)
对于django不太熟练的,可以现在system中,models.py 写个简单的模型:
如下:
# 学生表(名字,性别)
class Student(models.Model):
GENDER_CHOICES = (
(1, "男"), # 前面是存储的值,后面是显示的值(admin页面显示的值)
(2, "女"),
)
name = models.CharField(max_length=100)
sex = models.SmallIntegerField(choices=GENDER_CHOICES, verbose_name="性别", null=True, blank=True, help_text="性别")
执行命令:makemigrations, 和migrate
数据库中就有了学生表
在system应用 的 admin.py 中, 访问admin页面(先creatsuperuser一个账号),也有了这个模型
from . import models
admin.site.register(models.Student)
如下图:就可以确认新增的应用已经成功了
重写User模型
先写一个模型基类。新建 utils包。 新建models.py, 其中定义可以公共使用的模型字段,如创建人,创建时间,更新时间,更新人,id等,
- 其中settings.AUTH_USER_MODEL,会报错,是因为没有配置
"""
@Remark: 自定义模型基类
"""
from django.db import models
from tkplat_django import settings # 项目配置文件(改成项目名)
# 2.模型抽象基类A
class BaseModel(models.Model):
# AutoField类型字段,自增长的IntegerField类型字段,primary_key=True表示主键【用这个好看点】
id = models.AutoField(primary_key=True, verbose_name="ID", help_text="ID")
# 2.关联字段_创建人
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, related_name="%(app_label)s_%(class)s_creator",
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
# 3.其他字段
modifier = models.CharField(max_length=50, null=True, blank=True, help_text="修改人", verbose_name="修改人") # 填真实姓名
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
desc = models.CharField(max_length=255, verbose_name="备注", null=True, blank=True, help_text="备注")
def __str__(self):
if hasattr(self, 'name'):
return self.name
#elif hasattr(self, 'url'):
#return self.url
else:
return self.desc
class Meta:
abstract = True
verbose_name = '基础模型'
verbose_name_plural = verbose_name
system应用中的models.py 中 , 新增用户模型, 并且继承模型基类
from django.db import models
from django.contrib.auth.models import AbstractUser
from utils.models import BaseModel
TABLE_PREFIX = "sys_" # 表前缀
# 2.用户表
class User(AbstractUser, BaseModel):
IDENTITY_CHOICES = (
(0, "超级管理员"),
(1, "系统管理员"),
(2, "普通用户"),
)
GENDER_CHOICES = (
(1, "男"), # 前面是存储的值,后面是显示的值(admin页面显示的值)
(2, "女"),
)
# 1.重写2个字段
username = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='用户名', help_text="用户名")
email = models.EmailField(max_length=60, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
# 3.自定义字段
realname = models.CharField(max_length=30, verbose_name="真实姓名", help_text="真实姓名")
mobile = models.CharField(max_length=11, verbose_name="电话", null=True, blank=True, help_text="电话")
identity = models.SmallIntegerField(choices=IDENTITY_CHOICES, verbose_name="用户类型", null=True, blank=True, default=1,
help_text="用户类型")
avatar = models.CharField(max_length=200, verbose_name="头像", null=True, blank=True, help_text="头像")
nickname = models.CharField(max_length=100, help_text="昵称", verbose_name="昵称", default="")
sex = models.SmallIntegerField(choices=GENDER_CHOICES, verbose_name="性别", null=True, blank=True, help_text="性别")
class Meta:
db_table = TABLE_PREFIX + "user"
verbose_name = '用户表'
ordering = ('-id',)
settings文件中要指定django用新定义的模型
# 指定django用户模型为--->自定义的
AUTH_USER_MODEL = 'system.User'
建议把数据库删了,重新创建。 同时system中的migrations也删了重新来
注: 要加个应用名后面
makemigrations system
migrate 一下,迁移。
效果:可以发现用户表变成了sys_user, 其中还有自定义的字段,如realname, 和模型基类中的字段create_datetime 创建时间等
知识点: 模型中定义选项类型字段时,使用了choices,值为元组套元组, **左边那个值和和定义的字段类型一致,它是用来存的,右边那个值是显示(比如说会显示到admin页面)值
如下:mysql中存的是2, admin页面显示的是女
序列化器中,有一个固定格式的方法,如要获取一个对象,sex的值,obj.get_sex_display()
可以这么写
sex_value = serializers.SerializerMethodField(method_name='get_sex_value')
def get_sex_value(self, obj):
return obj.get_sex_display()
创建apps包管理应用
在项目根路径下,新建apps包,目的是将system应用,和将来已有的应用,都可以拖进来。
把system拖进来后配置:,拖的时候把他去掉
- 修改setting.py , 添加
# apps 绝对路径( sys.path是个列表)
sys.path.insert(0,os.path.join(BASE_DIR,"apps"))
- 做如下操作: 之后,应用就可以被识别了,可以发现setting中注册应用的地方,system没有黄色了
就把它当成正常的应用来用就可以了
规范后端接口
先应用中,admin.py 中,增加一行, 让用户在admin页面能看到
admin.site.register(models.User)
再次createsuperuser创一个用户,登录admin页面看看
1.用模型序列化器和模型视图集实现增刪改查
应用中,新建 serializers.py 文件, (如果模型多的话,可以采用新建一个serializers包的方式,下面一个模型用一个py文件)
from rest_framework.serializers import ModelSerializer
from .models import User
发现:rest_framework 是一个第三方库需要安装 和注册。
pip install djangorestframework
# 然后pip freeze >> requirements.txt
回到serializers.py中
from rest_framework.serializers import ModelSerializer
from .models import User
class UserSerializer(ModelSerializer):
class Meta:
model =User
fields = '__all__'
在views.py中
from .serializers import UserSerializer
from .models import User
from rest_framework.viewsets import ModelViewSet
class UserViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
应用下新建urls.py.
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
system_router = DefaultRouter()
system_router.register('users',views.UserViewSet)
urlpatterns = [
# E.视图集
path('', include(system_router.urls))
]
外层的urls.py , 关联这里的urls
path("sys/", include("system.urls")),
查
方式1:访问浏览器:http://127.0.0.1:8000/sys/users/ (如果app没有注册restframework,打开浏览器就不到)
方式2:用postman请求用get也可以
增
用postman 或者用 rf的页面新增后,新增的密码是没有加密的。所有还不能用作登录(后面解决)。
改和删
2.规范项目响应结构
我们通过写视图集基类来实现, 可以改变视图集实现的增删改查的响应结构。
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
restframwork增删改查接口响应统一
源码如下:
删除,使用Reponse返回时,只传了status,所以看到响应为空
更新、新增,查详情、查列表、时传了data serializer.data
做法:
第一步:
是重写from rest_framework.response import Response 的 Response
继承了父类,还是需要传data,msg,status等。
在utils包中,新建json_response.py
"""
@Remark: 自定义的JsonResonpse返回内容
"""
from rest_framework.response import Response
# 1.返回单一数据
class ResponseOk(Response):
"""
不包含分页信息的接口返回,主要用于查询单条数据
(1)默认code返回200, 不支持指定其他返回码
"""
def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
content_type=None,):
std_data = {
"code": 200,
"msg": msg,
"result": data
}
super().__init__(std_data, status, template_name, headers, exception, content_type)
# 2.返回数据列表
class ResponseList(Response):
"""
标准响应成功的返回
(1) 返回响应数据:ResponseList(data) 或者 ResponseList(data=data)
(2) 默认code返回200, 不支持指定其他返回码
"""
def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
content_type=None,page=1,limit=1,total=1):
std_data = {
"code": 200,
"msg": msg,
"result": {
"page": page,
"limit": limit,
"total": total,
"data": data
}
}
super().__init__(std_data, status, template_name, headers, exception, content_type)
# 3.返回异常
class ResponseError(Response):
"""
标准响应错误的返回
(1) 返回错误原因:ResponseError(msg='xxx')
(2) 返回指定错误码:ResponseError(code=xxx),默认错误码返回400
"""
def __init__(self, data=None, msg='error', code=400, status=None, template_name=None, headers=None,
exception=False, content_type=None):
std_data = {
"code": code,
"msg": f"走了error即走了exception:{msg}",
"result": data
}
super().__init__(std_data, status, template_name, headers, exception, content_type)
第二步
写视图集基类,重写增删改查的响应
在utils包中,新建viewset.py。
如下的代码中,使用了第三方库,需要安装和注册 (用法后面介绍)
代码如下
其中:
a. 支持写新增用的序列化器,和更新用的序列化器
b. 写了权限配置(先暂时注释),后期的业务视图集都继承它时,相当于就做好了配置
c. 过滤搜索和排序方式做好了配置 ,具体的视图集中指定具体的字段就可以了
"""
@Remark: 自定义视图集基类
"""
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
from utils.json_response import ResponseList, ResponseError, ResponseOk
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
class BaseModelViewSet(ModelViewSet):
"""
自定义的ModelViewSet:
统一标准的返回格式; 新增,查询,修改可使用不同的序列化器
(1)create_serializer_class 新增时,使用的序列化器
(2)update_serializer_class 修改时,使用的序列化器
(3)重写增删改查查方法,使其返回统一响应消息格式
(4)新增批量删除方法
(5)配置了添加过滤器、搜索与排序 -
"""
# 0、原来的
queryset = None # 指定查询集(所有的查询对象)
serializer_class = None # 指定序列化器
# 1.不同序列化器支持
create_serializer_class = None # 新增时使用的序列化器
update_serializer_class = None # 修改时使用的序列化器
# 3.权限认证
# permission_classes = [
# IsAuthenticated,
# ]
# 4.过滤、搜索与排序
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] # 后端过滤、搜索与排序
filterset_fields = [] # 4.1 过滤字段
search_fields = [] # 4.2 搜索字段
ordering_fields = '__all__' # 4.3 排序字段,所有字段
# 8.重写获取序列化器类(因为新增、修改可以指定不同的系列化器)
def get_serializer_class(self):
"""新增时,如果定义了create_serializer_class,就用此序列化器
更新时,如果定义了update_serializer_class,就用此序列化器
否则用 serializer_class 此序列化器
"""
action_serializer_name = f"{self.action}_serializer_class"
# 8.2 判断视图集中是否存在
action_serializer_class = getattr(self, action_serializer_name, None)
if action_serializer_class:
return action_serializer_class
# 8.3 不存在,则使用默认的
return super().get_serializer_class()
# A.重写新增API
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 ResponseOk(data=serializer.data, msg="新增成功")
# B.重写删除API
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.delete()
return ResponseOk(msg="删除成功")
# C.重写修改API
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)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return ResponseOk(data=serializer.data, msg="修改成功")
# D.重写查询列表API
def list(self, request, *args, **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 ResponseList(data=serializer.data, msg="操作成功")
# E.重写查看详细API
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return ResponseOk(data=serializer.data, msg="操作成功")
# F.批量删除
@action(methods=['delete'], detail=False, url_path="batch_delete")
def batch_delete(self, request, *args, **kwargs):
request_data = request.data
keys = request_data.get('id', None)
if keys:
self.get_queryset().filter(id__in=keys).delete()
return ResponseList(data=[], msg="删除成功!")
else:
return ResponseError(msg="未获取到id字段")
system应用中,views中UserViewSet,改变成继承上面写的视图集基类
响应结构改变成功
统一异常响应
首先,这里可以查看到源码,进去到方法下打个断点,当响应异常时都会进来
from rest_framework.views import exception_handler
还是用上面的例子,当password没有的时候
当需要权限时,会走到这里
还有一种异常,当路由不存在时,代码都走不进来(需要考虑下)
现在来重新excetion_handler,
- 在utils包中,新建exception.py
其中的原理,只是继承了原来的exception_handler方法。然后给不同的状态增加了code,并使用ResponseError来返回 - settings.py配置,让restframework 用自己写的异常处理
REST_FRAMEWORK = {
# 1.分页器
# 'DEFAULT_PAGINATION_CLASS': 'utils.pagination.MyPageNumberPagination',
# 2.自定义异常处理函数
'EXCEPTION_HANDLER': 'utils.exception.my_exception_handler',
# 其他:api返回时日期时间格式配置
# 'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
# 'DATE_FORMAT': "%Y-%m-%d",
}
import logging
from django.db import IntegrityError
from django.db.models import ProtectedError
from rest_framework.exceptions import AuthenticationFailed, NotAuthenticated, APIException, ValidationError
from rest_framework.views import exception_handler
from utils.json_response import ResponseError
# 自定义异常处理函数
# 1.第一个参数:错误的原因
# 2.第二个参数:上下文的意思
logger = logging.getLogger("")
def my_exception_handler(exc, context):
"""
统一异常拦截处理
目的:(1)取消所有的500异常响应,统一响应为标准错误返回
(2)准确显示错误信息
:param ex:
:param context:
:return:
"""
response = exception_handler(exc, context)# 调用它自己的函数,返回一个响应对象
code=400
error='未知错误'
# 如果响应对象不为空
if response:
# 1.认证失败
if isinstance(exc, AuthenticationFailed):
code = 401
error = str(exc)
# 2.未认证
elif isinstance(exc, NotAuthenticated):
code = 401
error = str(exc)
# 3.数据校验
elif isinstance(exc, ValidationError):
errorMsg = exc.detail
try:
error = ''
for key in errorMsg:
try:
if key:
error = error + str('%s:%s' % (key, errorMsg[key][0]))
else:
error = errorMsg[key]
except:
error = errorMsg[key]
except:
error = errorMsg[0]
elif isinstance(exc, APIException):
error = exc.detail
elif isinstance(exc, TypeError):
error = str(exc) # 原样输出错误
elif isinstance(exc, ProtectedError):
error = "删除失败:外键约束"
elif isinstance(exc, Exception):
error = str(exc) # 原样输出错误
logger.error(error)
else:
error = str(exc)
else:
if isinstance(exc, TypeError):
error = str(exc) # 原样输出错误
elif isinstance(exc, IntegrityError):
error = str(exc) # 原样输出错误
else:
error = str(exc)
print(str(exc))
return ResponseError(msg=error,code=code)
百度一下:
如下图两种异常的情况,就算处理成功了
3.重写djangoRf的分页
- utils包中新建pagination.py(
代码中指定了,请求时要传 limit 和page, 并且响应时会用到规范响应
) - settings中指定REST_FRAMEWORK的分页器配置
REST_FRAMEWORK = {
# 1.分页器
'DEFAULT_PAGINATION_CLASS': 'utils.pagination.MyPageNumberPagination',
# 2.自定义异常处理函数
'EXCEPTION_HANDLER': 'utils.exception.my_exception_handler',
# 其他:api返回时日期时间格式配置
# 'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
# 'DATE_FORMAT': "%Y-%m-%d",
}
from rest_framework.pagination import PageNumberPagination
from utils.json_response import ResponseList
from django.core import paginator
# 自定义分页器,继承DRF默认的分页器
class MyPageNumberPagination(PageNumberPagination):
"""
自定义分页器
"""
# 1.重要参数
page_size = 5 # 1.1 默认每页显示5条数据
page_size_query_param = 'limit' # 1.2 页面传入的每页显示条数
page_query_param = 'page' # 1.3 页面传入的第几页
# 2.重写返回函数
def get_paginated_response(self, data):
msg = 'success'
# 2.1 无数时
if not data:
msg = '暂无数据'
data = [] # 如果无数据,也要返回一个空列表
# 2.2 返回
return ResponseList(data=data,
msg=msg,
total=self.page.paginator.count,
page=int(self.get_page_number(self.request, paginator)) or 1,
limit=int(self.get_page_size(self.request)) or 10
)
验证一下:用户列表接口时经过分页出来才返回的,可以看total值有变化,也可以传参来验证
传递limit参数,和page参数去确认
视图集的使用
功能验证
过滤、搜索与排序的简单介绍,因为utils中viewset中已经配置好了,所以在使用只需要在比如中
UserViewSet配置需要的字段就好了。
可以去看这篇 一、视图类、视图集
如下:
排序生效:
时间不对,把settings中的它们配上
登录实现
现在发现,用户表中没有创建人id,和修改人modifier。
要完成这步,首先要完成登录。采用token登录的方式。
登录接口
pip install djangorestframework-simplejwt (再pip freeze >> requirements.txt一下)
安装之后,在settings.py 中指定一下配置
# settings.py
from datetime import timedelta
SIMPLE_JWT = {
# 指定前缀,【例如】headers : {'Authorization' : JWT + '空格' + token}
"AUTH_HEADER_TYPES": ("JWT",),
# 指定token有效期, 默认是5分钟,除了minutes,还可以是days, hours, seconds
'ACCESS_TOKEN_LIFETIME': timedelta(days=7), # 访问令牌过期时间
# 刷新令牌过期时间, 一般配置成 >= 2倍的访问令牌过期时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=14),
'ROTATE_REFRESH_TOKENS': True, # 是否开启刷新令牌,若为True 则每次登录都会刷新访问令牌和刷新令牌
'BLACKLIST_AFTER_ROTATION': False, # 是否在刷新令牌后加入黑名单,若为True,,刷新后将token加到黑名单 (针对黑名单应用程序)
'UPDATE_LAST_LOGIN': True, # 是否更新用户的最后登录时间,若为True,则每次登录都更新 user表中的last_login字段
'SIGNING_KEY': SECRET_KEY, # 签名密钥,默认是【本文件】settings.py中的SECRET_KEY------可以自定义修改
# 下面几个没懂起
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # 滑动刷新令牌的过期时间
'SLIDING_TOKEN_REFRESH_LIFETIME_GRACE_PERIOD': timedelta(minutes=5), # 滑动刷新令牌宽限期
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 滑动刷新令牌的过期时间声明名称
"TOKEN_OBTAIN_SERIALIZER": "system.serializers.LoginSerializer",
}
内层urls中配置(外层也可以,为了规范写在内),获取token视图类 的路由。
from rest_framework_simplejwt.views import (
TokenObtainPairView,
)
urlpatterns = [
# .........
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # 用于获取
]
访问这个http://127.0.0.1:8000/sys/token/, 可以进行登录了,但目前响应结构不是我们想要的,
重写登录序列化器和登录视图集
应用下的serializers.py中。
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class LoginSerializer(TokenObtainPairSerializer):
class Meta:
model = User
fields = "__all__"
read_only_fields = ["id"]
default_error_messages = {
'no_active_account': ('账号或密码错误') # 账号密码不对、或 is_active为0 ,都登录不上,
# 默提示"No active account found with the given credentials"
}
def validate(self, attrs):
username = attrs['username']
password = attrs['password']
user = User.objects.filter(username=username).first()
if not user:
result = {
"code": 400,
"msg": "账号或密码不正确!",
"data": None
}
return result
if user and not user.is_staff: # 判断是否允许登录后台
result = {
"code": 400,
"msg": "您没有权限登录后台!",
"data": None
}
return result
if user and not user.is_active:
result = {
"code": 400,
"msg": "该账号已被禁用,请与管理员联系!",
"data": None
}
return result
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['username'] = self.user.username
data['userId'] = self.user.id
data['refresh'] = str(refresh) # refresh_token
data['access'] = str(refresh.access_token) # access_token
request = self.context.get('request')
request.user = self.user
result = {
"code": 200,
"msg": "登录成功!",
"data": data
}
else:
result = {
"code": 400,
"msg": "账号或密码不正确!",
"data": None
}
return result
views中
from .serializers import LoginSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
# 2.登录类
class LoginView(TokenObtainPairView):
serializer_class = LoginSerializer
内层urls,重新绑定自己的视图类
再次登录就发现已经统一了响应结构了
视图集基类中开启权限认证.
原因是drf中没有配置。
REST_FRAMEWORK = {
# ------------------------------------
'DEFAULT_AUTHENTICATION_CLASSES': [
# 先进行token认证
'rest_framework_simplejwt.authentication.JWTAuthentication',
# b.Session会话认证
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
]
# ------------------------------------
}
再登录就成功了
创建人、更新人
utils包中viewset中 的BaseModelViewSet中,加上
这样,进行新增 和 修改时,就可以传入值,存到表里
def perform_create(self, serializer):
# 在创建对象时,设置创建人为当前登录用户
serializer.save(creator=self.request.user)
def perform_update(self, serializer):
# 在更新对象时,设置修改人为当前登录用户, 想存什么字段,就写什么字段
serializer.save(modifier=self.request.user.realname)
完善注册用户接口(用户新增)
现在最明显的问题就是,新增用户后,密码没有加密。
还有一些字段没有做想要的校验,或者想要指定的提示
修改用户序列化器,
- 密码不展示
- 一些字段增加修改校验不通过的提示 ,如username
- 增加了一个sex_value的字段
from rest_framework import serializers
class UserSerializer(ModelSerializer):
"""
管理员用户-序列化器
"""
class Meta:
model = User
exclude = ['password']
extra_kwargs = {
# username字段的校验, 必须唯一提示修改
'username': {'error_messages': {'required': '账号不能为空', 'max_length': '账号最大长度不能超过50个字符'}},
}
read_only_fields = ["id", "create_datetime", "update_datetime"]
sex_value = serializers.SerializerMethodField(method_name='get_sexValue')
def get_sexValue(self,instance):
return instance.get_sex_display() # get_字段名_display
实现密码的加密, 还是在序列化器中
from django.contrib.auth.hashers import make_password
from rest_framework.validators import UniqueValidator
class UserCreateSerializer(ModelSerializer):
"""
管理员用户新增-序列化器--- 新增时, 不传password,会设置默认密码为666666
"""
username = serializers.CharField(max_length=50,
validators=[UniqueValidator(queryset=User.objects.all(), message="账号必须唯一")])
password = serializers.CharField(required=False, default="666666")
is_staff = serializers.BooleanField(required=False, default=True) # 是否允许登录后台
def create(self, validated_data):
if "password" in validated_data.keys():
if validated_data['password']:
validated_data['password'] = make_password(validated_data['password'])
return super().create(validated_data)
class Meta:
model = User
fields = "__all__"
read_only_fields = ["id", "create_datetime", "update_datetime"]
class UserUpdateSerializer(ModelSerializer):
"""
用户修改-序列化器,修改时,password不传,密码就不会变
"""
username = serializers.CharField(max_length=50,
validators=[UniqueValidator(queryset=User.objects.all(), message="账号必须唯一")])
password = serializers.CharField(required=False, allow_blank=True)
def update(self, instance, validated_data):
if "password" in validated_data.keys():
if validated_data['password']:
validated_data['password'] = make_password(validated_data['password'])
return super().update(instance, validated_data)
class Meta:
model = User
fields = "__all__"
read_only_fields = ["id", "create_datetime", "update_datetime"]
序列化器中,再分别指定下 新增和更新序列化器
在线接口文档
settings中
pip install coreapi
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', (这个在drf配置中)
INSTALLED_APPS = [
..........
'rest_framework', # 当使用from rest_framework的documentation (接口文档的模板时,就需要这里引用)
]
外层urls.py中
# coreapi文档需要进行的导入信息
from rest_framework.documentation import include_docs_urls
urlpatterns = [
.......
# coreapi文档路径
path('docs/', include_docs_urls(title="接口测试平台API文档",
description="这个是接口平台的文档"
))
]
就可以访问了http://127.0.0.1:8000/docs/
最后附上:requirements.txt
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
mysqlclient==2.2.4
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
djangorestframework==3.15.1
mysqlclient==2.2.4
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
django-filter==24.2
djangorestframework==3.15.1
mysqlclient==2.2.4
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
django-filter==24.2
djangorestframework==3.15.1
djangorestframework-simplejwt==5.3.1
mysqlclient==2.2.4
PyJWT==2.8.0
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
Django==4.2.11
django-cors-headers==4.3.1
django-filter==24.2
djangorestframework==3.15.1
djangorestframework-simplejwt==5.3.1
mysqlclient==2.2.4
PyJWT==2.8.0
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
asgiref==3.8.1
backports.zoneinfo==0.2.1
certifi==2024.2.2
charset-normalizer==3.3.2
coreapi==2.3.3
coreschema==0.0.4
Django==4.2.11
django-cors-headers==4.3.1
django-filter==24.2
djangorestframework==3.15.1
djangorestframework-simplejwt==5.3.1
idna==3.6
itypes==1.2.0
Jinja2==3.1.3
MarkupSafe==2.1.5
mysqlclient==2.2.4
PyJWT==2.8.0
requests==2.31.0
sqlparse==0.4.4
typing-extensions==4.10.0
tzdata==2024.1
uritemplate==4.1.1
urllib3==2.2.1