I'm trying to create a simple Django app that allows users to create and edit Recipes which consist of Ingredients.
models.py
class Recipe(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
class Ingredient(models.Model):
api_id = models.IntegerField(primary_key=True) # id in Spoonacular database
name = models.CharField(max_length=100) # human-readable name
units = models.CharField(max_length=10) # unit of measure (e.g. 'oz', 'lb'), includes '' for 'number of'
amt = models.PositiveIntegerField() # number of units
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
I have a detail view that displays the Ingredients for a given Recipe, along with buttons to add and remove ingredients. The primary key of the recipe is in the URL.
view of detail page with add/remove ingredient buttons
I'm trying to get it so that when a user clicks on the 'Remove Ingredients' button, it redirects them to a form where they can select which Ingredients from the given Recipe they want to remove. This is the part I can't get to work. As best I can tell, this requires a ModelChoiceField with a queryset where the ForeignKey of the Ingredient matches the primary key of the Recipe ('recipe_pk'). It seems like I need to pass the primary key of the given Recipe into the Form via **kwargs in the constructor, and then have the Form select the appropriate Ingredients. However, this gives me an AttributeError
with the description 'tuple' object has no attribute 'get'
when I try to render the template. Here's a screenshot:
AttributeError
Is this the right way to go about creating such a form, and if so any idea where this is going wrong?
forms.py
# Trying to create a form where a user can select any number of Ingredients
# associated with the Model
class RemoveIngredientForm(forms.Form):
to_remove = forms.ModelChoiceField(queryset=Ingredient.objects.all())
def __init__(self, *args, **kwargs):
super(RemoveIngredientForm, self).__init__(args, kwargs)
recipe = Recipe.objects.get(pk=kwargs['recipe_pk'])
self.fields['to_remove'].queryset = Ingredient.objects.filter(recipe=recipe)
views.py
...
def remove_ingredients(request, **kwargs):
"""
GET: list of existing ingredients
POST: remove selected ingreident from model
"""
if request.method == 'POST':
form = RemoveIngredientForm(request.POST, kwargs=kwargs)
if form.is_valid():
picked = form.cleaned_data.get('picked')
return HttpResponse('How did I get here?') # Haven't gotten to this point yet
recipe = Recipe.objects.get(pk=kwargs['recipe_pk']) #'recipe_pk' is the primary key of the Recipe and lives in the URL
try:
ingredients = Ingredient.objects.filter(recipe=recipe)
except Ingredient.DoesNotExist:
ingredients = None
form = RemoveIngredientForm(**kwargs)
context = {
'recipe': recipe,
'form': form
}
return render(request, 'food/remove_ingredients.html', context=context)
<!-- food/remove_ingredients.html -->
<h1>Removing ingredients from {{recipe.name}}</h1>
<form method='POST'>
{{ form.as_p }}
<input type='submit' value='submit'>
</form>
CodePudding user response:
The choicefield won't work because it needs a dictionary of choices. What you want to do is display a listview of ingredients for a particular recipe with a delete button next to each ingredient which calls a deleteview on that particular ingredient. So you call your deleteingredients url with the pk of the recipe. Then in your function you pass the queryset of the ingredients to that recipe pk into the context. Then in your template render each ingredient with a button that calls delete on that particular ingredient with the pk of that ingredient. So no form is needed at all. You just need another view which deletes the ingredient and then redirects to the recipe.