drf-Djangorestframework小白也能懂啦

DRF是django框架的一个插件或者说是django的一个工具包,用于在Web后台构建Restful接口,drf提供了更多的功能(如三大验证,Restful接口等),它的CBV模型比django自带的CBV模型更加好用,对大项目来说功能更完善,开发效率更高。

一、安装

新建文件夹drf 配置解析器 在当前目录下执行

pip install django
pip install djangorestframework

二、搭建项目(纯净版)

scss 复制代码
# 创建一个 Django程序 和文件夹同名 注意小数点
django-admin startproject drf .(目录名字)
#创建目录 (模块)
python manage.py startapp api(模块名)

1.注释不需要的配置

setting.py

目的:纯净版 注释不需要的内容 注册drf 新增drf配置

ini 复制代码
INSTALLED_APPS = [
    #"django.contrib.admin",
    #"django.contrib.auth",
    #"django.contrib.contenttypes",
    #"django.contrib.sessions",
    #"django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework", #注册rest应用
    "api.apps.ApiConfig" #引入自己的注册的模块名
]
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    # "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    #"django.contrib.auth.middleware.AuthenticationMiddleware",
    #"django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                #"django.contrib.auth.context_processors.auth",
               # "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

#修改时间
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
# USE_TZ = True
USE_TZ = False




#语言修改
LANGUAGE_CODE = 'zh-Hans'#en-us为英文

#新增以下drf配置
REST_FRAMEWORK={
    "UNAUTHENTICATED_USER":None,#配置匿名用户
    "APPEND_SLASH": False,  # 去掉url尾部斜杠
}
#数据库配置 先注释 后续配置mysql
DATABASES = {
    "default": {
        # "ENGINE": "django.db.backends.sqlite3",
        # "NAME": BASE_DIR / "db.sqlite3",
    }
}

django原生可直接读取该文件的配置 但是再drf里封装了一层 需要读取setting配置需要REST_FRAMEWORK这个对象里写入配置

UNAUTHENTICATED_USER 相当于默认匿名用户配置 如果不设置该字段 接口会被鉴权无法访问

2.配置redis

pip install django-redis

setting.py文件中 配置redis的缓存

makefile 复制代码
CACHES = {
    "default":{
        "BACKEND":"django_redis.cache.RedisCache",
        "LOCATION":"redis://127.0.0.1:6379",
        "OPTIONS": {
			"CLIENT_CLASS":"django_redis.client.Defaultclient",
            "PASSWORD": "123"
        }
    }
 }

-BACKEND: 固定

-LOCATION redis的地址

-CLIENT_CLASS 固定

-PASSWORD redis的密码

3.编写路由和视图

两种形式,一种函数形式FBV 一种类形式CBV

1.函数形式

必须加装饰器@api_view 参数为list 里面为支持的方法["get","post"]

ini 复制代码
#方法一 配置路由
urlpatterns=[
    path('auth/',views.auth),
]
python 复制代码
#方法一 函数形式
from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view(["GET"])
def auth(request):
    return  Response('status': True,' message': "success"})

2.类形式

类必须继承rest_framework的APIView 类方法可提供get post等方法

urls里 导出为类的as_view()方法 此方法在继承的父类身上 此方法返回一个函数

ini 复制代码
#方法二 类形式
urlpatterns=[
    path('auth/',UserInFo.as_view()),
    #path('auth/<str:params>/',UserInFo.as_view()) 参数
]
python 复制代码
#方法二 类形式
from rest_framework.views import APIView
from rest_framework.response import Response
class UserInFo(APIView):
    def get(self,request, *args,**kwargs):
        #可以直接用urls里定义的形参接受参数
        #或者 (self,request,**args,**kwargs)
        #或者 self.args   self.kwargs
        return Response({'status': True, 'message': "success"})
        
    #def post(self,request):
        #........

4.配置端口

根目录:manage.py

python 复制代码
#引入
from django.core.management.commands.runserver import Command as Runserver

def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "admin.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    Runserver.default_addr = '0.0.0.0'  # 修改默认地址
    Runserver.default_port = '8880'  # 修改默认端口
    main()

5.运行

1.方法一: 命令行

python manage.py runserver

2.方法二:配置pycharm

点击编辑配置

配置项目manage.py文件位置

形参为 runserver

应用后点击播放键即可启动

三、MySql

1.连接配置

建库

先使用命令或者可视化工具 先建库

npm包选择

有两种库可以选择 pymysql和mysqlclient 选择其中一种方案即可

1、mysqlclient

pip install mysqlclient

修改配置settings.py

makefile 复制代码
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "xxx",#数据库名字
        "USER": "root", #账号
        "PASSWORD": "root123", #密码
        "HOST":"127.0.0.1",#数据库地址
        "PORT":3306,  #端口一般默认
    }
}

NAME:数据库名称

USER:数据库用户名

PASSWORD:数据库密码

HOST:数据库主机地址

PORT:数据库端口号

2、pymysql

2.1安装
pip install pymysql
2.2修改配置settings.py
arduino 复制代码
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'xxx',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3306',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
            'charset': 'utf8mb4',
        },
        'MYSQL': {
            'driver': 'pymysql',
            'charset': 'utf8mb4',
        },
    }
}

字段解释同mysqlclient

2.3修改配置settings.py 同级文件 init.py
arduino 复制代码
import pymysql
pymysql.install_as_MySQLdb()

2.数据库初始化

在api模块下 models.py里

