Allow users to create ingredients using a CreateView and ManyToMany field

I have a form where users can create ingredients using a jQuery Plugin for Custom Tags.

Submitting the form results in the error Field 'id' expected a number but got 'b'. where each letter in the ingredient (vs the word) is being picked up as an item that's expected to already exist in the database.

# form.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"]
# Models
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()
# View

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

    # get user
    # def get_object(self):
    #     return self.request.user

    # determine which submit button was pressed
    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)

I created a form field that wasn't part of the ModelForm, processed it, then associated it to the post.

# models.py

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

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

    def __str__(self):
        return self.name

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()

# form.py
class EatForm(forms.ModelForm):

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

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

# view.py

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

    # determine which submit button was pressed
    def form_valid(self, form):        

        ingredient_ids = []
        allergen_ids = []

        add_record = form.save(commit=False)
        
        # associate user to post
        add_record.user = self.request.user
        
        # get or create ingredients
        ingredients = form.cleaned_data['ingredient_names'].split(',')
        for ingredient in ingredients:
            record = Ingredient.objects.get_or_create(name=ingredient)
            ingredient_ids.append(record[0].id)

        # get or create allergens
        allergens = form.cleaned_data['allergen_names'].split(',')
        for allergen in allergens:
            record = Allergen.objects.get_or_create(name=allergen)
            allergen_ids.append(record[0].id)

        # save post
        add_record.save()
        
        # associate ingredients to saved post
        add_record.ingredient.set(ingredient_ids)
        
        # associate allergens to saved post
        add_record.allergen.set(allergen_ids)
        
        # save the post with ManyToMany populated
        add_record.save()
        
        return super().form_valid(form)
Back to Top