ORM概念
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
ORM由来
让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。
几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的。
ORM的优势
1.移植性好,如果我们要把mysql改成orocal,只需要改变setting配置,不需要改orm的语句,隔离数据库和数据库版本之间的差异。
2.让软件开发人员专注于业务逻辑的处理,提高了开发效率。便于维护
3.orm会提供了防止sql注入的功能。
默认utf8编码,Django默认所有的数据库是utf8编码
ORM解决的主要问题是对象和关系的映射。它通常把
- 一个类和一个表一一对应,
- 类的每个实例对应表中的一条记录,
- 类的每个属性对应表中的每个字段。
ORM的劣势
ORM的缺点是会在一定程度上牺牲程序的执行效率。多了一个翻译的过程
ORM用多了SQL语句就不会写了,关系数据库相关技能退化...
ORM总结
ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。
但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。
但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。
1 django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 。
引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
2 mysql驱动程序
- MySQLdb(mysql python) # django默认的驱动, 这就是为什么我们在init 文件中导入import pymysql pymysql.install_as_MySQLdb() 的原因了
- mysqlclient
- MySQL
- PyMySQL(纯python的mysql驱动程序) #所以我们要修改init文件
3.常用的orm有:
- django的orm 功能最齐全,只适合django项目
- sqlalchemy 文档不友好,适合大的项目
- peewee 轻量级
Django中的ORM
Model
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表,
基本情况:
- 每个模型都是一个Python类,它是django.db.models.Model的子类。
- 模型的每个属性都代表一个数据库字段。
- 综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档链接。
步骤:
1. 手动创建数据库 mysite
2. 在app/models.py里面写上一个类,必须继承models.Model这个类 (注意启动Django项目)
from django.db import models #Create your models here. class Person(models.Model): #注意后边这个model要大写 id=models.AutoField(primary_key=True) #默认不为空 name=models.CharField(max_length=32) hobby=models.CharField(max_length=32) def__str__(self): #这步是为了显示对象时知道对象的名字 return self.name class Meta: db_table='user' #如果没有这个表的名字为app_user,有了它就变为user了
3. 在Django 项目的settings.py 里面 配置上数据库的相关信息
DATABASES={ "default":{ "ENGINE":"django.db.backends.mysql", "NAME":"mysite", "USER":"root", "PASSWORD":"123456", "HOST":"127.0.0.1", #一个空字符串是默认的localhost ,这里的值可以 填写 "127.0.0.1" or"" or"localhost
如果这个值以向前斜杠(“/”)开始,而您使用的是MySQL,MySQL将通过UNIX(windows不支持)套接字连接到指定的套接字。 "PORT":3306 #空字符串表示默认端口,也就是3306 }
}
4. 在Django项目里的__init__.py里面写上 两句话 import pymysql pymysql.install_as_MySQLdb()
import pymysql pymysql.install_as_MySQLdb() #因为django默认的是mysqldb()驱动连接mysql,但是mysqldb不支持python3,所以我们就用pymysql替换mysqldb
5.在python.console中 给Django发布命令
1. python manage.py makemigrations # 告诉Django我的model.py中做了一些改动, 这时其实是在该app下建立 migrations目录,
并记录下你所有的关于modes.py的改动,比如0001_initial.py,数据库文件数据库没有增加新的表"但是这个改动还没有作用到数据库文件,数据库没有增加新的表
2. python manage.py migrate # 这时候才真的把作用到数据库文件,产生对应的表

数据类型:
数字 Int
字符: CharField() 相当于MySQL中的varchar() ,在这里并没有char类型
时间: DateField() #只显示年月日
DatetimeField() # 显示年月日时分秒
关于时间:dateField():
如果你只在前端这样写:
<td>{{ class.first_day}}</td>
它得到的结果是这样的:

那么怎么才能得到正常的年月日呢? 比如:2017-12-26,你需要这样写
<td>{{ class.first_day|date:'Y-m-d' }}</td>
对orm的操作
查:
首先在views 中导入 models模块
from django.shortcuts import render,HttpResponse from . import models # Create your views here. def index(request): #查询 student_list=models.Person.objects.all()[0].name #model.Person.objects.all得到的是一个queryset列表,列表中包含着所有对象,然后指定对象,
然后再指定它的属性 print(student_list) return HttpResponse("ok")
2.models.Person.objects.first() 得到第一个对象
3. model.Person.objects.last() 得到最后一个对象
4.model.Person.objects.filter(name="韩信") 根据条件查询 得到是一个列表包含着对象,取值时要按列表的规则来,
比如model.Person.objects.filter(name="韩信")[0].id
5.我们可以把条件写成字典,传到filter()中去, model.Person.objects.filter(**字典) #一定要把字典打散传进去
6.model.Person.objects.get(id=1) 它会得到一个对象,但是get找不到或得到2个结果会报错,filter找不到不会报错,所以不推荐使用get
7.models.Tb1.objects.exclude(name='seven') # 排除指定条件的数据
增:
第一种方法:生成数据并提交到数据库 def index(request): one=models.Person.objects.create(name="李白",hobby="女") #one 得到的仅是一个object. student_list=models.Person.objects.all() #student_list得到的才是全部的对象 print(one) # return HttpResponse("ok") return render(request,"student_list.html",{"student_list":student_list}) 第二种方法:只提交数据并不直接提交到数据库,然后手动提交的数据库 new_user2=models.Person(name="李白",hobby="nv) #先存到缓存中, new_user2.save()#这步才是提交到数据库中
删:
models.Person.objects.filter(name="曹盖").delete()
改:
第一种方法
models.Person.objects.filter(name="李白").update(name="张白") #注意单个对象没有update()方法
#第二种方法:
user48=models.Person.objects.filter(age=48).first()
user48.uername="一枝花"
#提交一下"
user48.save()
#单个对象如果要修改数据怎么办?
obj=models.Person.object.get(id=2)
obj.name="韩信"
obj.save()
批量插入
def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的个数 objs = [ models.DDD(name='r11'), models.DDD(name='r22') ] models.DDD.objects.bulk_create(objs, 10)
完整的文章参见
https://www.cnblogs.com/liwenzhou/p/8276526.html
表结构
class Class(models.Model):
id = models.AutoField(primary_key=True) # 主键
cname = models.CharField(max_length=32) # 班级名称
first_day = models.DateField() # 开班时间
查询班级
URL部分:
url(r'^class_list/$', views.class_list, name="class_list"),
视图部分:
def class_list(request):
class_list = models.Class.objects.all()
return render(request, "class_list.html", {"class_list": class_list})
HTML部分:
<table border="1">
{% for class in class_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ class.id }}</td>
<td>{{ class.cname }}</td>
<td>{{ class.first_day|date:'Y-m-d' }}</td>
</tr>
{% endfor %}
</table>
新增班级
URL部分:
url(r'^add_class/$', views.add_class, name="add_class"),
视图部分:
def add_class(request):
# 前端POST填好的新班级信息
if request.method == "POST":
cname = request.POST.get("cname")
first_day = request.POST.get("first_day")
# 还可以这么获取提交的数据,但不推荐这么写
# data = request.POST.dict()
# del data["csrfmiddlewaretoken"]
# 创建新数据的两种方式
# new_class = models.Class(cname=cname, first_day=first_day)
# new_class.save()
models.Class.objects.create(cname=cname, first_day=first_day)
# 跳转到class_list
return redirect(reverse('class_list'))
# 返回添加班级的页面
return render(request, "add_class.html")
HTML部分:
在班级列表页面添加一个a标签:
<a href="{% url 'add_class' %}">新页面添加</a>
新添加页面:
注意 {% csrf_token %} 和 date类型的input标签。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>添加班级</title>
</head>
<body>
<form action="{% url 'add_class' %}" method="post">
{% csrf_token %}
<p>班级名称:<input type="text" name="cname"></p>
<p>开班日期:<input type="date" name="first_day"></p>
<p>提交<input type="submit"></p>
</form>
</body>
</html>
删除班级
URL部分:
url(r'^delete_class/$', views.delete_class, name="delete_class"),
视图部分:
def delete_class(request):
class_id = request.GET.get("class_id")
models.Class.objects.filter(id=class_id).delete()
return redirect(reverse("class_list"))
HTML部分:
在班级列表页面的表格中添加删除。
<a href="{% url 'delete_class' %}?class_id={{ class.id }}">删除</a>
编辑班级
URL部分:
url(r'^edit_class/$', views.edit_class, name="edit_class"),
视图部分:
def edit_class(request):
if request.method == "POST":
class_id = request.POST.get("id")
cname = request.POST.get("cname")
first_day = request.POST.get("first_day")
models.Class.objects.create(id=class_id, cname=cname, first_day=first_day)
return redirect(reverse("class_list"))
class_id = request.GET.get("class_id")
class_obj = models.Class.objects.filter(id=class_id)
if class_obj:
class_obj = class_obj[0]
return render(request, "edit_class.html", {"class": class_obj})
# 找不到该条记录
else:
return redirect(reverse("class_list"))
HTML部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>编辑班级</title>
</head>
<body>
<form action="{% url 'edit_class' %}" method="post">
{% csrf_token %}
<input type="text" value="{{ class.id }}" style="display: none">
<p>班级名称:<input type="text" name="cname" value="{{ class.cname }}"></p>
<p>开班日期:<input type="date" name="first_day" value="{{ class.first_day|date:'Y-m-d' }}"></p>
<p>提交<input type="submit"></p>
</form>
</body>
</html>
补充
如果将之前的URL由 /edit_class/?class_id=n修改为 /edit_class/n/ ,视图函数和HTML部分分别应该如何修改?
URL部分:
url(r'^edit_class/(\d+)$', views.edit_class, name="edit_class"),
视图部分:
def edit_class(request, class_id):
if request.method == "POST":
cname = request.POST.get("cname")
first_day = request.POST.get("first_day")
models.Class.objects.create(id=class_id, cname=cname, first_day=first_day)
return redirect(reverse("class_list"))
class_obj = models.Class.objects.filter(id=class_id)
if class_obj:
class_obj = class_obj[0]
return render(request, "edit_class.html", {"class": class_obj})
# 找不到该条记录
else:
print("没有该班级")
return redirect(reverse("class_list"))
HTML部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>编辑班级</title>
</head>
<body>
<form action="{% url 'edit_class' class.id %}" method="post">
{% csrf_token %}
<input type="text" value="{{ class.id }}" style="display: none">
<p>班级名称:<input type="text" name="cname" value="{{ class.cname }}"></p>
<p>开班日期:<input type="date" name="first_day" value="{{ class.first_day|date:'Y-m-d' }}"></p>
<p>提交<input type="submit"></p>
</form>
</body>
</html>
queryset的特点:
1.可切片: 但是不支持负数的索引.
可切片 使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMIT 和OFFSET 子句。 1 >>> Entry.objects.all()[:5] # (LIMIT 5) >>> Entry.objects.all()[5:10] # (OFFSET 5 LIMIT 5) 不支持负的索引(例如Entry.objects.all()[-1])。通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。
2. 可迭代
articleList=models.Article.objects.all() for article in articleList: print(article.title)
3.惰性查询
查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。
queryResult=models.Article.objects.all() # not hits database print(queryResult) # hits database for article in queryResult: print(article.title) # hits database
4.缓存机制
每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。
在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。
请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:
print([a.title for a in models.Article.objects.all()]) print([a.create_time for a in models.Article.objects.all()])
这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它:
queryResult=models.Article.objects.all() print([a.title for a in queryResult]) print([a.create_time for a in queryResult])
何时查询集不会被缓存?
查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。所以,这意味着使用切片或索引来限制查询集将不会填充缓存。
例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:
>>> queryset = Entry.objects.all() >>> print queryset[5] # Queries the database >>> print queryset[5] # Queries the database again
然而,如果已经对全部查询集求值过,则将检查缓存:
>>> queryset = Entry.objects.all()
>>> [entry
for
entry
in
queryset]
# Queries the database
>>>
print
queryset[
5
]
# Uses cache
>>>
print
queryset[
5
]
# Uses cache
下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:
>>> [entry for entry in queryset]
>>>
bool
(queryset)
>>> entry
in
queryset
>>>
list
(queryset)
queryResult=models.Article.objects.all()
print(queryResult) # hits database
print(queryResult) # hits database
5.不能直接被序列化
queryset不能直接序列化为json字符串,这个知识点在rest_framework中利用到了.详情请看这篇博客
单表查询api汇总
Queryset:实际上是一些对象组成的列表
object是表示单个对象 注意:单个对象可以.字段名得到该字段下的内容 ,Queryset只能Queryset.values("字段名")才能得到该内容, 因为类中的对象只有.属性名才能得到属性值
Queryset常见的方法汇总:
1.filter()返回的是一个Queryset对象
2.all(): 返回的是一个Queryset对象
3.exclude(): 它包含了与所选条件不匹配的对象 返回的也是一个Queryset对象
models.Class_table.objects.exclude(cname="全栈10期")
结果:
(0.001) SELECT `app01_class_table`.`id`, `app01_class_table`.`cname` FROM `app01_class_table` WHERE NOT (`app01_class_table`.`cname` = '全栈10期') LIMIT 21; args=('全栈10期',) <QuerySet [<Class_table: Class_table object>, <Class_table: Class_table object>, <Class_table: Class_table object>, <Class_table: Class_table object>]>
4. values(*field):根据条件查找表中某一列的内容 ,返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典列表 ,每一条记录就是一个字典
>>>models.Class_table.objects.values("cname","id") #注意查询时列名要加引号
结果: (0.001) SELECT `app01_class_table`.`cname` FROM `app01_class_table` LIMIT 21; args=() <QuerySet [{'cname': '全栈126期ffsdf',"id":5}, {'cname': '全栈100期',"id":6}, {'cname': '全栈129期',"id":7}, {'cname': '发鬼地方',"id"=8}]>
5.values_list(*field) :和values的作用差不多,返回一个ValueQuerySet——一个特殊的QuerySet, 形式为一个元组列表,列表中包含这元组,每一条信息就是一个元组
models.Class_table.objects.values_list("cname","id") <QuerySet [('全栈126期ffsdf', 5), ('全栈100期', 6), ('全栈129期', 7), ('发鬼地方', 8)]>
6.order_by(*field): 对查询结果进行正向排序, order_by(*-field) 对结果进行反向排序
7.get() 它会得到一个对象,但是get找不到会报错,
8.reverse(): 对查询结果反向排序
9.distinct(): 从返回结果中去除重复纪录,对Queryset用没有意义,因为主键唯一,一般对values去重
10.count(): 返回数据库中匹配查询(QuerySet)的对象数量。
11.first(): 返回第一条记录 ,返回的是一个对象
12.last(): 返回最后一条记录.返回的是一个对象
13.exists(): 如果QuerySet包含数据,就返回True,否则返回False ,看下边的原生SQL语句,只查一条数据,而不像count要查所有的数据,这样提高了工作效率

