Python+Django+Drf+drf-yasg 学习笔记
文章目录
References:
Django 项目启动:
# 创建环境
python -m venv venv
# 安装依赖
pip install -r requirement.txt
# 检测模型文件修改(初次启动的时候可以直接 migrate)
python manage.py makemigrations app_name
# 创建新定义的模型的数据表
python manage.py migrate
# 收集静态文件至 STATIC_ROOT 中
python manage.py collectstatic --noinput
# 启动服务
python manage.py runserver
# 进入命令行
python manage.py shell
从 0 开始构建一个 Django 项目
# 创建环境
python -m venv venv
# activate env
source venv/bin/activate
pip install django
python -m django --version
django-admin startproject mysite
# 此时已经能够启动一个项目了
python manage.py runserver
# 修改 settings 文件,
# 配置数据库,redis,
# 配置静态文件并生成静态文件(主要是 admin 使用)
python manage.py collectstatic --noinput
# 编写应用,并在 settings 中添加到 INSTALLED_APPS
python manage.py makemigrations
python manage.py migrate
# 如果不想本次生成 migrate 文件,可以删除文件并在数据库中 django_migration 表中删除相关记录以及对应表
# 配置限流策略
# 配置日志输出路径
# 配置 swagger 等(如有需要)
# 编写业务代码
# 编写 entrypoint.sh 启动脚本,编写 .dockerfile
模型层(models)
作用: 构建和操纵 web 应用的数据
特点:由于 Django 的 orm 即对象关系映射模式,每一个模型映射一张数据库表
-
每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
-
模型类的每个属性都相当于一个数据库的字段
-
Q:
model_name.objects.get()
和model_name.objects.filter()
有什么区别- A: get 可以理解为精准匹配,通常返回值是一个定义的model对象,只有一条记录返回的时候才正常,也就说明get的查询字段必须是主键或者唯一约束的字段。当返回多条记录或者是没有找到记录的时候都会抛出异常。
- filter返回 QuerySet 对象列表,有没有匹配的记录都可以。
migrate
若对于新的未上线使用的数据库模型更改,可以将测试环境数据库中 django_migration 中对应 app 的记录删除,然后生成 migration 文件之后 migrate 方可不报错。
字段关联关系
通常我们建的各个表都有关联关系,比如商品表 Store 有外键 books,其为 Book 表的主键
Book 表有外键 authors,其为 Author 表的主键
django joins and aggregates 告诉我们如果想在 Store 表中知道每一本书籍的最高最低价格可以这么使用:
from django.db.models import Max, Min
# 每一本书都知道最高价和最低价
q = Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
>>> q[0].min_price
如果想知道每本书的价格可以这么使用:
F() 可以引用数据库模型字段值执行数据库操作,而不需要把其从数据库中拉出到 Python 内存中
from django.db.models import F
q = Store.objects.annotate(price=F('books__price'))
>>> q[0].price
annotate 和 aggretate 的区别
-
aggregate() 为所有的 QuerySet 生成一个汇总值,相当于 Count()。返回结果类型为 Dict。
-
annotate() 为每一个 QuerySet 在指定属性上生成汇总值,相当于 GROUP BY。返回结果类型 QuerySet。
django 字段类型
django 字段对应 mysql 中的数据类型参考:Django中的常用字段类型
drf serializers
serializers 的适用对象:
serializers 主要是对 django QuerySet 对象, Model 实例的操作(当然并不限于,它可以将 queryset 或者数据模型实例等转化成原生的 python 数据类型,进而能够渲染进如 json、xml 或其他内容类型),包括序列化(将 Python 数据类型转换为 json)和反序列化
如何使用 serializers:
-
通常我们可能会在一个 app 的 serializers 文件中里面创建多个不同的 serializer 来辅助我们的请求,比如
AAACreateSerializer
AAAListSerializer
。 -
通常我们用来做请求参数的序列化、校验或返回结果的序列化。基于此,可以在
swagger_auto_schema
中注明 request_body 以及 response(两者使用的序列化类可以不一样)
serializer 的分类:
- 直接继承 serializers.Serializer
通常用作不涉及数据模型的 get post put delete 请求的请求参数或返回内容序列化,常用的 validation 可以参考
- validation 相关内容
通常我们在视图中对数据反序列化处理的时候会使用 serializer.is_valid(raise_exception=True)
,这里就是进行验证。validation 在 serializer 中分为两类:
1. 字段级别的 validation
通过在 Serializer 类下写 `validate_<field_name>` 方法
2. 对象级别的 validation(对多个字段进行验证)
通过在 Serializer 类下写 `validate` 方法
- 关于嵌套字典的序列化
可以参考 Django Rest Framework ListField and DictField
结论是不要嵌套 serializers.DictField 而是再定义一个 serializer 类做嵌套。
- 其他
如果是做数组的序列化,可以这样标识
class TestSerializer(serializers.Serializer):
a = Serializers.CharField(label='a')
b = Serializers.CharField(label='a')
class ResSerializer(serializers.Serializer):
res = TestSerializer(many=True)
# 最终使用 ResSerializer 序列化出的结果就是 [{a:'1', b:'2'}, {a:'2', b:'3'}]
- 继承于 serializers.ModelSerializer
- ModelSerializer 的父类是Serializer,是 在Serializer的基础上的扩展
- 可以直接通过 Class Meta 里配置 fields 来对 model 进行序列化,简化了 Serializer 的操作
- Meta里可以配置如下配置:
- model:指定需要序列化的 model
- depth:序列化时,读取数据的深度,默认是0,0表示仅展示该model对象的值,1表示为展示与该model对象直接外键关联的对象的相关数据,以此类推
- fields:需要序列化的字段,是一个元组,
__all__
表示所有字段 - exclude:不需要序列化的字段,默认是在全字段的情况下剔除不需要序列化的字段,
- 注意:fields和exclude不能同时使用
- extra_kwargs:给fields中的字段,添加额外的属性,如只读(read_only)、只写(write_only)等
- read_only_fields:只读字段
- validators:可以直接在 Meta 中定义要使用到的 validator
备注:
- 起别名 & 外键:在 Meta 外显示指定序列化字段时,可以通过source='xxx’来指定需要关联的外键对象的值,通过点分式获取对应的字段,当然这也是一种给字段起别名的方式
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
...
- 在 Meta 外显示指定序列化字段时,除了 1 中的方式还可以使用
SerializerMethodField
通过在类上添加一个方法获取其值,添加任意类型的数据到对象的序列化表示上。
同时结合 drf-yasg @swagger_serializer_method 可以用这个装饰器来装饰 serializers.SerializerMethodField 来提示 swagger 如何描述这个字段
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
@swagger_serializer_method(serializer_or_field=serializers.IntegerField)
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
- 一般我们会在 serializer 中重写 create 或 update 等方法(如果你有这个需求需要添加你的逻辑,同时添加
Model.objects.update_or_create
),然后在 views 中直接执行serializer.save()
即可,因为请求到来后会先执行 views 中 create 方法,然后执行serializer.save()
的时候执行 serializer 中的 create 方法 - 不是任何一个接口都需要 serializer,一些直接返回列表的接口则可以直接
Response(data)
,或直接Response(Iterator)
passing iterator,因为 Django 原生的 HttpResponse 会立即将 Iterator 消费掉 - django-rest-framework: Cannot call
.is_valid()
as nodata=
keyword argument was passed when instantiating the serializer instance 当你想通过 serializer 序列化数据时可以直接将数据作为第一个参数传入,得到serializer.data
;当你想反序列化数据时需要通过关键字参数 data 传入,此时如果想使用serializer.data
必须先调用serializer.is_valid()
# serialize 常用于序列化 queryset
serializer = CommentSerializer(comment)
serializer.data
# {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
# deserializer 常用于验证要存入数据库的数据,并存储
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
视图层(views)- 相当于 controller 层,负责处理用户的请求并返回响应
视图有基于函数的视图,有基于类的视图,一般使用 drf 之后,我们会继承 ModelViewSet 来创建基于类的视图
APIView
APIView 类是 drf 提供的 Django View
类的子类,和 View
类一样,进来的请求会被 dispatch 到一个准确的 handler 方法比如 .get()
.post()
但他也有以下几个跟传统的 View
类不同的点:
- 传递给 handler 方法的请求是 drf 的
Request
实例,而不是 Django 的HttpRequest
实例 - handler 方法返回的是 drf 的
Response
,而非 Django 的HttpResponse
- 任何 APIException 异常都将被捕获并调停到适当的响应中。
- 传入的请求将经过身份验证,并在将请求 dispatch 给处理程序方法之前运行适当的权限
与/或
限制检查。
ViewSets
这个类有以下两个特殊点:
-
ViewSets 类是一类基于类的视图,它不提供各种像
.get
.post
这样的方法 handler。而是提供想.list
.create()
这样的 actions -
创建了继承 ViewSets 类的视图之后,不再需要显式的在 urlconf 中注册视图,而是通过 router 类注册视图,它会为你自动配置好 urlconf
router = DefaultRouter()
router.register('account', views.AccountViewSet)
app_name = 'account'
urlpatterns = [
path('', include(router.urls))
]
- ModelViewSet 继承自各个 mixin 类和 GenericViewSet,所以默认就提供了增删查改相关方法,具体在代码中点击进入看就会发现
django-filter 过滤
实现过滤能力,比如通过在 url 中传递参数进行过滤。可以使用默认的过滤后端,也可以按自己的需求实现过滤后端
代码实现参考:demo
路由
关于 URL:
- Django 规定每一个模式要求 URL 以一个斜线结尾
- urlpatterns 都可以 include 其他 URLconf 模块,即将一部分 URL 放置于其他 URL 下面
- URL 模式可以使用正则表达式,使用 re_path() 而不是 path()
而对于 drf:常用路由是 DefaultRouter
- drf 添加了对自动 URL 路由到 Django 的支持,提供了一种简单、快速和一致的方式将试图逻辑连接到一组 URL
- 常用的是 DefaultRouter,默认会不全常见的 CRUD 接口
- 自动基于 viewSet 生成 xxx-list 以及 xxx-detail,自定义的接口,默认将 “_” 替换成 “-”
Admin
这是 Django 最强大的部分之一,它从你的模型中读取元数据,提供一个快速的、以模型为中心的界面,受信任的用户可以管理你网站的内容(或者说元数据)。
- step1: 从 根 URLConf 中创建 admin 路由
- step2: python manage.py createsuperuser 创建管理员账号
- step3: 继承 ModelAdmin 用来处理自定义界面
- 其他处理主题的方式可以参考 https://docs.djangoproject.com/zh-hans/3.2/ref/contrib/admin/#hooking-adminsite-to-urlconf
管理静态文件
- 配置静态文件,定义 STATIC_URL STATIC_ROOT
- 配置 whiternoise 提供静态文件服务(可以不依赖像 nginx 这样的 external service)
- python manage.py collectstatic
gunicorn
wsgi(Web Server Gateway Interface or Python Web Server Gateway Interface)
定义:
python web 服务器网关
作用:
它是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。(貌似 js 没有)
Gunicorn 是一个 unix 上被广泛使用的高性能的 Python WSGI UNIX HTTP Server。
# 启动命令
gunicorn [OPTIONS] [WSGI_APP]
gunicorn --config etc/gunicorn.py cloud_resource.wsgi:application
Django log
将所有日志写入一个文件并保存在服务器上:
通过调用 logging.getLogger() 时传入名字,同时结合 Handler 将日志输出到指定的以这个名称命名的文件。
(其中:Handler 是决定如何处理 logger 中每一条消息的引擎。它描述特定的日志行为,比如把消息输出到屏幕、文件或网络 socket。)
Django uuid 采坑
注意通常我们在 Django 代码中设置数据的 id 为 uuid 格式,通过 rest 接口返回给前端的时候即为 uuid string 的格式,落库的时候是普通没有横杠的字符串,读取时 Django 应该做了一次转换。
然而我们从数据库中直接 copy 出的 id 是没有短横杠的,将此传入参数中查询数据是查询不到的。
python 语法
# python 能表示的最大数
# 最大整数
sys.maxsize
# 最大浮点数
float('inf')
# 最小整数
-sys.maxsize-1
# 最小浮点数
float('-inf')
python 基本数据类型
numbers(数字),string(字符串),tuple(元组),list(列表),dict(字典), set(集合)
可变数据类型:存的是内存中的地址,修改时改值不改址:list(列表),dict(字典), set(集合)
不可变数据类型:存的是值,修改时改值也改址:numbers(数字),string(字符串),tuple(元组)
关于 tuple:
元组:一种有序列表,但和 list 不同的是一经定义无法改变,且定义时必须含有初值,因此安全性高。
*args **kwargs 区别
*args 无名参数,是一个 tuple
**kwargs 关键字参数,是一个 dict
列表相关
对于 iterable 类型的数据,可以多关注 itertools(为高效循环而创建迭代器的函数) 中的方法
注:凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。参考:迭代器
# 遍历列表的索引和值
for idx, val in enumerate(list):
# 利用这种方式可以得到键为数组值,值为索引的字典
nums_dict = {val:idx for idx,val in enumerate(list)}
# 使用 groupby 对对象数组进行分组真的很方便
# 结合 itemgetter 使用更灵活,
from operator import itemgetter
import itertools
dict_list = [{'name': 'a', 'value': 1}, {'name': 'a', 'value': 2}, {'name': 'b', 'value': 1}, {'name': 'c', 'value': 2}]
after_grouped = [{"name":key, "data":list(group)} for key,group in itertools.groupby(a, key=itemgetter('name'))]
>>> [{'name': 'a', 'data': [{'name': 'a', 'value': 1}, {'name': 'a', 'value': 2}]}, {'name': 'b', 'data': [{'name': 'b', 'value': 1}]}, {'name': 'c', 'data': [{'name': 'c', 'value': 2}]}]
排序
python3 有两种排序方式,list.sort() 和 sorted() 函数,区别如下
- list.sort() 会直接修改原列表,sorted() 函数 会返回一个新的排序后列表
- list.sort() 只能应用于列表,sorted() 函数可以接受任何可迭代对象,因此推荐使用 sorted() 来解决问题
- 使用方式举例
# list sort 方法会改变原数组
list.sort(key=None, reverse=False)
# 使用 key 自定义排序
arr = [[1,2], [0,1], [7,2]]
arr.sort(key=lambda x:x[1])
# 自定义方法排序,使用比较函数,负值表示小于,零表示相等,正值表示大于
def my_sort(x, y):
if x[0] == y[0]:
return x[1] - y[1]
else:
return x[0] - y[0]
>>> [[0, 1], [1, 2], [7, 2]]
# sorted 应用于所有可迭代对象上,且不会对原对象进行修改,会返回一个新的对象
sorted(iterable, key=None, reverse=False)
a = [[1, 0], [1, 1], [0, 9]]
a.sort(key=functools.cmp_to_key(my_sort))
test = sorted(a, key=functools.cmp_to_key(my_sort))
# 会是同样的结果
test = sorted(a, key=itemgetter(0, 1))
print(a) # [[0, 9], [1, 0], [1, 1]]
列表(数组)初始化
带初始值或定长的数组初始化:
# 一维数组
list0 = [0]*10
# 二维数组,注意一定不用一维数组的方式
list00 = [list() for i in range(10)]
# 已知 dict 的键不能是可变数据如 list dict 等,但为了能正常使用哈希表,可以用 id(obj) 返回 identity
id(list0)
>>> 450572xxxx
# 列表中某个元素的位置
aa = [1,2]
aa.index(2) # 1
# 批量替换列表中的内容
nums[l:r+1] = another_list
nums = [1,2,3]
nums[0:2] = [4,5] # nums 变成 [4,5,3]
不定长数组初始化:
list0 = []
# 声明了数组之后逐渐追加元素
list0.append(1)
求二维数组的最大值:
max_val = max(max(item) for item in res)
判断相关的语法
# 判断一个字符串是否为数字
str = "123"
str.isdigit() # True
复制列表 or 字典
import copy
a = [1,2,3]
# b 即为和 a 中元素完全相同的列表,但 b 指向的地址和 a 不同
b = a[:]
b = a * 1
b = list(a)
# 但是上述方法只进行了一层拷贝,要想达到深拷贝
b = copy.deepcopy(a)
c = {"a":"b"}
d = copy.copy(c)
字符串相关
# 拼接字符串
str1 = "test0"
str2 = "--"
str = str1 + str2
# 重复字符串
str1 = "test"
str = str1 *2 # "testtest"
# 字符串转数组
str = "hello"
str_list = list(str)
# 数组转字符串(这个需要 str_list 里的元素也是字符串)
str = ''.join(str_list)
str = ''.join(list(map(str, int_list)))
# 统计某个字符出现的次数
("test").count('t')
>>> 2
# split You can specify the separator, default separator is any whitespace.
s = "a good example"
s.split()
>>> ['a', 'good', 'example']
字典相关
# 判断字段中是否有某个 key
d = {'name':'Tom', 'age':10, 'Tel':110}
print('name' in d)
# 遍历字典
for key, val in dict.items()
# 移除字典键值对
del d['name']
随机数
import random
random.randint(1, 10) # [1,10]之间任意一个数,注意右边为闭区间
randow.randrange(1,10) # [1,10) 之间任意一个数,注意右边为开区间
数学运算
# 求绝对值
abs(val)
# 求二进制字符串
bin(4)
>>> '0b100'
# 统计字符串中某个值出现的次数
bin(4).count('1')
>>> 1
# python 表示二进制数的方式
0b10 == 2
>>> True
# 累积奇偶数用异或运算 ^
1^1
>>> 0
0^0
>>> 0
1^0
>>> 1
# ord() 返回能代表 unicode 字符的数字
ord('a')
>>> 97
python 装饰器 decorator
在代码运行期间动态添加附加功能的东西就是装饰器。很多语言中都有装饰器,比如 javascript 在 ts 中即可以使用 @decorator, python 也不例外
类相关
python 中所有类默认继承 object 类。而 object 类提供了了很多原始的内建属性和方法,所以用户自定义的类在 Python 中也会继承这些内建属性, 比如 __init__
__str__
等
__str__
:将实例对象按照自定义的格式用字符串的形式显示出来,提高可读性,通常调用 print(x) 的时候就会调用这个方法。
python operator
The operator module in Python (itemgetter, attrgetter, methodcaller)
python 提供内置的 operators 去获取 items, attributes, 调用 methods. 通常这些 operator 操作适用于 key 的操作中,比如 sorted/min/max 等
python 异常处理
我们知道当程序抛出异常的时候会导致 python interpreter 退出,有些场景比如 http 请求检测到 4xx 错误时,我们同样想让程序终止,该怎么做呢?
-
raise Execption
,注意可以使用 specific exception class to avoid unexpectation errors -
使用
sys.exit(1)
,它其实也是抛出一个异常,但是继承于BaseException
References:
Stop Using Exceptions Like This in Python
Difference between calling sys.exit() and throwing exception
requests
resp.text 是 Content of the response, in unicode.
resp.content 是 Content of the response, in bytes.
如果想使用 json,直接 resp.json() 即可
**如何获得 url name ? **
通常写单测的时候会用到 reverse(url_name) 来获取 url 的方式,而往往我们知道 url 而不知道 url_name,此时可以通过在 views 里写如下方式来获取 url_name
from django.urls import resolve
print(resolve(request.path).view_name)
python 常见的配置文件格式
文件类型复杂性分别为:ini < json ~ toml < yaml
为什么要使用配置文件
跨语言进行配置文件共享时,就需要选择使用通用的配置文件类型来存储配置内容,常见的配置文件格式类型由 ini/json/toml/yaml/xml
- ini 文件:默认值以字符串的形式呈现
from configparser import ConfigParser
cfg = ConfigParser()
cfg.read("/Users/Bobot/db.ini")
db_cfg = dict(cfg.items("localdb"))
con = pymysql.connect(**db_cfg)
- json 文件
with open('/Users/Bobot/db.json') as j:
cfg = json.load(j)['localdb']
- tomal 文件:比 ini 文件扩展了时间戳、布尔值、数组等,样式和 python 原生写法十分相似,推荐使用
import toml
with open('/Users/Bobot/db.toml', 'r') as file:
data = toml.load(file)
- yaml 文件:
from pyyaml import yaml
with open('/Users/Bobot/db.yaml', "r") as config:
cfg = yaml.safe_load(config)