如何在Django中添加没有微秒的 DateTimeField 属性详解


Posted in Python onJanuary 30, 2019

前言

今天在项目中遇到一个Django的大坑,一个很简单的分页问题,造成了数据重复。最后排查发现是DateTimeField 属性引起的。

下面描述下问题,下面是我需要用到的一个 Task Model 基本定义:

class Task(models.Model):
 # ...... 省略了其他字段
 title = models.CharField(max_length=256, verbose_name=u'标题')
 created_at = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')

问题描述

前端这边的分页方式不是常规的 page、page_size 方式,而是使用标志位的方式进行分页,我这里采用的就是通过创建时间的时间戳作为分页标记。比如下面是返回的第一页的数据:

{
 "data": {
 "count": 5,
 "has_next": 1,
 "tasks": [
 {
 "title": "这是一个作业标题1",
 "ts": 1546829224000,
 "id": 1
 },
 {
 "title": "这是一个作业标题2",
 "ts": 1546829641000,
 "id": 2
 }
 ]
 },
 "result": 1
}

要请求第2页的数据只需要在请求的 API 中传递上一页最后一条数据的时间戳即可,这里我们就传递 1546829641000,这样当我后台接收到这个值过后就直接过滤大于该时间戳的数据,再取一页数据返回前端即可,逻辑上很简单。过滤核心代码如下:

ts = string_utils.get_num(request.GET.get('ts', 0), 0)
alltask = Task.objects.filter(created_at__gt=date_utils.timestamp2datetime(ts))

这段代码很简单,主要就是将前台传递过来的时间戳转换成 DateTime 类型的数据,然后利用created_at__gt来过滤,就是大于这个时间点的就可以。然后问题来了,查询出来的数据始终包含了上一页最后一条数据,感觉很奇怪,我这里明明用的是gt而不是gte,怎么会重复这条数据呢。

于是,我们把上一页最后一条数据的 created_at 字段打印出来和传递过来的时间戳进行对比下:

>>> task = Task.objects.get(pk=2)
>>> task.created_at
datetime.datetime(2019, 1, 7, 10, 54, 1, 343136)

然后将时间戳转换成 DateTime 类型的数据:

>>> ts = int(1546829641000/1000)
>>> date_utils.timestamp2datetime(ts)
datetime.datetime(2019, 1, 7, 10, 54, 1)

现在看到区别没有,从数据库中查询出来的 created_at 字段的值包含了一个微秒,就是后面的 343136,而时间戳转换成 DateTime 类型的值是不包含这个微秒值的,所以我们上面查询的使用created_at__gt来进行过滤很显然 created_at 的值是大于下面的值的,因为多了一个微秒,所以就造成了数据重复了,终于破案了。

解决方法

那么要怎么解决这个问题呢?当然我们可以直接在数据库中就保存一个时间戳的字段,用这个字段直接来进行查询过滤,肯定是可以解决这个问题的。

如果就用现在的 created_at 这个 DateTimeField 类型呢?如果保存的数据没有这个微秒是不是也可以解决这个问题啊?

我们可以去查看下源码为什么 DateTimeField 类型的数据会包含微秒,下面是django/db/backends/mysql/base.py文件中的部分代码说明:

class DatabaseWrapper(BaseDatabaseWrapper):
 vendor = 'mysql'
 # This dictionary maps Field objects to their associated MySQL column
 # types, as strings. Column-type strings can contain format strings; they'll
 # be interpolated against the values of Field.__dict__ before being output.
 # If a column type is set to None, it won't be included in the output.
 _data_types = {
 'AutoField': 'integer AUTO_INCREMENT',
 'BinaryField': 'longblob',
 'BooleanField': 'bool',
 'CharField': 'varchar(%(max_length)s)',
 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
 'DateField': 'date',
 'DateTimeField': 'datetime',
 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
 'DurationField': 'bigint',
 'FileField': 'varchar(%(max_length)s)',
 'FilePathField': 'varchar(%(max_length)s)',
 'FloatField': 'double precision',
 'IntegerField': 'integer',
 'BigIntegerField': 'bigint',
 'IPAddressField': 'char(15)',
 'GenericIPAddressField': 'char(39)',
 'NullBooleanField': 'bool',
 'OneToOneField': 'integer',
 'PositiveIntegerField': 'integer UNSIGNED',
 'PositiveSmallIntegerField': 'smallint UNSIGNED',
 'SlugField': 'varchar(%(max_length)s)',
 'SmallIntegerField': 'smallint',
 'TextField': 'longtext',
 'TimeField': 'time',
 'UUIDField': 'char(32)',
 }

 @cached_property
 def data_types(self):
 if self.features.supports_microsecond_precision:
  return dict(self._data_types, DateTimeField='datetime(6)', TimeField='time(6)')
 else:
  return self._data_types

 # ... further class methods

上面的 data_types 方法中在进行 MySQL 版本检查,属性supports_microsecond_precision来自于文件django/db/backends/mysql/features.py:

class DatabaseFeatures(BaseDatabaseFeatures):
 # ... properties and methods

 def supports_microsecond_precision(self):
 # See https://github.com/farcepest/MySQLdb1/issues/24 for the reason
 # about requiring MySQLdb 1.2.5
 return self.connection.mysql_version >= (5, 6, 4) and Database.version_info >= (1, 2, 5)

