## django_bulk_update源码分析
这个第三方插件的体量几乎只相当于工作时两三天的代码量了,是一个比较容易开始进行源代码阅读的模块,阅读完这个代码对自定义的进行django拓展也是一个相当好的借鉴
### django_bulk_update文件结构
django_bulk_update在被调用时实际只有四个文件,分别是
1. \_\_init__.py
2. helper.py
3. manager.py
4. query.py
### \_\_init__.py文件
__init__文件是所有要被调用的python模块都有的文件,里面的代码只是对当前的模块版本进行了指定
~~~py
# __init__.py
__version__ = '2.2.0'
~~~
### helper.py文件
此模块的主要文件,bulk_update功能就在此文件里
~~~py
def validate_fields(meta, fields):
fields = frozenset(fields)
field_names = set()
for field in meta.fields:
if not field.primary_key:
field_names.add(field.name)
if field.name != field.attname:
field_names.add(field.attname)
non_model_fields = fields.difference(field_names)
if non_model_fields:
raise TypeError(
"These fields are not present in "
"current meta: {}".format(', '.join(non_model_fields))
)
~~~
validate_fields是一个对bulk_update中需要修改的字段做校验的方法,这里的传入的两个参数,meta可以看做就是django中模型类的_meta属性(实验了一下后发现正常使用时传进去的的确是Option类),也就是一个Options类,fields即用户输入的想要修改的字段名字符串数组
可以看到这里对输入的原始fields数组做了一个frozenset备份,在校验完成后和这些字段中符合逻辑的集合进行差集运算,只要fields集合不是field_names集合的子集则马上抛出TypeError异常,这里的for循环主要处理的就是meta中的fields,根据判断条件可以看出,bulk_update功能**是不支持主键修改的**
~~~py
def get_fields(update_fields, exclude_fields, meta, obj=None):
deferred_fields = set()
if update_fields is not None:
validate_fields(meta, update_fields)
elif obj:
deferred_fields = obj.get_deferred_fields()
if exclude_fields is None:
exclude_fields = set()
else:
exclude_fields = set(exclude_fields)
validate_fields(meta, exclude_fields)
exclude_fields |= deferred_fields
fields = [
field
for field in meta.concrete_fields
if (
not field.primary_key and
field.attname not in deferred_fields and
field.attname not in exclude_fields and
field.name not in exclude_fields and
(
update_fields is None or
field.attname in update_fields or
field.name in update_fields
)
)
]
return fields
~~~
get_fields方法看起来比较长,实际上在做的事情比较单调,依然是通过meta参数指向的Options类进行字段筛选,需要注意的是这里的筛选条件比较多,写法相对复杂
<br>
~~~py
def grouper(iterable, size):
# http://stackoverflow.com/a/8991553
it = iter(iterable)
while True:
chunk = tuple(itertools.islice(it, size))
if not chunk:
return
yield chunk
~~~
<br>
实际上今天碰到的问题就是依靠这个方法来解决的,因为业务代码中数据库中有10W+的数据,如果直接使用all()拿到所有数据然后不做其他处理进行bulk_update,则因为单条SQL语句处理的数据量过大导致Jenkins集成时django报ProgrammingError,提示mysql server gone away,实际上就是处理超时,这里同事给的解决办法就是使用batch_size参数处理这个问题,将数据分成500条一个的chunk块来进行更新,而bulk_udpate支持的batch_size参数就是在这个方法里实现了chunk分块,这里使用了iter方法将需要更新的对象列表转化为了一个迭代器,通过islice给迭代器分片,最后形成一个生成器供使用,这样就解决了分块批量修改数据的需求
- Django基础
- 模型
- 外键
- Model Manager
- 过滤器函数
- 查询对象
- 字段的细节
- QuerySet的应用
- 视图
- Django类视图
- 权限控制
- Django进阶
- 中间件
- _meta组件
- 信号
- User模块
- prefetch_related和select_related的区别
- 较少被用到的查询对象
- Django的深层设计理念
- Declarative Syntax
- django的migration操作
- 较少用到的Queryset方法的一些坑
- Django配置
- Django环境配置变量
- Django源码阅读
- ORM
- QuerySet源码
- Query源码
- Q&F
- Model和Manager的详解
- Http请求响应
- HttpRequest
- 自建数据结构
- Django开发辅助工具
- Django-rest-framework
- Serializer
- 异步任务调度器Celery
- 数据库补充
- 定义
- 字段
- 事务
- 视图
- 函数
- 联结
- 窗口函数
- GROUPING运算符
- HAVING谓词
- django_bulk_update
- django_bulk_update源码分析
- 项目小功能开发
- Django的一些小细节