ini 复制代码
#引入django models模块
from django.db import models

#假定建一个用户类  必须继承models 会自动创建表名为 :模块名字+类名字
class UserInfo(models.Model):
    id = models.BigAutoFileId(primary_key=True) #自增id  这行可以不写 django会默认添加
    name = models.CharField(max_length=32) #字符串
	age = models.IntegerField()#数字 

	#参数详解
    #age = =models.IntegerField(verbose_name="年龄") verbose_name表示写入备注
    #age = =models.IntegerField(default=2) default代表插入默认数据
    #age = =models.IntegerField(null=True,blank=True) 代表可以为空

	#连表  假如和部门关联
	#depart = models.ForeignKey(to="关联表的类名",to_field="关联表的key",on_delete=models.CASCADE)
	#on_delete: 表示级联删除 如果关联表的值被删除 怎么处理当前值
	#	models.CASCADE  当前值也会被删除
	#	models.SET_NULL  当前值也会置空
	#注意 虽然这里的键名为sex  django里面会自动改为 建名+_id   :sex_id

    #约束  参数名choices  此值只能选择1或者2
	gender_choices=((1,"男"),(2,"女"))
	gender=models.SmallIntegerField(choices=gender_choices)
	

当写入这个类 django 会自动转换为sql语句

等同于:

sql 复制代码
create table app_userinfo(
 	id bigint auto_increment primary key,
  name varchahar(32),
  age int
)
#表名为 :模块名字+类名字

在跟目录执行以下命令 既可以创建表 (需要注册 INSTALLED_APPS app 原因:默认在去找注册的app里下面的models是否有内容)

python manage.py makemigrations
python manage.py migrate

参数model属性

AutoField 一个自动递增的整型字段,添加记录时会自动增长,AutoField 字段通常会用于充当数据表的主键,若模型没有指定主键字段,则Django 自动添加一个 AutoField字段
IntegerField 整数
SmallIntegerField 具有较小的输入范围,具体范围依赖于所使用的数据库
BigIntegerField 64位整型字段
BinaryField 二进制数据字段,只能通过 types 对其赋值
FloatField 浮点型字段,定义本字段时必须传入 max_digits 和decimal_places 参数,用于定义总位数(不包括小数点和符号)和小数位数
DecimalField 十进制浮点数,max_digits参数表示总位,decimal_places 参数表示小数位数
CommaSeparatedIntegerField 用于存放逗号分隔的整数值,相较与普通的 CharField, 它有特殊的表单数据验证要求
DurationField 存储时间周期,用 Python 的 timedelta 类型构建
EmailField 带检查 Email 合法性的CharField
FileField 单个文件上传字段,在定义本字段时传入参数 upload_to,用于保存上载文件的服务器文件系统路径,这个路径必须 包含 striftime formatting, 该格式将上载 文件的 date/time 替换
FilePathField 按目录限制规则选择文件,定义本字段必须传入参数 path ,用于限制目录
ImageField 类似于 FileField,同时验证上传对象是否是一个合法的图片,有两个可选参数 height_field 和 width_field,如果提供这两个参数,则图片将按照提供的高度和宽度规格保存,该字段要求安装 Python Imaging 库
IPAddressField 一个字符串形式的IP地址,例如: 192.23.250.2
NullBooleanField 类似于 BooleanField, 但比其多一个None选项
PhoneNumberField 带有美国风格的电话号码校验的 CharField,格式为 XXX-XXX-XXXX
SlugField 只包含字母、数字、下划线和连字符的输入字段,通常用于URL
URL 用于保存URL
USStateField 美国州名的缩写字段,由两个字母组成
XMLField XML字段是具有XML合法验证的TextField
BooleanField 布尔字段
CharField 字符串字段,用于较短的字符串
TextField 大容量文本字段
DateField 日期字段auto_now 当对象被保存时,将该字段的值设置为当前时间 auto_now_add 当对象被首次创建时,将该字段的值设置为当前时间
DateTimeField 类似于 DateField,但同时支持于时间的输入
TimeField 时间字段,类似于 DateTimeField,但只能表达和输入时间

模型类属性参数

max_length 定义字符的长度,例如 : headline = models.CharField(max_length=255)
primary_key 设置一个模型的主键字段,例如:primary_key=true
null 定义是否允许相对应的数据库字段为Null,默认设置为 False
blank 定义字段是否可以为空
choices 定义字段的可选值,本字段的值应该是一个包含二维元素的元组,元组的每个元素的第一个值是实际存储的值,,第二个值是HTML页面进行选择时显示的值
default 设置默认值,例如: default="1"
help_text HTML页面中输入控件的帮助字符串
unique 是否为字段定义数据库的唯一约束
verbose_name 人性化名称 例如:name = models.CharField(verbose_name="姓名",max_length=12,)
db_index 若值为True,则在表中会为此字段创建索引 默认False
db_column 字段的名称 如果未指定,则使用属性的名称

关于auto_now和auto_now_add时间格式化问题

重写字段映射方法

python 复制代码
class DateTimeFieldFormat(models.DateTimeField):
    """
    数据库datetime类型字段格式化(%Y-%m-%d %H:%M:%S)
    precision:需要保留的小数位数
    """

    def __init__(self, verbose_name=None, name=None, precision=0, **kwargs):
        self.precision = precision
        super().__init__(verbose_name, name, **kwargs)

    def db_type(self, connection):
        return 'datetime(%d)' % self.precision



