You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以更符合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 13:35:22