企业级django项目搭建

项目搭建

环境配置

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拖进来后配置:,拖的时候把他去掉
在这里插入图片描述

  1. 修改setting.py , 添加
# apps 绝对路径( sys.path是个列表) 
 
sys.path.insert(0,os.path.join(BASE_DIR,"apps"))
  1. 做如下操作: 之后,应用就可以被识别了,可以发现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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值