class UserInfoModel(models.Model):
    username = models.CharField(verbose_name="用户名", max_length=16, unique=True, default='admin')
    password = models.CharField(verbose_name="密码", max_length=16, default='123')
    create_time = DateTimeFieldFormat(verbose_name="创建时间", auto_now_add=True)
    update_time = DateTimeFieldFormat(verbose_name="修改时间", auto_now=True)

3.数据增删改查

新增

scss 复制代码
UserInfo.objects.create(name="张三", age=12, gender=1)
#等同于  insert into app_userinfo(name)values("张三")...

查询

查询全部:类名.objects.all() 结果为一个列表

ini 复制代码
list=UserInfo.objects.all()
for item in list:
    print(item.name)

条件查询 类名.objects.filter(条件)

ini 复制代码
list=UserInfo.objects.filter(id=1)
#结果也是列表 

obj=UserInfo.objects.filter(id=1).first()
#获取第一个单独数据 得到一个对象

修改

修改某条 : 类名.objects.filter(条件).update(age=12) 修个某条为12

修改全部:类名.objects.all().update(age=12) 修改全部age为12

ini 复制代码
UserInfo.objects.filter(id=2).update(name=12)
#返回一个int 1或者0  表示成功或者失败

删除

删除某条 : 类名.objects.filter(条件).delete()

删除全部:类名.objects.all().delete()

ini 复制代码
UserInfo.objects.filter(name="张三").delete()
#返回一个元组  (0,{})或者(1,{'api.UserInfo': 1})  表示成功或者失败

四、request对象

request是被drf封装了一层新的request对象,django原生的request 封装在新对象的_request属性里,可以使用request._request访问原生request 但django为了方便 封装时通过__getattribute__和__getattr__做了处理 直接使用request.method也能访问到request._request.method

1.解析器

不用的话 默认支持JSONParser,FormParser,MultiPartParser三种解析器

需要改默认 需要改全局配置 setting里新增

例:DEFAULT_PARSER_CLASSES":["rest_framework.parsers.JSONParser"]

单个配置解析器JSONParser,FormParser

python 复制代码
from rest_framework.parsers import JSONParser, FormParser
from rest_framework.negotiation import DefaultContentNegotiation

class UserInFo(APIView):
    def get(self,request, *args,**kwargs):
        # 所有解析器
        parser_classes = [JSONParser,FormParser] #JSON格式和formdata
        # 根据请求,匹配对应的解析器  寻找
        content_negotiation_class = DefaultContentNegotiation

        #获取参数方法
    	request.query_params.get("id") #获取get url参数 id

        #post请求
		#request.data
       

文件解析器

csharp 复制代码
from rest_framework.parsers import MultiPartParser
class UserInFo(APIView):
    def post(self,request,params):
        parser_classes = [ MultiPartParser] #文件解析器包括 文件和其他表单
        #获取文件对象
    	file=request.data.get("img")
    	

2.获取参数

1.读取请求头的content-type类型读取

2.根据不同类型获取解析器

csharp 复制代码
#http://127.0.0.1:8000/getUserInfo/?id=1&&name=xxxx
request.query_params.get("id") #获取get url参数 id

request.data #获取请求体 post 参数

五、认证组件

应用场景:判断是否带有token

新建

ruby 复制代码
from rest_framework.authentication import BaseAuthentication
#from rest_framework.exceptions import AuthenticationFailed 抛出认证失败异常

class AuthView(BaseAuthentication):
    def authenticate(self,request): #认证方法 authenticate[固定方法名字]
        #例:
        #无token返回 raise AuthenticationFailed({"code":999:"error":"认证失败"}) 
        #有返回XXX元组
	def authenticate_header(self,request):# authenticate方法异常后控制WWW-Authenticate返回值
        return "token"  #返回一个字符串即可 

注意:此认证类组件 不能放在视图views中 可单独使用一个文件

类名随意 只要继承BaseAuthentication类就一定有authenticate方法

authenticate 方法说明 主要用户认证条件

返回值(三种):

1.成功:返回元组(user,token) 一般这种格式相当(request.user,request.auth)会自动赋值 后续直接访问

2.失败:引入drf里exceptions类 抛出异常 raise AuthenticationFailed({"code": 999,"error":"认证失败"})

3.未知:返回None 可用于匿名用户

authenticate_header 方法说明:

当authenticate方法异常后 控制响应头里WWW-Authenticate返回值 返回值为此方法return的值

该方法如果没有返回值 可能响应状态码受影响例如本该401 结果返回403

使用

方法一:单个接口使用 authentication_classes字段 注意单个接口必须继承drf的APIView类

ruby 复制代码
#方法一
class Login(APIView):
    authentication_classes=[] #如果未空数组则不需要验证  如果为[AuthView] 则使用配置的方法认证可配置多个方法用,隔开
    def get(self,request, *args,**kwargs):
        #....

方法二:全局配置setting.py 新增字段DEFAULT_AUTHENTICATION_CLASSES

ini 复制代码
REST_FRAMEWORK={
    "UNAUTHENTICATED_USER":Nnoe,#配置匿名用户
    "DEFAULT_AUTHENTICATION_CLASSES":[认证组件地址(例api.auth.AuthView)]
}

如果同时配置:先去全局去读,再去局部读取 局部优先级更高

使用多个认证类

authentication_classes配置多个验证(全局同理) [认证类1,认证类2,认证类3]

假如 list值全为[None,None,None] 程序会继续执行,相当于允许匿名用户 self.user=None self.auth=None

