如何减少Django模型重复代码并优化?继承报错求解决方案
Django重复模型类的代码优化方案
问题场景
存在多个结构高度相似的Django模型类,重复代码较多,示例如下:
class One(models.Model): CHOICES = ( ('Text1', 'Text1'), ('Text2', 'Text2'), ('Text3', 'Text3'), ) name = models.CharField( max_length=50, verbose_name='Name1', choices=CHOICES, unique=True ) class Meta: ordering = ('id',) verbose_name = 'Name' verbose_name_plural = 'Names' def __str__(self): return self.name class Two(models.Model): CHOICES = ( ('Text4', 'Text4'), ('Text5', 'Text5'), ) name = models.CharField( max_length=50, verbose_name='Name1', choices=CHOICES, unique=True ) class Meta: ordering = ('id',) verbose_name = 'Name' verbose_name_plural = 'Names' def __str__(self): return self.name
需要优化以减少代码冗余。
尝试的继承方案及报错
尝试通过继承普通models.Model基类实现复用,但执行迁移时报错:
错误信息:你正尝试为模型one添加一个不可为空的字段'choices_ptr',但未设置默认值;我们无法完成此操作(数据库需要填充已有行的字段值)。
尝试的代码如下:
class Choices(models.Model): CHOICES = (('.', '.'),) name = models.CharField( max_length=50, verbose_name='Name', choices=CHOICES, unique=True, ) class Meta: ordering = ('id',) def __str__(self): return self.name class One(Choices): CHOICES = ( ('Text1', 'Text1'), ('Text2', 'Text2'), ('Text3', 'Text3'), ) class Meta: verbose_name = 'Name' verbose_name_plural = 'Names' class Two(Choices): CHOICES = ( ('Text4', 'Text4'), ('Text5', 'Text5'), ) class Meta: verbose_name = 'Name' verbose_name_plural = 'Names'
报错原因
普通的Django模型继承属于多表继承,Django会为父类创建独立的数据库表,子类表会自动添加一个指向父类的外键字段(如choices_ptr)。如果子类对应的数据库表已存在(之前已执行过迁移),新增父类继承时,数据库需要为已有行填充这个外键字段的值,但未设置默认值,因此触发错误。
正确优化方案:使用抽象基类
将基类标记为抽象基类(在Meta中设置abstract=True),Django不会为抽象基类创建数据库表,仅复用其字段、方法和元数据,不会生成额外的外键字段,完美解决代码冗余问题。
修改后的代码如下:
class ChoicesBase(models.Model): name = models.CharField( max_length=50, verbose_name='Name', unique=True, ) class Meta: abstract = True # 标记为抽象基类,不生成数据库表 ordering = ('id',) def __str__(self): return self.name class One(ChoicesBase): CHOICES = ( ('Text1', 'Text1'), ('Text2', 'Text2'), ('Text3', 'Text3'), ) # 重写name字段,仅修改choices和verbose_name参数 name = models.CharField( max_length=50, verbose_name='Name1', choices=CHOICES, unique=True, ) class Meta(ChoicesBase.Meta): # 继承父类Meta的ordering,仅添加自定义的verbose_name verbose_name = 'Name' verbose_name_plural = 'Names' class Two(ChoicesBase): CHOICES = ( ('Text4', 'Text4'), ('Text5', 'Text5'), ) name = models.CharField( max_length=50, verbose_name='Name1', choices=CHOICES, unique=True, ) class Meta(ChoicesBase.Meta): verbose_name = 'Name' verbose_name_plural = 'Names'
进一步优化(可选)
如果verbose_name等元数据也存在重复,可以使用类工厂函数动态生成模型类,示例如下:
def create_choice_model(choices, verbose_name, verbose_name_plural): class ChoiceModel(ChoicesBase): CHOICES = choices name = models.CharField( max_length=50, verbose_name='Name1', choices=CHOICES, unique=True, ) class Meta(ChoicesBase.Meta): verbose_name = verbose_name verbose_name_plural = verbose_name_plural # 必须设置db_table,避免动态生成的模型类表名冲突 db_table = f'app_{verbose_name.lower()}' return ChoiceModel # 生成模型类 One = create_choice_model( choices=(('Text1', 'Text1'), ('Text2', 'Text2'), ('Text3', 'Text3')), verbose_name='Name', verbose_name_plural='Names' ) Two = create_choice_model( choices=(('Text4', 'Text4'), ('Text5', 'Text5')), verbose_name='Name', verbose_name_plural='Names' )
注意:动态生成模型类需要确保db_table唯一,避免数据库表名冲突。
内容的提问来源于stack exchange,提问作者Inozem
相关产品推荐
相关产品推荐

