如何以更符合DRY原则优化Django饮品配方模型?
重构Django饮品配方模型的DRY方案
你的现有模型通过重复定义10组配料字段实现功能,完全违反了DRY原则,维护和扩展都很麻烦。下面是最合理的重构方案:
核心思路:拆分独立配料模型
把配方和配料拆分为两个关联模型,通过外键建立一对多关系,同时保留“最多10种配料”的限制。
重构后的模型代码
from django.core.exceptions import ValidationError from django.db import models class Recipe(models.Model): drink_name = models.CharField(max_length=250, blank=True, null=True, verbose_name='饮品名称') drink_story = models.TextField(verbose_name='饮品故事') drink_picture = ResizedImageField(upload_to='drinks/', null=True, blank=True, verbose_name='饮品图片') def __str__(self): return self.drink_name or "未命名饮品" def clean(self): # 验证每个配方最多10种配料 if self.ingredients.count() > 10: raise ValidationError("每个配方最多只能添加10种配料") class Ingredient(models.Model): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredients', verbose_name='所属配方') name = models.CharField(max_length=250, verbose_name='配料名称') amount = models.DecimalField(max_digits=5, decimal_places=1, verbose_name='用量') def __str__(self): return f"{self.name}: {self.amount}"
为什么这是更好的选择
- 彻底解决重复代码:所有配料的字段定义统一在
Ingredient模型里,后续修改字段(比如调整名称长度、用量精度)只需改一处 - 灵活调整限制:如果以后要改成最多15种配料,只需要修改
clean方法里的数字,不用新增一堆字段 - 数据库更规范:符合关系型数据库的设计范式,避免冗余数据
- 查询更方便:用
recipe.ingredients.all()就能直接拿到该配方的所有配料,不用再逐个判断10个字段是否有值
旧数据迁移提示
如果你的数据库里已有旧数据,需要写迁移脚本把原来的ingr_nameN和ingr_amountN字段数据转到新的Ingredient表中,示例逻辑如下(需替换成你的应用名称):
# 迁移脚本中的forward和backward函数示例 def forwards(apps, schema_editor): Recipe = apps.get_model('你的应用名', 'Recipe') Ingredient = apps.get_model('你的应用名', 'Ingredient') for recipe in Recipe.objects.all(): # 遍历10组旧字段 for i in range(1, 11): name = getattr(recipe, f'ingr_name{i}') amount = getattr(recipe, f'ingr_amount{i}') # 只迁移有值的配料 if name and amount is not None: Ingredient.objects.create(recipe=recipe, name=name, amount=amount) def backwards(apps, schema_editor): # 回滚逻辑:把Ingredient数据写回旧字段 Recipe = apps.get_model('你的应用名', 'Recipe') Ingredient = apps.get_model('你的应用名', 'Ingredient') for recipe in Recipe.objects.all(): ingredients = list(recipe.ingredients.all())[:10] for i in range(1, 11): if i-1 < len(ingredients): setattr(recipe, f'ingr_name{i}', ingredients[i-1].name) setattr(recipe, f'ingr_amount{i}', ingredients[i-1].amount) else: setattr(recipe, f'ingr_name{i}', None) setattr(recipe, f'ingr_amount{i}', None) recipe.save()
内容的提问来源于stack exchange,提问作者Thomas Doll-Datema
相关产品推荐
相关产品推荐