假如 有一个抛出异常则会 后续不执行

假如 有一个成功返回元组 则会后续不执行

只有遇到None的情况才会往后执行 相当于逻辑或的关系

六、权限组件

应用场景:角色权限、多条件同时成立

新建

python 复制代码
from rest_framework.permissions import BasePermission


class PermissionView(BasePermission):
    #定义错误信息
    message={"status":False,"msg":"无权访问"}
    def has_permission(self,request): #认证方法 has_permission[固定方法名字]
        #self.message = xxx  可以修改错误信息
       #返回True或者False 
        
   

权限类必须继承permissions里的BasePermission类,同时必须定义has_permission方法

has_permission方法返回值:True和false

自定义错误信息 self.message

使用

方法一:单个接口使用 permission_classes字段 注意单个接口必须继承drf的APIView类

ruby 复制代码
#方法一
class GetOrderView(APIView):
    permission_classes=[PermissionView] #list 值为上一步定义的权限类名
    def get(self,request, *args,**kwargs):
        #....

方法二:全局配置setting.py 新增字段DEFAULT_PERMISSION_CLASSES

ruby 复制代码
REST_FRAMEWORK={
    "UNAUTHENTICATED_USER":Nnoe,#配置匿名用户
    "DEFAULT_PERMISSION_CLASSES":['权限组件地址(例api.auth.PermissionView)'] #值为list可配置多个
}

使用多个权限类

permission_classes配置多个验证(全局同理) [权限类1,权限类2,权限类3]

在默认情况下:

1.必须满足每个权限类 返回True 才算通过 是并且的关系

2.一旦遇到Flase 立即返回 后续补执行

改变并权限类且关系

改变默认情况下 权限认证规则 不需要满足且的关系 例如改变成满足一个就可以

定义类方法check_permissions

php 复制代码
from rest_framework.permissions import BasePermission


class PermissionView(BasePermission):
   # message={"status":False,"msg":"无权访问"}
   # def has_permission(self,request): 
       #返回True或者Flase 
    
    def check_permissions(self,request):
       # 用self.get_permissions()获取所有权限类
        for per in self.get_permissions():
            per.has_permission(self,request) #获取权限验证结果
    	#-------根据结果处理------
    	#如果不做处理 直接return 表示不需要拦截
        #拦截: 调用 self.permission_denied(request,"错误信息")
        

check_permissions方法 不做任何处理 相当于通过

调用self.permission_denied(request,"错误信息") 表示拦截

七、限流组件

应用场景:限制频繁访问 例ip限制

新建

python 复制代码
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache# 引入reids缓存模块

#继承SimpleRateThrottle 类
class BaseThrottle(SimpleRateThrottle):
    scope = "ip" # 定义此限流类的名字
	THROTTLE_RATES = {"ip": "5/m"} #访问频次
	cache = default_cache #redis缓存
	def get_cache_key(self, request, view):#必须继承的方法
		ident = self.get_ident(request) # 获取请求用户IP 作为唯一标识 可自定义唯一标识
		return self.cache_format % {'scope': self.scope, 'ident': ident}

scope:当前限流类的名字 ,作用为 可根据此名字 找到对应THROTTLE_RATES的限流频率

ident: 对于限制唯一标识

THROTTLE_RATES:可在当前类里定义 格式为 {限流类名:次数/时间 } 也可以全局配置在setting.py

makefile 复制代码
REST_FRAMEWORK={
    "DEFAULT_THROTTLE_RATES": {
        "XXX":"2/m",  #根据限流类 scope定义的名字 为key 值为限流频率
        "XX":"3/m"
    }
}

使用

ruby 复制代码
class Login(APIView):
    throttle_classes=[BaseThrottle]  #引入上文编写的限流类放入list
    def get(self,request, *args,**kwargs):
        #....

全局同认证权限同理

八、版本

设置 settings

css 复制代码
	REST_FRAMEWORK = {
	"VERSION_PARAM': "V",
	"DEFAULT_VERSION":"V1",#默认版本
	"ALLOWED_VERSIONS": ["v1","v2","v3"], #设置有几个版本
	"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning"#此项配置为全局配置
     
}

#DEFAULT_VERSIONING_CLASS 为全局配置 可选参数有URLPathVersioning AcceptHeaderVersioning QueryParameterVersioning 配置了之后不必再每个视图类里写入versioning_class=xxxx

1.版本在url中

urls.py

python 复制代码
from django.urls import path,re_path