从上面代码可以看出如果使用的 MySQL 大于等于 5.6.4 版本,属性DateTimeField会被映射成为数据库中的datetime(6),所以保存的数据就包含了微秒。

在 Django 中暂时没有发现可以针对改配置进行设置的方法,所以我们要想保存的数据不包含微秒,我们这里则可以将上面的data_types属性进行覆盖即可:

from django.db.backends.mysql.base import DatabaseWrapper

DatabaseWrapper.data_types = DatabaseWrapper._data_types

将上面的代码放置在合适的地方,比如models.py或者__init__.py或者其他地方,当我们运行 migrations 命令来创建 DateTimeField 列的时候对应在数据库中的字段就被隐射成为了datetime,而不是datetime(6),即使你用的是 5.6.4 版本以上的数据库。

当然要立即解决当前的问题,只需要更改下数据库中的 created_at 字段的类型即可:

mysql> ALTER TABLE `task` CHANGE COLUMN `created_at` `created_at` datetime NOT NULL;
Query OK, 156 rows affected (0.14 sec)
Records: 156 Duplicates: 0 Warnings: 0

这样数据重复的 BUG 就解决了。

参考链接:https://stackoverflow.com/questions/46539755/how-to-add-datetimefield-in-django-without-microsecond

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
pygame学习笔记(3):运动速率、时间、事件、文字
Apr 15 Python
Python类的定义、继承及类对象使用方法简明教程
May 08 Python
Python标准库之collections包的使用教程
Apr 27 Python
浅谈Python采集网页时正则表达式匹配换行符的问题
Dec 20 Python
pandas删除指定行详解
Apr 04 Python
Python玩转PDF的各种骚操作
May 06 Python
使用python爬取微博数据打造一颗“心”
Jun 28 Python
Python集合操作方法详解
Feb 09 Python
浅谈spring boot 集成 log4j 解决与logback冲突的问题
Feb 20 Python
Python如何实现定时器功能
May 28 Python
python thrift 实现 单端口多服务的过程
Jun 08 Python
python 模拟登陆github的示例
Dec 04 Python
Pandas统计重复的列里面的值方法
Jan 30 #Python
自学python的建议和周期预算
Jan 30 #Python
python DataFrame 取差集实例
Jan 30 #Python
对python dataframe逻辑取值的方法详解
Jan 30 #Python
对Python中DataFrame选择某列值为XX的行实例详解
Jan 29 #Python
把pandas转换int型为str型的方法
Jan 29 #Python
使用pandas把某一列的字符值转换为数字的实例
Jan 29 #Python
You might like
大师制作的中短波矿石收音机
2020/04/02 无线电
php数字每三位加逗号的功能函数
2015/10/22 PHP
JSON字符串传到后台PHP处理问题的解决方法
2016/06/05 PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
2017/09/20 PHP
php 删除一维数组中某一个值元素的操作方法
2018/02/01 PHP
jquery为页面增加快捷键示例
2014/01/31 Javascript
js实现九宫格图片半透明渐显特效的方法
2015/02/16 Javascript
浅谈toLowerCase和toLocaleLowerCase的区别
2016/08/15 Javascript
Javascript使用function创建类的两种方法(推荐)
2016/11/19 Javascript
Bootstrap CSS组件之输入框组
2016/12/17 Javascript
详解js的六大数据类型
2016/12/27 Javascript
jQuery插件扩展操作入门示例
2017/01/16 Javascript
微信小程序 详解页面跳转与返回并回传数据
2017/02/13 Javascript
webpack学习教程之publicPath路径问题详解
2017/06/17 Javascript
ES6中Class类的静态方法实例小结
2017/10/28 Javascript
Node.js中,在cmd界面,进入退出Node.js运行环境的方法
2018/05/12 Javascript
[01:14:31]Secret vs VG 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
[43:03]完美世界DOTA2联赛PWL S2 PXG vs Magma 第二场 11.21
2020/11/24 DOTA
python开发之tkinter实现图形随鼠标移动的方法
2015/11/11 Python
Python下载指定页面上图片的方法
2016/05/12 Python
关于Python 3中print函数的换行详解
2017/08/08 Python
Python 解决中文写入Excel时抛异常的问题
2018/05/03 Python
python 创建一个空dataframe 然后添加行数据的实例
2018/06/07 Python
利用PyCharm Profile分析异步爬虫效率详解
2019/05/08 Python
Python3.9又更新了:dict内置新功能
2020/02/28 Python
Django如何使用redis作为缓存
2020/05/21 Python
python保留格式汇总各部门excel内容的实现思路
2020/06/01 Python
python 三种方法提取pdf中的图片
2021/02/07 Python
Wojas罗马尼亚网站:波兰皮鞋品牌
2018/11/01 全球购物
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
2015/07/16 面试题
简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程
2016/01/06 面试题
大学生学习自我评价
2014/01/13 职场文书
乡镇安全生产目标责任书
2014/07/23 职场文书
小学家长通知书评语
2014/12/31 职场文书
文明倡议书
2015/01/19 职场文书
WINDOWS下安装mysql 8.x 的方法图文教程
2022/04/19 MySQL