学了rest-framework来补充几个api
14 only(*field):根据条件返回queryset数据类型,列表中套对象(只包含*field的对象),这就是和values的不同
text=DegreeCourse.objects.all().only('id','name') print(">>>>",text)
结果:
>>>> <QuerySet [<DegreeCourse: linx云计算运维>, <DegreeCourse: python 全栈中期>, <DegreeCourse: python全栈高级>]>
15. defer(*field):返回不含条件中字段的Queryset对象列表注意和exclude不同,defer只过滤字段,exclude过滤的是对象
tect=DegreeCourse.objects.all().defer('id','name') print(tect)
结果:
<QuerySet [<DegreeCourse: python 全栈中期>, <DegreeCourse: python全栈高级>, <DegreeCourse: linx云计算运维>]>
返回的是QuerySet对象:
1. all() 2. filter() 3. exclude() 4. valus() 5. values_list() 6. order_by() 7. reverse() 8. distinct() 9.only() 10.defer()
返回数字的:
1. count()
返回布尔值的:
1. exists()
返回具体的对象的:
1. get()
2. first()
3. last()
单表查询之神奇的双下划线
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in models.Tb1.objects.filter(name__contains="ven") # 获取name字段包含"ven"的 相当于SQL中的 like %ven% models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 models.Tb1.objects.filter(id__range=[1, 3]) # id范围是1到3的,等价于SQL的bettwen and 类似的还有:name__startswith == "abc #相当于 like abc% istartswith, name__endswith="cd", #相当于SQL中的 like %cd iendswith date字段还可以: models.Class.objects.filter(first_day__year=2017)
在Django的日志设置中,配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句。
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
Django项目完整版LOGGING配置:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'
},
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
'collect': {
'format': '%(message)s'
}
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 3,
'formatter': 'standard',
'encoding': 'utf-8',
},
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
'collect': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'collect',
'encoding': "utf-8"
}
},
'loggers': {
# 默认的logger应用如下配置
'': {
'handlers': ['default', 'console', 'error'], # 上线之后可以把'console'移除
'level': 'DEBUG',
'propagate': True,
},
# 名为 'collect'的logger还单独处理
'collect': {
'handlers': ['console', 'collect'],
'level': 'INFO',
}
},
}
Django项目常用LOGGING配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'
},
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
'collect': {
'format': '%(message)s'
}
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 3,
'formatter': 'standard',
'encoding': 'utf-8',
},
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
'collect': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'collect',
'encoding': "utf-8"
}
},
'loggers': {
# 默认的logger应用如下配置
'': {
'handlers': ['default', 'console', 'error'], # 上线之后可以把'console'移除
'level': 'DEBUG',
'propagate': True,
},
# 名为 'collect'的logger还单独处理
'collect': {
'handlers': ['console', 'collect'],
'level': 'INFO',
}
},
}

