I've a form to create a cocktail recipe. It has some common fields like name, description and other, and also a list of ingredients. An ingredient has an ID (foreign-key for a table of ingredients), as the value of a select tag, a name and a quantity numeric field, as user input. At first, in the HTML there is only one field for an ingredient; if the user want to add another ingredient to the recipe, a button 'Add ingredient' create another field, just after the previous one.
How can I manage this kind of form with WTForms and Flask-WTF, in a Python/Flask backend? My problem is: how can I say in the form class "there is this list that must have at least one item (an ingredient), and no maximum" so that I can properly insert the recipe into database?
maybe create a 3rd table?
Table 1 (Cocktail Table):
cocktail_id
cocktail_name
Table 2 (Ingredients Table):
cocktail_id
ingredients_name
Table 3 (Cocktail Ingredients Table):
cocktail_ingredients_id
cocktail_id
ingredients_id
quantity
Then for a given cocktail, search table 3 for all results of cocktail_id.
You can inner join the other tables to get the ingredient name or cocktail name when needed.
When uploading to database, you would just need to create a new entry for each cocktail ingredient you're saving to the cocktail ingredients table; with the cocktail_id, ingredients_id, and the quantity.
EDIT 1: There are many things you could do, for example when you are creating your form in your route, you could send to it your cocktail_id, and then when you're creating your form, you'll search for all instances of cocktail_id in your Table 3; and create a new (or set of) field(s) for each result. And the moment the user pressing 'add ingredient', you could send them
def createForm(cocktail_id):
class FormClass(FlaskForm):
# put your non dynamic fields here
pass
# add in your fields that are dependent on how many ingredients
post_variables_list = []
# find all table 3 results for given cocktail
table3_objects = Table3.query.filter_by(id=cocktail_id).all()
for table3_object in table3_objects:
setattr(FormClass, f'ingredientname_{table3_object.table3_id}', StringField('Ingredient Name', default=table3_object.ingredient_name))
post_variables_list.append(f'ingredientname_{table3_object.table3_id}')
return FormClass(), post_variables_list
@app.route('/example_route/<int:cocktail_id>')
def example_route(cocktail_id):
form, post_variables_list = createForm(cocktail_id)
if request.method=='POST':
submit_dictionary = {}
submit_dictionary = form.data
for current_variable in post_variables_list:
fieldname, table3_id = current_variable.split('_')
# find existing object, that you can create by redirecting to a different route
# which just creates a new table 3 entry, which is associated w/ this cocktail each time you
# press add ingredient and then redirects you to here
table3_object = db.session.query(Table3).filter(Table3.id==table3_id).first()
if fieldname = 'name':
table3_object.name = submit_dictionary[current_variable]
# could have other conditions here for other fields
db.session.add(table3_object)
db.session.commit()
# route which just creates a new empty ingredient
@app.route('create_new_empty_ingredient/int:cocktail_id')
def create_new_empty_ingredient(cocktail_id):
# create new entry in table 3
table3_object = Table3 (
cocktail_id = cocktail_id
)
db.session.add(table3_object)
db.session.commit()
# redirect back to example_route
return redirect(url_for('example_route', cocktail_id=cocktail_id))