如何用Django Forms创建包含多个带数量的MealItem的Meal对象?
问题描述
我需要创建一个表单,用来创建包含多个MealItem子对象的Meal对象,每个MealItem都要能单独设置quantity参数。但目前的表单和视图组合只能选择一个子对象并设置一个数量,尝试过用前缀或修改表单,只能实现选择多个MealItem,没法给每个子对象单独设置数量。
现有代码
Models
class Meal(models.Model): name = models.CharField(max_length=200) user = models.ForeignKey(User, on_delete=models.CASCADE) image = models.ImageField(upload_to=meal_image_upload_to, blank=True, null=True) def calculate_total_macros(self): total_kcal = 0 total_carbohydrates = 0 total_fats = 0 total_proteins = 0 total_glycemic_load = 0 total_glycemic_index = 0 total_weight = 0 weighted_glycemic_index = 0 meal_items = self.mealitem_set.all() for meal_item in meal_items: quantity = meal_item.quantity food_item = meal_item.food_item total_weight += quantity # 食物营养数据按每100g计算 total_kcal += food_item.kcal * quantity/100 total_carbohydrates += food_item.carbohydrates * quantity/100 total_fats += food_item.fats * quantity/100 total_proteins += food_item.proteins * quantity/100 total_glycemic_load += food_item.glycemic_load * quantity/100 total_glycemic_index += food_item.glycemic_index weighted_glycemic_index += food_item.glycemic_index * quantity if total_weight != 0: total_kcal_per_100g = round((total_kcal / total_weight) * 100, 2) total_carbohydrates_per_100g = round((total_carbohydrates / total_weight) * 100, 2) total_fats_per_100g = round((total_fats / total_weight) * 100, 2) total_proteins_per_100g = round((total_proteins / total_weight) * 100, 2) total_glycemic_load_per_100g = round((total_glycemic_load / total_weight) * 100, 2) else: total_kcal_per_100g = 0 total_carbohydrates_per_100g = 0 total_fats_per_100g = 0 total_proteins_per_100g = 0 total_glycemic_load_per_100g = 0 average_glycemic_index = round(weighted_glycemic_index / total_weight, 2) return { 'total_kcal': total_kcal, 'total_carbohydrates': total_carbohydrates, 'total_fats': total_fats, 'total_proteins': total_proteins, 'total_glycemic_load': total_glycemic_load, 'total_kcal_per_100g': total_kcal_per_100g, 'total_carbohydrates_per_100g': total_carbohydrates_per_100g, 'total_fats_per_100g': total_fats_per_100g, 'total_proteins_per_100g': total_proteins_per_100g, 'total_glycemic_load_per_100g': total_glycemic_load_per_100g, 'average_glycemic_index': average_glycemic_index } def __str__(self): return self.name class MealItem(models.Model): meal = models.ForeignKey(Meal, on_delete=models.CASCADE) food_item = models.ForeignKey(FoodItem, on_delete=models.CASCADE) quantity = models.PositiveIntegerField(default=1) def __str__(self): return f"{self.food_item.name} - {self.meal.name}"
Forms
class MealItemForm(forms.ModelForm): class Meta: model = MealItem fields = ['food_item', 'quantity'] MealItemFormSet = forms.inlineformset_factory(Meal, MealItem, form=MealItemForm, extra=1, can_delete=True) class MealForm(forms.ModelForm): class Meta: model = Meal fields = ['name', 'image'] def __init__(self, user=None, *args, **kwargs): super(MealForm, self).__init__(*args, **kwargs) self.user = user def save(self, commit=True): meal = super(MealForm, self).save(commit=False) if self.user: meal.user = self.user if commit: meal.save() return meal
Views
def add_meal(request): if request.method == 'POST': form = MealForm(request.user, request.POST, request.FILES) formset = MealItemFormSet(request.POST, instance=Meal()) if form.is_valid() and formset.is_valid(): meal = form.save() formset.instance = meal formset.save() return redirect('meal_list') else: form = MealForm(user=request.user) formset = MealItemFormSet(instance=Meal()) return render(request, 'add_meal.html', {'form': form, 'formset': formset})
Template
<form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} {{ formset.management_form }} {% for form in formset %} {{ form.as_p }} {% endfor %} <input type="submit" value="Add Meal"> </form>
解决方法
你的代码已经用到了inlineformset_factory,核心问题出在视图的POST请求处理逻辑上,修改如下:
修正后的视图代码
def add_meal(request): if request.method == 'POST': form = MealForm(request.user, request.POST, request.FILES) # 先验证主表单有效性,再绑定临时Meal实例到表单集 if form.is_valid(): # 创建未保存到数据库的Meal临时实例 meal = form.save(commit=False) # 将临时实例传给表单集,确保关联关系正确 formset = MealItemFormSet(request.POST, instance=meal) if formset.is_valid(): # 先保存主Meal对象 meal.save() # 再保存所有MealItem子对象 formset.save() return redirect('meal_list') # 表单无效时,重新初始化表单集(保留用户输入) else: formset = MealItemFormSet(request.POST, instance=Meal()) else: form = MealForm(user=request.user) formset = MealItemFormSet(instance=Meal()) return render(request, 'add_meal.html', {'form': form, 'formset': formset})
额外优化:默认显示多个子表单
如果想默认显示多个空白的MealItem输入项,只需修改inlineformset_factory的extra参数,比如设置为3:
MealItemFormSet = forms.inlineformset_factory(Meal, MealItem, form=MealItemForm, extra=3, can_delete=True)
修改后,模板会默认渲染3组food_item选择框和quantity输入框,每组都可以单独设置值,提交后会自动创建多个关联到同一Meal的MealItem对象。
内容的提问来源于stack exchange,提问作者Urek
相关产品推荐
相关产品推荐

