前言
使用 django 的 orm 建模型的時候,添加 DateTimeField 字段,發現存到數據庫的日期時間格式是'2020-06-28 21:30:48.481516'
我們一般習慣的格式是'2020-06-28 21:30:48'不帶后面的6位數毫秒
參考stackoverflow鏈接:https://stackoverflow.com/questions/46539755/how-to-add-datetimefield-in-django-without-microsecond
環境:
- django 2
- mysql 5.7
問題描述
model 模型是這樣寫的
class People(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
create_time = models.DateTimeField()
class Meta:
db_table = "people"
同步數據庫后,表里面的字段類型如下
mysql> desc people;
+-------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| create_time | datetime(6) | NO | | NULL | |
+-------------+-------------+------+-----+---------+----------------+
Django 創建的 datetime 字段是帶有6位數的毫秒的
datetime(6)
我們期望的是 datetime 在同步數據庫的時候應該不帶毫秒
datetime()
解決辦法
這是一個非常有趣的問題。我查看了源代碼,下面是用小數秒設置日期時間的原因,找到源碼的位置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 中暫時沒有發現可以針對改配置進行設置的方法,所以最后用了猴子補丁(monkey-patching):
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 版本以上的數據庫。
強制修改表
上面的猴子補丁(monkey-patching)對於已存到數據庫的數據是沒法修改的,如果是已經建表並且有數據了,需執行SQL修改表。
你想立即解決這個問題,數據庫的日期時間字段 datetime(6) 強制修改成 datetime()即可
ALTER TABLE `yoyo_card` CHANGE COLUMN `add_time` `add_time` datetime NOT NULL;
執行效果
當然這只是一個臨時解決方案