urlpatterns=[
    #方法一 <str:version> 代表版本
    path('api/<str:version>/auth/',UserInFo.as_view())
    
    #方法二正则
    re_path(r'^api/(?P<version>\w+)/auth/$),UserInFo.as_view())
]

视图组件

python 复制代码
from rest_framework.versioning import URLPathVersioning #获取url版本
#用户相关类
class UserInFo(APIView):
    versioning_class=URLPathVersioning #
    def get(self,request,*args,**kwargs):
        print(request.version)#获取版本

2.版本在请求头中

例如header里

Accept:application/json; version=1.0

urls.py

css 复制代码
from django.urls import path
urlpatterns=[    path('api/auth/',UserInFo.as_view())]

视图组件

python 复制代码
from rest_framework.versioning import AcceptHeaderVersioning #获取请求头版本
#用户相关类
class UserInFo(APIView):
    versioning_class=AcceptHeaderVersioning 
    def get(self,request,*args,**kwargs):
        print(request.version)#获取版本

九、序列化

从数据库获取的QuerySet或数据对象 转化为JSON

假设models有以下数据

ini 复制代码
#假如models.py有以下内容
from django.db import models
class UserModel(models.Model):
	name=models.CharField(verbose_name="姓名")
	age=models.IntegerField(verbose_name="年龄")
	gender=models.SmallIntegerField(verbose_name="性别",choices=((1,"男"),(2,"女")))

1.serializers

新建序列化器

ini 复制代码
from rest_framework import serializers
class UserSerializer(serializers.Serializer): #继承Serializer 也可以继承自定义的序列化器 
    name = serializers.CharField()
    age = serializers.IntegerField()
    #定义需要获取的键名    此例相当于获取name和age

使用 单个对象序列化

css 复制代码
class UserView(APIView):
    def get(self,request,*args,**kwargs):
        userInfo=models.UserModel.objects.all().first() #假如获取数据库一条数据
    	ser=UserSerializer(instance=userInfo)#参数为数据库数据 
        print(ser.data)
    	#此时相当与ser.data 为{name:'XXX',age:11} 没有gender字段 

多个对象序列化

css 复制代码
class UserView(APIView):
    def get(self,request,*args,**kwargs):
        userInfo=models.UserModel.objects.all() #假如获取数据库多条数据

    	ser=UserSerializer(instance=userInfo,many=True)#参数many为True
        print(ser.data)
    	#[{name:'XXX',age:12},{name:'YYYY',age:11}]

2.ModelSerializer

假设models有以下数据

ini 复制代码
#假如models.py有以下内容
from django.db import models
#用户表
class UserModel(models.Model):
    name=models.CharField(verbose_name="姓名")
    age=models.IntegerField(verbose_name="年龄")

    gender=models.SmallIntegerField(verbose_name="性别",choices=((1,"男"),(2,"女")))
	#假设关联部门
	depart = models.ForeignKey(verbose_name="部门",to="Depart",on_delete=models.CASCADE) 
	ctime = models.DateTimeField(verbose_name="时间",auto_now_add=True)
	#一对多 关系
	tags = models.ManyToManyField(verbose_name="标签",to="Tag")

#部门表
class DepartModel(models.Model):
	title = models.CharField(verbose_name="部门",max_length=32)
    sort = models.IntegerField(verbose_name="顺序")
	count = models.IntegerField(verbose_name="人数")

#标签表
class TagModel(models.Model):
	caption = modes.CharField(verbose_name="标签",max_length=32)

ModelSerializer 将会序列化 全部models里定义的字段

kotlin 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
	class Meta:
		model = models.UserModel
		fields = "__all__" #取出全部数据

fields = "all" #取出全部数据

fields =["name","age"] 也可以指定字段 也可以指定没有的字段 但是要赋值

使用

同上文serializers

source方法

1.choices数据

kotlin 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
    gender_text = serializers.CharField(source="get_gender_display")
    #新增返回字段gender_text  值为自己的自定义的 相当于执行get_gender_display 获取choices 1为男
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","gender_text"]
        #此时取值为 [{"name":"XX","age":11,"gender":1,"gender_text":"男"}]

2.联表数据

kotlin 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
    #depart = serializers.CharField(source="depart") #这样会拿到关联depart所有数据
    depart = serializers.CharField(source="depart.title") #这样会拿到关联depart 部门名字
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","depart"]
        #此时取值为 {"name":"XX","age":11,"gender":1,"depart":"运营部"}

format方法

kotlin 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
    ctime = serializers.CharField(format="%Y-%m-%d") #格式化时间字段
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","ctime"]
        #此时取值为 {"name":"XX","age":11,"gender":1,"ctime":"2022-11-11"}

自定义方法

python 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
    xxx=serializers.SerializerMethodField() #设置自定义方法 xxx为自定义的返回键名
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","ctime","xxx"]
    
	def get_xxx(self,obj): 
        return "姓名:{},年龄{}".format(obj.name,obj.age)

SerializerMethodField会自动触发 名为 get_xxx的方法 xxx为自定义的返回值健名 值为此方法的返回值

参数 obj为当前对象

嵌套方法

主要针对于 ManyToManyField和ForeignKey

一对多(傻瓜式写法)

python 复制代码
from rest_framework import serializers
from django.db import models

class UserSerializer(serializers.ModelSerializer):
    tag=serializers.SerializerMethodField() #设置自定义方法 
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","ctime","tag"]
    
	def get_tag(self,obj): 
    	query=obj.tags.all() #获取tag关联表所有的值
        res=[{'id':tag.id,'caption':tag.caption} for tag in query] #循环取值 相当于tag字段值为数组
        
        return res

正式写法

借用序列化器

ini 复制代码
from rest_framework import serializers
from django.db import models

#部门序列化器
class DepartSerializer(serializers.ModelSerializer):
    class Meta:
        model=models.DepartModel
		fields = "__all__"
#标签序列号器
class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model=models.TagModel
		fields = "__all__"
        
class UserSerializer(serializers.ModelSerializer):
    depart=DepartSerializer() #可以直接等于这个类的示例
    tag=TagSerializer(many=True) #多个加参数many
    class Meta:
		model = models.UserModel
		fields = ["name","age","gender","depart","tag"]

3.数据校验

也是基于序列化器 做入参校验

1.Serializer

1.定义

定义序列化器 ,主要为参数required=True

ini 复制代码
from rest_framework import serializers
class UserSerializer(serializers.Serializer):  
    name = serializers.CharField(required=True) 
    age = serializers.IntegerField(required=True)
   
参数:其他校验
python 复制代码
from rest_framework import serializers
from django.core.validators import RegexValidator

class UserSerializer(serializers.Serializer):  
    name = serializers.CharField(required=True,max_length=20,min_length=10) 
	age = serializers.CharField(validators=[RegexValidator(r"\d+",message="年龄为数字")]) 

	# name = serializers.CharField(read_only=True,write_only=True) 

max_length 字符串最大长度

min_length 最小长度

validators:[] 值为数组,自定义校验方法

-RegexValidator(正则,信息) 正则校验

-message失败后的原因

钩子函数

定义validate_+ 字段名字方法 返回bool 用exceptions.ValidationError抛出异常

python 复制代码
from rest_framework import serializers
from django.core.validators import RegexValidator
from rest_framework import exceptions

class UserSerializer(serializers.Serializer):  
	age = serializers.CharField(validators=[RegexValidator(r"\d+",message="年龄为数字")])
	class Meta:
        model=models.UserModel
		fields = "__all__"
    #定义方法
	def validate_age(self,value):
        if len(value)>2:
        	raise exceptions.ValidationError("年龄不能超过两位数")
        #self.initial_data  获取当前所有参数
        return value
   

validate_执行顺序 根据代码先后 根据fields里数据顺序先后

全局钩子(额外校验 ,非单独 很少用)

python 复制代码
from rest_framework import serializers
from django.core.validators import RegexValidator
from rest_framework import exceptions

class UserSerializer(serializers.Serializer):  
	age = serializers.CharField(validators=[RegexValidator(r"\d+",message="年龄为数字")])
	#此方法为全局钩子函数 整体
    def validate():
        raise exceptions.ValidationError("全局钩子校验失败")

setting.py drf配置中 可新增字段"NON_FIELD_ERRORS_KEY":xxx 统一全局钩子 返回错误字段的键

2.使用

方法一:判断 is_valid方法 返回值校验

python 复制代码
class UserView(APIView):
    def post(self,request,*args,**kwargs):
    	ser=UserSerializer(data=request.data)#使用data 传入接收参数 
        if ser.is_valid():  #is_valid方法返回不
            print(ser.validated_data)#validated_data方法获取 校验后的新json数据
        else:
            print(ser.errors) #校验不通过的原因

返回错误信息的是英文如何调整:

在setting.py中 新增字段 LANGUAGE_CODE="zh-hans" 处于REST_FRAMEWORK外面

英文的话值为 en-us

方法二:使用raise_exceptio参数

ini 复制代码
class UserView(APIView):
    def post(self,request,*args,**kwargs):
    	ser=UserSerializer(data=request.data)
        ser.is_valid(raise_exception=True):  #raise_exception参数为True
        

该参数为True后 如果校验成功 程序继续执行 失败后 直接返回异常

缺点:返回值结构固定

2.ModelSerializer

1.定义

基于字段extra_kwargs对象 添加 对象键名为 字段名 值为校验条件

kotlin 复制代码
class UserSerializer(serializers.ModelSerializer):
    # 这里也可以定义 自定义字段校验 例:gender_text=xxxx  fields就数组新增该字段
    class Meta:
		model = models.UserModel
        fields = ["name","age"]
    	extra_kwargs = {
            "name": {"max_length": 5,"min_length": 1},
            "age":{....}
        }

2.使用

和Serializer差不多 但是可以直接使用 save把数据存入数据库

scss 复制代码
class UserView(APIView):
    def post(self,request,*args,**kwargs):
    	ser=UserSerializer(data=request.data)
        if ser.is_valid():  
            print(ser.validated_data)
            ser.save()#使用此方法 可以将数据直接存入数据库
        else:
            print(ser.errors)

使用此方法注意:但是用户传入的字段必须和数据库字段一一对应

1.如果校验必填字段少了这里会报错

解决:ser.save(age=11)此方法可以传入参数给默认值

2.如果用户多传字段

解决:ser.validated_data.pop("more") 可以让存储数据库 删除该字段

3.复杂表结构校验 例联表

如果是ForeignKey 和ManyToManyField会自动校验 关系表和关联表 里有无该主键

如要做特殊校验 需要在钩子函数中处理 钩子函数中 会返回该对象 关联后的值 可直接操作

4.同时校验和序列化

主要用户两个参数

read_only 仅仅序列化时读取 write_only仅仅校验写入数据库

使用时候 案例:

kotlin 复制代码
class UserView(APIView):
    def post(self,request,*args,**kwargs):
    	ser=UserSerializer(data=request.data) #校验
        if ser.is_valid():  
            ser.save()
            #如果同时用一个 那么不用再次初始化UserSerializer  直接save之后使用data就是被同一个序列化器处理后的data
            return Response(ser.data) #返回序列化结果
        else:
            print(ser.errors)

序列化器和验证器

ini 复制代码
class UserSerializer(serializers.ModelSerializer):
    gender_text = serializers.CharField(source="get_gender_display",read_only=True)#设定仅读
    class Meta:
		model = models.UserModel
        fields = ["name","age","gender","gender_text"]
    	extra_kwargs = {
            "id": {"read_only": True},#设定仅读
            "gender": {"write_only":True}#设定仅写
        }

Serializer同理, 相当于用户传入 gender 1 那么给他返回对应的字典{gender_text:男} 不会有字段{gender:1}和id

在校验时候 id 和gender_text 为必填

自定义钩子

原理

应用场景:假如需要校验gender参数必须传1或者2,返回时却要返回关联的关系(男或女)

python 复制代码
from collections import OrderedDict
from rest_framework.fields import SkipField
from rest_framework.relations import PKOnLyObject

class UserSerializer(serializers.ModelSerializer):
    class Meta:
		model = models.UserModel
        fields = ["name","age","gender"]
    	extra_kwargs = {
            "id": {"read_only": True}#设定仅读
        }
	#自定义钩子
	def to_representation(self, instance):
        ret = OrderedDict()
		fields = self._readable_fields

		for field in fields:
            #这里相当于 函数名字按照xx 匹配 使用时候就是 xx_键名
            if hasattr(self,'xx_%s' % field.field_name):
                value = getattr(self,'xx_%s' % field.field_name)(instance)
                ret[field.field_name] = value
            else:
                try:
					attribute = field.get_attribute(instance)
                except SkipField:
                    continue
                check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
                if check_for_none is None:
                    ret[field.field_name] = None
                else:
                    ret[field.field_name] = field.to_representation(attribute)
    return ret

	#使用
	def xx_gender(self,obj):
        return obj.get_gender_display() #结果为男
        

xx_%s 正则匹配 可以为自定义方法取个名字

在使用的时候 用xx_参数名 就i可以

封装

定义一个工具函数 hook.py

python 复制代码
from collections import OrderedDict
from rest_framework.fields import SkipField
from rest_framework.relations import PKOnlyObject
from rest_framework import serializers

class HookSerializer(serializers.ModelSerializer):
    def to_representation(self, instance):
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            #这里相当于 函数名字按照xx 匹配 使用时候就是 xx_键名
            if hasattr(self,'xx_%s' % field.field_name):
                value = getattr(self,'xx_%s' % field.field_name)(instance)
                ret[field.field_name] = value
            else:
                try:
                    attribute = field.get_attribute(instance)
                except SkipField:
                    continue
                check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
                if check_for_none is None:
                    ret[field.field_name] = None
                else:
                    ret[field.field_name] = field.to_representation(attribute)
        return ret

使用时候继承HookSerializer类就可以

ruby 复制代码
from api.utlis.hook import HookSerializer#引入HookSerializer 
class UserSerializer(HookSerializer,serializers.ModelSerializer):
    class Meta:
        #....
    def xx_():
    

十、封装response

为了方便返回格式统一封装response 新建utils文件夹 新建response.py文件

python 复制代码
from rest_framework.response import Response


class APIResponse(Response):
    """
    二次封装Response
    """
    def __init__(self, code=0, data_msg='ok', data=None, http_status=None, headers=None, exception=False,
                 **kwargs):
        results = {
            'code': code,
            'msg': data_msg
        }

        # 如果返回结果就将返回结果赋给到data中
        if data is not None:
            results["data"] = data

        # 如果传递其他的参数,将会被放到kwargs中被接收
        if kwargs is not None:
            for k, v in kwargs.items():
                # 采用反射的方法,去赋值
                setattr(results, k, v)  # results[k] = v
        super().__init__(data=results, status=http_status, headers=headers, exception=exception)
bash 复制代码
#使用
return APIResponse(200,'操作成功') #需要返回data 加上data=xxx

#返回样式
{
    "code": 200,
    "msg": "操作成功"
}

十一、日志封装

utils文件夹 新建logger.py文件,采用loguru 得先安装

pip install loguru 
python 复制代码
"""
# @software: PyCharm
# @file:middle_log.py
# @project: 封装日志
"""
import os
import time
from loguru import logger
from pathlib import Path
import sys

t = time.strftime("%Y_%m_%d")

BASE_DIR = Path(__file__).resolve(strict=True).parent.parent


class Logger:
    __instance = None
    logger.remove()
    logger.add(sys.stdout,
                    format="<green>{time:YYYYMMDD HH:mm:ss}</green> | "  # 颜色>时间
                           "{process.name} | " 
                           "{thread.name} | "  
                           "<cyan>{module}</cyan>.<cyan>{function}</cyan>"  # 模块名.方法名
                           ":<cyan>{line}</cyan> | "  # 行号
                           "<level>{level}</level>: "  # 等级
                           "<level>{message}</level>",  # 日志内容
                    )
    logger.add(os.path.join(BASE_DIR, f"log/interface_log_{t}.log"), format='{time:YYYYMMDD HH:mm:ss} - '  # 时间
                                                                            "{process.name} | "  # 进程名
                                                                            "{thread.name} | "  # 进程名
                                                                            '{module}.{function}:{line} - {level} -{message}',
               rotation="500MB", encoding="utf-8", enqueue=True,
               retention="10 days")

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = super(Logger, cls).__new__(cls, *args, **kwargs)

        return cls.__instance

    def info(self, msg):
        return logger.info(msg)

    def debug(self, msg):
        return logger.debug(msg)

    def warning(self, msg):
        return logger.warning(msg)

    def error(self, msg):
        return logger.error(msg)


loggings = Logger()
if __name__ == '__main__':
    loggings.info("2222222222222")
    loggings.debug("333333333333")
    loggings.warning("444444444444")
    loggings.error("555555555555")

十二、全局错误处理

utils文件夹 新建exceptions.py文件

自定义 异常处理人为抛出异常,系统异常统一处理

python 复制代码
from rest_framework.views import exception_handler as drf_exception_handle
from rest_framework.response import Response
from utils.logger import logger
from utils.response import APIResponse


class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


def exception_handler(exc, context):
    request = context.get('request')
    view = context.get('view')
    token = request.headers.get("token") or "未登录用户"
    if isinstance(exc, MyError):
        logger.warning('请求失败:用户:【%s】,请求地址:【%s】,失败原因:【%s】' % (token, request.get_full_path(), str(exc)))
        return APIResponse(1001, str(exc), data=None)
    logger.error('用户:【%s】,使用:【%s】 请求,请求:【%s】 地址,视图函数:【%s】,错误:【%s】' % (
        token, request.method, request.get_full_path(), str(view), str(exc)
    ))
    return APIResponse(999, '服务器出错,请联系系统管理员')

setting.py文件中

arduino 复制代码
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
}

