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)