-
主要解决使用django自带ORM字段类型,migrate建表时与mysql字段类型规范不符或缺失
-
使用原生SQL语句建表则可以忽略
-
扩展的字段类型
- FixedCharField
django.db.models.CharField
对应mysql的varchar
,这里扩展出char类型
- BinaryFixCharField
- 区分大小写的char,对应mysql字段定义语句
char(n) binary
- 区分大小写的char,对应mysql字段定义语句
- BinaryCharField
- 区分大小写的varchar,对应mysql字段定义语句
varchar(n) binary
- 区分大小写的varchar,对应mysql字段定义语句
- TextField
django.db.models.TextField
对应mysql的longtext
,这里扩展出text类型
- LongTextField
django.db.models.TextField
的别名,命名更严谨
- TinyIntField
- 参考
django.db.models.BigIntegerField
,扩展出tinyint类型
- 参考
- FixedCharField
-
代码
# 文件位置:libs/django/db/models.py
# coding: utf-8
from django.db import models
from django.utils.translation import gettext_lazy as _
class FixedCharField(models.Field):
"""char类型"""
description = _("Char")
def db_type(self, connection):
"""
限定生成数据库表的字段类型为 char,长度为 max_length 指定的值
"""
return 'char(%s)' % self.max_length
class BinaryFixCharField(models.Field):
"""char binary 类型"""
description = _("Char Binary")
def db_type(self, connection):
return 'char(%s) binary' % self.max_length
class BinaryCharFiled(models.Field):
"""varchar binary类型"""
description = _('Varchar Binary')
def db_type(self, connection):
return 'varchar(%s) binary' % self.max_length
class TextField(models.TextField):
"""text类型"""
description = _('Text')
def db_type(self, connection):
return 'text'
LongTextField = models.TextField
class TinyIntField(models.IntegerField):
"""tinyint类型"""
description = _('Tiny (1 byte) integer')
MAX_TINYINT = 127
def formfield(self, **kwargs):
return super().formfield(**{
'min_value': -TinyIntField.MAX_TINYINT - 1,
'max_value': TinyIntField.MAX_TINYINT,
**kwargs,
})
def db_type(self, connection):
return 'tinyint'
- 表示xxx类型字段,使用枚举类中定义的字段代替手动指定值,减少误操作
- 支持choices设置
- 代码
# 文件位置:libs/django/db/enum.py
# coding: utf-8
from enum import Enum, unique
from types import DynamicClassAttribute
def first_value_unique(enumeration):
"""装饰器:序列的第一个元素唯一"""
unique(enumeration)
number_set = set()
for elem in enumeration:
first_value = elem.value[0]
if first_value in number_set:
raise ValueError('duplicate first value found in %r' % elem)
number_set.add(first_value)
return enumeration
class TypeFieldEnum(Enum):
"""类型字段枚举类
使用demo:
@first_value_unique
class XxxEnum(TypeFieldEnum):
# 元组格式:(数据库中存储的值, 展示内容)
XXX1 = (1, '展示内容1')
XXX2 = (2, '展示内容2')
...
Model.objects.create(xxx_type=XxxEnum.XXX1.val)
"""
@classmethod
def to_tuple(cls):
"""转为元组"""
return tuple(item.value for item in cls)
@DynamicClassAttribute
def val(self):
"""值"""
return self.value[0]
@DynamicClassAttribute
def display(self):
"""展示"""
return self.value[1]
- django.db.models.BigAutoField
- 以自增
bigint
为主键 - primary_key=True
- 以自增
- libs.django.db.models.TinyIntField
- 对应mysql
tinyint
类型 - 存储范围:-128 ~ 127
(1字节)
- 建议表示类型字段使用此类型
- 对应mysql
- django.db.models.IntegerField
- 对应mysql
int
类型 - 存储范围:-2^31 ~ 2^31-1
(4字节)
- 对应mysql
- django.db.models.BigIntegerField
- 对应mysql
bigint
类型 - 存储范围:-2^63 ~ 2^63-1
(8字节)
- 对应mysql
- libs.django.db.models.FixedCharField
- 对应mysql
char
类型 - max_length对应char的长度
- 对应mysql
- django.db.models.CharField
- 对应mysql
varchar
类型 - 建议设置null=False, default=''
- 对应mysql
- libs.django.db.models.BinaryFixCharField
- 对应mysql字段定义语句
char(n) binary
,实质是使用该表定义的字符集下,_bin
后缀的校对规则,如utf8mb4_bin
- 区分大小写
- 对应mysql字段定义语句
- django.db.models.DateField
- 对应mysql
date
类型
- 对应mysql
- django.db.models.DateTimeField
- 对应mysql
datetime(6)
类型 - auto_now_add=True,模型创建时自增添加当前时间
- auto_now=True,模型创建或更新时添加当前时间
- 注意:QuerySet.update() 为直接执行SQL语句,不涉及模型对象操作,需要手动指定更新时间
- 对应mysql
- django.db.models.BooleanField
- 对应mysql
tinyint
类型 - 建议设置default=0或1
- 通常建议每个表固定定义一个
is_deleted
字段,作逻辑删除用
- 对应mysql
- django.db.models.DecimalField
- 绝对精度类型,对应mysql
decimal(M, D)
类型 - max_digits <==> M,表示数字总个数
- decimal_places <==> D,表示小数位的个数
- 建议金钱相关字段使用
- 绝对精度类型,对应mysql
django.db.models.FloatField- 低精度,禁止使用
- libs.django.db.models.TextField
- 对应mysql
text
类型 - 文本存储考虑使用
- 对应mysql
- django.db.models.TextField
- 对应mysql
longtext
类型 - 命名不严谨,不推荐使用
- 对应mysql
- libs.django.db.models.LongTextField
- 对应mysql
longtext
类型, - 为django.db.models.TextField的别名,命名更严谨
- 超大文本存储考虑使用
- 对应mysql
- django_jsonfield_backport.models.JSONField
- 三方模块
- 对应mysql
json
类型 - 不推荐使用
jsonfield
三方模块- migrate 创建的类型为mysql
longtext
- migrate 创建的类型为mysql
- 外键字段
- django.db.models.OneToOneField
- 一对一外键(源表唯一键)使用
- db层面会自动添加唯一索引
- 必须加 db_constraint=False,避免建立数据库层面的外键约束
- on_delete由于实际场景为逻辑删除,不会出现delete,不做要求
- django.db.models.ForeignKey
- 多对一外键使用
- db层面会自动添加二级索引
- 必须加 db_constraint=False,避免建立数据库层面的外键约束
- on_delete由于实际场景为逻辑删除,不会出现delete,不做要求
django.db.models.ManyToManyField- 自动生成中间表,不可控,禁止使用
- 推荐自定义中间表,指定
django.db.models.ForeignKey
- django.db.models.OneToOneField
- primary_key:
bool
- 指定db层面主键
- verbose_name:
str
- django层面的字段注释,不会应用到mysql comment
- max_length:
int
- 字符串类型指定db层面存储字符个数上限,django层面也会做校验
- null:
bool
- 指定db层面字段可否为
NULL
,django层面也会做校验 - 通常情况,推荐设置null=False
- 大字段类型,统一设置null=True
- 指定db层面字段可否为
- default:
Union[Any, Callable]
- 仅django层面,在模型实例化时触发
- 默认值,可以传入函数,模型定义时会调用
__call__
方法
- unique:
bool
- db层面添加唯一索引,索引名称不可控
- db_index:
bool
- db层面添加二级索引,索引名称不可控
- auto_now_add:
bool
- 日期类型,新增模型,model_obj.save() 时触发
- 区别与 default=datetime.datetime.now,触发时机不同
- auto_now:
bool
- 日期类型,model_obj.save() 时触发
- QuerySet.update() 时是直接执行SQL,不涉及模型的变化,不会触发,需要手动指定时间
- choices:
Sequence[Tuple[Any, Any]]
- 每个元组的第一个值为数据库中实际存储的值,第二个值为显示内容
- 定义后可以使用
model_obj.get_字段名_display()
获取显示内容
- DecimalField字段属性
- max_digits:
int
- 表示数值的总个数(小数部分+整数部分)
- decimal_places:
int
- 表示小数部分的数值个数
- max_digits:
- 外键字段属性
- to:
Union[Model, str]
- 关联的模型
- to_field:
Optional[str] = None
- 关联模型的字段,默认使用主键
- on_delete:
Callable
- 依赖字段数据删除(delete)时触发的操作,由于实际业务场景都是逻辑删除(update),所以不做特殊要求
- models.CASCADE
- 级联删除
- models.PROTECT
- 抛出异常,不可删除
- models.DO_NOTHING
- 无操作
- models.SET_NULL
- 与之关联的值设置为null,必须null=True
- db_constraint:
bool = True
- 是否创建mysql层面的外键,必须设置为False
- to:
- db_table:
str
- 对应db层面的表名
- verbose_name:
str
- django层面的表注释,不会应用到mysql层面的表注释
- unique_together:
Sequence[str]
- mysql层面会创建联合唯一索引,推荐使用元组
- index_together:
Sequence[str]
- mysql层面会创建联合二级索引,推荐使用元组
# app_xxx/models.py
# coding: utf-8
import datetime
from django.db import models
from django_jsonfield_backport.models import JSONField
from libs.django.db.models import FixedCharField, BinaryCharFiled, TinyIntField, TextField, LongTextField
from libs.django.db.enum import TypeFieldEnum, first_value_unique
from libs.uuid import make_uuid
__all__ = (
'Xxx',
'XxxDetail',
'Yyy'
)
class Xxx(models.Model):
class Meta:
# 实际表名以 `t_` 为前缀
db_table = 't_xxx'
# ORM表注释
verbose_name = 'xxx表'
# 联合索引,仅展示功能,无实际意义
index_together = ('xxx_type', 'xxx_name')
@first_value_unique
class XxxTypeEnum(TypeFieldEnum):
"""xxx类型枚举类"""
TYPE_ONE = (1, '类型1')
TYPE_TWO = (2, '类型2')
TYPE_THREE = (3, '类型3')
# 主键自增id,要求使用bigint且业务无关
id = models.BigAutoField(primary_key=True)
# xxx_id使用uuid,固定32位长度,唯一键
xxx_id = FixedCharField(verbose_name='xxxid', max_length=32, null=False, default=make_uuid, unique=True)
# 类型字段建议使用tinyint,针对choices扩展出枚举类,方便管理
xxx_type = TinyIntField(verbose_name='xxx类型', null=False, default=XxxTypeEnum.TYPE_ONE.val,
choices=XxxTypeEnum.to_tuple())
# varchar类型统一定义成 not null default ''
xxx_name = models.CharField(verbose_name='xxx名称', max_length=50, null=False, default='')
# varchar binary,区分大小写
xxx_name_bin = BinaryCharFiled(verbose_name='xxx名称(区分大小写)', max_length=50, null=False, default='')
# 每个表必加的三个字段
# 是否已删除,使用ORM布尔类型,对应mysql的tinyint
is_deleted = models.BooleanField(verbose_name='是否已删除', null=False, default=0)
# 模型创建时自动更新, model_obj.save()
create_time = models.DateTimeField(verbose_name='创建时间', null=False, auto_now_add=True)
# 模型修改时自动更新, model_obj.save(),注意:直接update时不触发
update_time = models.DateTimeField(verbose_name='更新时间', null=False, auto_now=True)
"""生成的表结构
CREATE TABLE `t_xxx` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`xxx_id` char(32) NOT NULL,
`xxx_type` tinyint(4) NOT NULL,
`xxx_name` varchar(50) NOT NULL,
`xxx_name_bin` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`is_deleted` tinyint(1) NOT NULL,
`create_time` datetime(6) NOT NULL,
`update_time` datetime(6) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `xxx_id` (`xxx_id`),
KEY `t_xxx_xxx_type_xxx_name_1318ffcb_idx` (`xxx_type`,`xxx_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
"""
class XxxDetail(models.Model):
"""xxx子表(详情表)
有些情况,需要将大字段从源表中垂直拆分出来,形成一个子表,以减少查询I/O
"""
class Meta:
db_table = 't_xxx_detail'
verbose_name = 'xxx详情表'
id = models.BigAutoField(primary_key=True)
'''一对一外键(源表唯一键)使用 `models.OneToOneField`
外键字段名到mysql中会自动加上 `_id`,这里实际字段名为 `xxx_id`
外键 to='Xxx' 一律使用源表模型类的字符串形式,不允许出现 to=Xxx,避免出现定义先后顺序不同导致报错
必须加 db_constraint=False,避免建立数据库层面的外键约束
on_delete由于实际场景为逻辑删除,不会出现delete,不做要求
'''
xxx = models.OneToOneField('Xxx', to_field='xxx_id', verbose_name='xxxid', on_delete=models.CASCADE, db_constraint=False)
'''大字段类型
django自带的TextField为mysql的longtext,这里使用真实的TextField
大字段类型统一设置 null=True
'''
content = TextField(verbose_name='内容', null=True)
long_content = LongTextField(verbose_name='长内容', null=True)
name_list = JSONField(verbose_name='名称列表', null=True)
"""生成的表结构
CREATE TABLE `t_xxx_detail` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` text,
`long_content` longtext,
`name_list` json DEFAULT NULL,
`xxx_id` char(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `xxx_id` (`xxx_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""
class Yyy(models.Model):
class Meta:
db_table = 't_yyy'
verbose_name = 'yyy表'
id = models.BigAutoField(primary_key=True)
yyy_id = FixedCharField(verbose_name='yyyid', max_length=32, null=False, default=make_uuid, unique=True)
'''多对一外键使用 `models.ForeignKey`
外键 to='Xxx' 一律使用源表模型类的字符串形式,不允许出现 to=Xxx,避免出现定义先后顺序不同导致报错
必须加 db_constraint=False,避免建立数据库层面的外键约束
on_delete由于实际场景为逻辑删除,不会出现delete,不做要求
null值的定义:
True: 关联查询默认使用 INNER JOIN
False: 关联查询默认使用 LEFT JOIN
'''
xxx = models.ForeignKey('Xxx', to_field='xxx_id', verbose_name='xxxid', null=False, on_delete=models.CASCADE, db_constraint=False)
is_deleted = models.BooleanField(verbose_name='是否已删除', null=False, default=0)
create_time = models.DateTimeField(verbose_name='创建时间', null=False, auto_now_add=True)
update_time = models.DateTimeField(verbose_name='更新时间', null=False, auto_now=True)
"""生成的表结构
CREATE TABLE `t_yyy` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`yyy_id` char(32) NOT NULL,
`is_deleted` tinyint(1) NOT NULL,
`create_time` datetime(6) NOT NULL,
`update_time` datetime(6) NOT NULL,
`xxx_id` char(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `yyy_id` (`yyy_id`),
KEY `t_yyy_xxx_id_ef705c68` (`xxx_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""
- udp:
User Define Property
from django.utils.functional import cached_property
class Member(models.Model):
id = models.BigAutoField(primary_key=True)
...
class RelDeptMember(models.Model):
id = models.BigAutoField(primary_key=True)
member = models.ForeignKey("Member", verbose_name="成员id", null=False, on_delete=models.CASCADE, db_constraint=False)
dept = models.ForeignKey("Dept", verbose_name="部门id", null=False, on_delete=models.CASCADE, db_constraint=False,
related_name="rel_rdm_dept")
is_deleted = models.BooleanField(verbose_name='是否已删除', null=False, default=0)
create_time = models.DateTimeField(verbose_name='创建时间', null=False, auto_now_add=True)
update_time = models.DateTimeField(verbose_name='更新时间', null=False, auto_now=True)
class Dept(models.Model):
id = models.BigAutoField(primary_key=True)
...
@cached_property
def udp_member_num(self):
"""成员数"""
return self.rel_rdm_dept.filter(is_deleted=0).count()
from django.utils.functional import cached_property
- cached_property 是
property
的超集,当get
时,会缓存数据,避免多次重复执行 - 示例见
7