十三、token鉴权

使用jwt 先安装

pip install djangorestframework-jwt

setting.py新增以下内容

ini 复制代码
import datetime 
INSTALLED_APPS = [
        ...
        'rest_framework_jwt'#注册应用
]

REST_FRAMEWORK = {
 	'DEFAULT_AUTHENTICATION_CLASSES': ( #全局认证组件
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    )
}
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),#过期时间7天
    'JWT_ALLOW_REFRESH': True,#允许刷新
}

JWT_AUTH扩展

JWT_SECRET_KEY: 'your_secret_key',#密钥

JWT_ALGORITHM: 'HS256',

JWT_VERIFY: True,

JWT_VERIFY_EXPIRATION: True,

JWT_EXPIRATION_DELTA: datetime.timedelta(seconds=3600),#token的有效期

JWT_ALLOW_REFRESH: True,

JWT_REFRESH_EXPIRATION_DELTA: datetime.timedelta(days=7),#刷新JWT的过期时间

JWT_AUTH_HEADER_PREFIX: 'JWT',

十四、配置文件详解

ini 复制代码
import os
from pathlib import Path

#项目根路径
BASE_DIR = Path(__file__).resolve().parent.parent
# 秘钥,涉及到加密的django中,都会用它
SECRET_KEY = "django-insecure-#gnxfjf&9@wokaeb33p5puv+^==#ky*6zp(&rhcb!s1+iw#w_r"

