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.注释不需要的配置
目的:纯净版 注释不需要的内容 注册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
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中
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
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',
}