当前位置: 首页 > 后端技术 > Python

在Django中使用MySQL的枚举(ENUM)类型,而不是使用VARCHAR来模拟

时间:2023-03-25 22:07:13 Python

Django3.0已经默认支持枚举类型。使用的方法是在模型中添加一个元组或内部类来表示枚举Options。参考官方文档和文末我的代码。在CharField或IntegerField的构造函数中添加一个choices参数,并指定表示枚举的元组常量或内部类成员。但是这种方法有一个缺陷,就是前端只实现了枚举类型,但是在代码层面,无法避免存储枚举选项以外的数据。在数据库(例如mysql)级别,枚举类型模型仍然只是一个VARCHAR()。如果要在Django中使用Mysql枚举(ENUM)类型,需要安装一个DjangoMysql扩展,名为django-mysql项目地址。安装参考官方文档。安装方法是:python-mpipinstalldjango-mysql很简单,就像安装其他python第三方包一样。然后需要在setting.py中的INSTALLED_APPS栏填写django_mysql,注意代码中使用下划线而不是横杠INSTALLED_APPS=(...'django_mysql',#注意代码中使用下划线而不是横杠)假设我们创建一个用户模型,用户的状态status是一个枚举类型,可以这样实现。测试环境为linux、python3.8.6、django3.1.7。与前面的方法类似,您需要创建一个包含枚举选项元组的常量STATUS_LIST。然后使用django_mysql.models模块提供的EnumField在构造函数的choices参数中传入元组常量。元组常量每个选项的第一项是存储在数据库中的数据形式,后面一项是显示在前端的人类可读形式(humanreadable)fromdjango.dbimportmodelsimportdjango_mysql.modelsclassUser0(models.型号):username=models.CharField(max_length=20,verbose_name='username')STATUS_LIST=(('A','normal'),('B','banned'),('C','loggedout''),)status=django_mysql.models.EnumField(choices=STATUS_LIST)def__str__(self):returnself.username迁移后查看数据库的类型,是真正的MySQLENUM类型,不是VARCHAR模拟的。+------------+--------------------+-|领域|类型|+------------+------------------+-|编号|整数||用户名|变种(20)||状态|枚举('A','B','C')|+------------+--------------------+-访问Django的管理页面,可以在前端看到可读的表单勾选optiondatabasetable可以看到数据已经存储了+-----+---------+-----+|编号|用户名|状态|+----+----------+--------+|1|伊兹都|B|+----+---------+-------+如果你试图在代码级别存储非法数据In[6]:fromtemp_test.modelsimport*In[7]:u0=User0()In[8]:u0.status='X'In[9]:u0.save()会导致MySQL数据错误DataError:(1265,"Datatruncatedforcolumn'status'atrow1")注意事项官方文档中有警告,可以在迁移过程中将新值附加到选项中,或者编辑现有选项的人类可读名称。但是,如果启用了MySQL严格模式,则编辑或删除现有的选择值将出错,如果未启用,将被替换为空字符串。此外,空字符串在ENUM上的行为很奇怪,有点像NULL,但又不完全是。因此建议您启用严格模式。在实践中我发现,如果先使用Django默认的枚举实现,再修改为使用django-mysql扩展实现,迁移时会出错。我的解决方案是删除表并重建。先删除数据库中model对应的表,然后把models.py中的model注释掉,然后执行pythonmanage.pymakemigrationspythonmanage.pymigrate--fake然后取消注释,使用django-mysql实现功能,执行以下命令重新迁移pythonmanage.pymakemigrationspythonmanage.pymigrate其他枚举方法实现参考在Django默认的枚举实现中,官方文档推荐第二种方法。如果需要在多个模型中使用相同的枚举字段,可以将元组常量放在全局,或者将内部类转换为与模型同级的类。fromdjango.dbimportmodels#通过创建枚举元组常量类User1(models.Model):username=models.CharField(max_length=20,verbose_name='username')STATUS_LIST=(('A','normal'),('B','Banned'),('C','Canceled'),)status=models.CharField(max_length=1,verbose_name='status',choices=STATUS_LIST)def__str__(self):返回自我。username#通过创建一个内部类classUser2(models.Model):username=models.CharField(max_length=20,verbose_name='username')classStatusList(models.TextChoices):ACTIVITY='A','normal'BANNED='B','banned'CLOSE='C','loggedout'status=models.CharField(max_length=1,verbose_name='status',choices=StatusList.choices)def__str__(self):returnself.username正常用法举例:In[1]:fromtemp_test.modelsimport*In[2]:u1=User1()In[3]:u1.username='yizdu'In[4]:u1.status='A'In[5]:u1.save()In[6]:u2=User2()In[8]:u2.username='yizdu'In[9]:u2.status=User2.StatusList.活动[10]:u2。保存()mysql>SELECT*FROMtemp_test_user1;+----+--------+--------+|编号|用户名|状态|+----+------------+--------+|2|伊兹都|A|+----+--------+--------+setTime中的2行:0.022smysql>SELECT*FROMtemp_test_user2;+----+---------+--------+|编号|用户名|状态|+----+------------+--------+|1|伊兹都|A|+----+--------+------+查看表中的数据类型,status字段由VARCHAR()存储#DESCtemp_test_user1;+----------+-------------+|领域|输入|+------------+------------+|编号|整数||用户名|变种字符(20)||状态|varchar(1)|+----------+------------+#DESCtemp_test_user2;+----------+------------+|领域|输入|+------------+------------+|编号|整数||用户名|可变字符(20)||状态|varchar(1)|+----------+------------+尝试存储枚举选项以外的数据,不就会有任何错误当然数据也会存储在数据库中In[14]:u1.status='X'In[15]:u1.save()In[16]:u2.status='X'In[17]:u2。修改save()后,Django后台管理页面会这样显示。不用担心用户名和上面的不一样,图片是以前的。