#代码可以热更新 项目上线,要改成false
DEBUG = True
#允许我的项目部署在哪个ip地址上,* 表示允许部署在所
ALLOWED_HOSTS = ["*"]


# django 是多个app组成的,里面配置app,默认带的app,django内置的app
#内置app:
#   admin后台管理,
#   auth权限管理,
#   contenttypes表中存app也表的关系,
#   sessions session表,django的session相关
#   messages:消息框架,flask讲闪现,是一样的东西
#  staticfiles:静态资源的
INSTALLED_APPS = [
    # "django.contrib.admin",
    # "django.contrib.auth",
    # "django.contrib.contenttypes",
    # "django.contrib.sessions",
    # "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",
    "api.apps.ApiConfig"
]
# 中间件
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware", # 安全相关中间件
    # "django.contrib.sessions.middleware.SessionMiddleware", # session相关中间件
    "django.middleware.common.CommonMiddleware",  # 带不带 / 问题
    "django.middleware.csrf.CsrfViewMiddleware",# csrf 认证,生成csrf串
    # "django.contrib.auth.middleware.AuthenticationMiddleware", # 用户认证
    # "django.contrib.messages.middleware.MessageMiddleware", #消息框架相关
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
# 根路由
ROOT_URLCONF = "admin.urls"
# 模板文件
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                # "django.contrib.auth.context_processors.auth",
                # "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
