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

使用CreateView与ManyToMany字段创建食材时字段类型错误求助

问题描述

使用jQuery插件Selectize.js实现了一个允许用户创建食材的表单,但提交表单时出现错误:Field 'id' expected a number but got 'b'——系统把食材名称的每个字符当成了需要匹配数据库的条目ID,而非整个单词。相关代码如下:

表单代码(forms.py)

class EatForm(forms.ModelForm):

    notes = forms.CharField(widget=forms.Textarea(attrs={"rows":3}),required=False)
    ingredient = forms.CharField(widget=forms.TextInput(),required=False)
    allergen = forms.CharField(widget=forms.TextInput(),required=False)

    class Meta:
        model = Eat
        fields = ["name", "notes", "meal","ingredient","allergen","favorite","datetime"]

模型代码

Eat模型

class Eat(models.Model):

    MEAL_CHOICES = (
        ("breakfast", "Breakfast"),
        ("lunch", "Lunch"),
        ("dinner", "Dinner"),
        ("supper", "Supper"),
        ("snack", "Snack"),
        ("drink", "Drink"),
    )

    id = models.BigAutoField(primary_key=True, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
    notes = models.CharField(max_length=255, blank=True, null=True)
    meal = models.CharField(choices=MEAL_CHOICES, max_length=20)
    ingredient = models.ManyToManyField(Ingredient,blank=True)
    allergen = models.ManyToManyField(Allergen,blank=True)
    favorite = models.BooleanField(default=False)
    datetime = models.DateTimeField()

Ingredient模型

class Ingredient(models.Model):
    id = models.BigAutoField(primary_key=True, editable=False)
    name = models.CharField(max_length=255, unique=True)

    def __str__(self):
        return self.name

视图代码(views.py)

class EatFormAddView(LoginRequiredMixin, CreateView):
    model = Eat
    template_name = "dashboard.html"
    success_url = reverse_lazy("core:activity")
    form_class = EatForm

    def form_valid(self, form):

        form.instance.user = self.request.user
        
        ingredients = form.cleaned_data['ingredient'].split(',')
        
        for ingredient in ingredients:
            try:
                ingredient_obj = Ingredient.objects.get(name=ingredient)
            except:
                ingredient_obj = Ingredient(name=ingredient)
                ingredient_obj.save()
        
        return super().form_valid(form)

错误原因
  1. 表单里的ingredient用了CharField,但模型中是ManyToManyField——Django默认会期望这个字段接收关联模型的ID列表,而非字符串。
  2. 虽然你在form_valid里做了字符串分割处理,但调用父类的form_valid时,原始的字符串值会被Django尝试解析为ManyToMany的关联ID,导致逐个字符被当成ID处理,触发类型错误。
  3. Selectize.js的配置可能没跟上,提交的格式不符合Django对ManyToMany字段的预期。

解决方案

1. 修改表单字段类型与验证逻辑

把表单中的ingredient和allergen替换为ModelMultipleChoiceField,并自定义验证逻辑支持创建新食材:

# forms.py
from django import forms
from .models import Eat, Ingredient, Allergen

class EatForm(forms.ModelForm):
    notes = forms.CharField(widget=forms.Textarea(attrs={"rows":3}), required=False)
    
    # 替换CharField为ModelMultipleChoiceField,指定用name字段匹配
    ingredient = forms.ModelMultipleChoiceField(
        queryset=Ingredient.objects.all(),
        required=False,
        widget=forms.TextInput(),
        to_field_name='name'
    )
    
    allergen = forms.ModelMultipleChoiceField(
        queryset=Allergen.objects.all(),
        required=False,
        widget=forms.TextInput(),
        to_field_name='name'
    )

    class Meta:
        model = Eat
        fields = ["name", "notes", "meal","ingredient","allergen","favorite","datetime"]

    # 重写clean方法处理新食材创建
    def clean_ingredient(self):
        # 获取提交的所有食材名称(可能是多个)
        raw_ingredients = self.data.getlist('ingredient')
        ingredient_objects = []
        
        for name in raw_ingredients:
            name = name.strip()
            if not name:
                continue
            # 自动创建或获取已有食材
            obj, created = Ingredient.objects.get_or_create(name=name)
            ingredient_objects.append(obj)
        
        return ingredient_objects
    
    # 同理处理allergen字段
    def clean_allergen(self):
        raw_allergens = self.data.getlist('allergen')
        allergen_objects = []
        
        for name in raw_allergens:
            name = name.strip()
            if not name:
                continue
            obj, created = Allergen.objects.get_or_create(name=name)
            allergen_objects.append(obj)
        
        return allergen_objects

2. 简化视图逻辑

移除视图中手动处理食材的代码,因为表单的clean方法已经完成了创建和获取:

# views.py
class EatFormAddView(LoginRequiredMixin, CreateView):
    model = Eat
    template_name = "dashboard.html"
    success_url = reverse_lazy("core:activity")
    form_class = EatForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        # 无需再手动处理ingredient/allergen,表单已处理完成
        return super().form_valid(form)

3. 配置Selectize.js提交格式

确保Selectize.js提交的是食材名称列表,而非单个字符串:

$(document).ready(function() {
    // 配置ingredient字段的Selectize
    $('#id_ingredient').selectize({
        plugins: ['remove_button'],
        delimiter: ',',  // 设置逗号为分隔符,匹配表单处理逻辑
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            };
        },
        valueField: 'name',  // 提交食材名称而非ID
        labelField: 'name',
        searchField: 'name'
    });

    // 配置allergen字段的Selectize
    $('#id_allergen').selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            };
        },
        valueField: 'name',
        labelField: 'name',
        searchField: 'name'
    });
});

内容的提问来源于stack exchange,提问作者bigredthelogger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:35:50