# 项目运行的配置---》项目上线运行,使用uwsgi 运行  application()
WSGI_APPLICATION = "admin.wsgi.application"

# 数据库配置,mysql 主从搭建完
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "test",  # 数据库名字
        "USER": "root",  # 账号
        "PASSWORD": "3735199",  # 密码
        "HOST": "127.0.0.1",  # 数据库地址
        "PORT": 3306,  # 端口一般默认
    }
}


AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]

# 国际化 语言时间等
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = False
USE_TZ = False

#返回校验错误为中文
LANGUAGE_CODE = "zh-hans"
# 静态资源
STATIC_URL = "static/"

#更改了id字段的字段类型
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
#DRF配置
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,  # 配置匿名用户
    'APPEND_SLASH': False,  # 去掉url尾部斜杠
    "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S",
    'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
}

相关推荐
大梦百万秋3 分钟前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
AI视觉网奇4 分钟前
Detected at node ‘truediv‘ defined at (most recent call last): Node: ‘truediv‘
人工智能·python·tensorflow
GuYue.bing26 分钟前
网络下载ts流媒体
开发语言·python
斌斌_____31 分钟前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
牛顿喜欢吃苹果39 分钟前
linux创建虚拟串口
python
路在脚下@40 分钟前
Spring如何处理循环依赖
java·后端·spring
-Mr_X-1 小时前
FFmpeg在python里推流被处理过的视频流
python·ffmpeg
一个不秃头的 程序员1 小时前
代码加入SFTP JAVA ---(小白篇3)
java·python·github
susu10830189111 小时前
python实现根据搜索关键词爬取某宝商品信息
爬虫·python
喜欢猪猪2 小时前
Java技术专家视角解读:SQL优化与批处理在大数据处理中的应用及原理
android·python·adb