loading...

Flask series part 11: Allowing a user to add recipes

brunooliveira profile image Bruno Oliveira ・5 min read

Introduction

In the previous installment of the series, we looked at adding authentication and supporting user registration in our application. It was mentioned that after this functionality was in place, we will be able to expand the functionalities of our app. This is what we will discuss now.

Allowing users to add recipes

The first thing we will be working on is to allow a user to add a recipe by himself to the database.
This functionality will be simple to implement at this point, after all, the implementation flow will be quite similar to previous things we have done so far: adding views in Jinja, connecting the server-side Flask code via REST API calls and writing the database models to store all the data.
However, the biggest caveat we will face during the implementation of this feature will be at database level:

We need to ensure that the recipes that each user will add, will be linked to his own account

This means, talking in relational terms: that there needs to be a one-to-many relationship between the User and the user entered recipes, which will be represented by the UserCreatedRecipes class.
Once this relationship is in place, we can then associate recipes with each user and, as a result, we will have a relational model that allows us to perform queries like:

  • give me all recipes of the currently logged in user;
  • give me all recipes from a user that don't have fish as an ingredient;
  • give me all recipes from a user that have less than 4 ingredients;

However, since this is simple to do via Pony once all the other logic is in place, we will simply create a dashboard-like view as a table that has all the user-entered recipes from a certain user, like a personal dashboard area.

Preparing the Jinja template

As usual, let's start by looking at the Jinja template for the form to enter a recipe:

<!DOCTYPE html>
<html lang="en">

{% extends "base_template.html" %}

{% block body %}
<body>
<h1>Add your own recipe</h1>
<form action="/userRecipe" method="post" novalidate>
    {{ form.hidden_tag() }}
    <p>
        {{ form.name.label }}<br>
        {{ form.name(size=32) }}
    </p>
    <p>
        {{ form.ingredients.label }}<br>
        {{ form.ingredients(size=32) }}
    </p>
    <p>
        {{ form.instructions.label }}<br>
        {{ form.instructions(size=32) }}
    </p>
    <p>{{ form.submit() }}</p>
</form>
</body>
{% endblock %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="{{url_for('static', filename = 'jquery.js')}}">\x3C/script>')</script>
</html>

As with all the previous forms, when we submit it, the data will be posted to the server to the endpoint mentioned in the action attribute of the form, in this case, /userRecipe. Once we have collected the user entered data, we will be able to map it to a Python class to represent it in our data model and then we can save it in the database. Let's now look at both the data model as well as the code for the endpoint.

Extending our data model and implementing the endpoint code

In order to save the user entered recipe to the database, we first need a Python class to represent a single user entered recipe. We will call it UserCreatedRecipe:

class UserCreatedRecipe(db.Entity):
    name = Required(str)
    ingredients = Required(str)
    instructions = Required(str)
    user = Required(User)

So, our UserCreatedRecipe is a database entity with a name, a comma-separated list of ingredients, a set of instructions as well as a reference to the user (the class User of our model) who created the recipe.

Obviously, the association needs to happen in both directions, so, our User class also needs to be extended to include a set (none, one, or potentially many, recipes):

class User(db.Entity, UserMixin):
    login = Required(str, unique=True)
    username = Required(str)
    password = Required(str)
    is_active = Required(bool)
    recipes = Set("UserCreatedRecipe")

This is the Pony syntax to create a one-to-many relationship, we need to ensure that both attributes "know eachother", with the correct cardinality: each UserCreatedRecipe will have a single reference to a User (done by adding it as an attribute of the class) and a User will be able to contain a reference to many recipes.

Let's look at the code that leverages all of this to implement the endpoint logic:

@db_session
@app.route('/userRecipe', methods=['GET', 'POST'])
def add_user_recipe():
    form = UserRecipeForm()
    if form.validate_on_submit():
        if request.method == 'POST':
            name = request.form['name']
            ingredients = request.form['ingredients']
            instructions = request.form['instructions']
            UserCreatedRecipe(name=name, ingredients=ingredients, instructions=instructions, user=current_user)
            commit()
        return redirect('/')
    else:
        return render_template('user_recipe.html', title='Add your own recipe', form=form)

The code for this endpoint is similar in structure to many other for endpoints that need to work with forms:

  • it supports get and post requests: get for the first time, when we want to simply present the Jinja template to the user, and post, for when we fill the form with data and want to pass that data to the endpoint;
  • it uses a @db_session annotation since we are working with entities that will be saved to a database;

When we receive the data from the form, we use it to create a new entity of type UserCreatedRecipe, where it's worth mentioning the usage of the current_user instance from flask-login to represent the currently logged in user in the session. This is how we are linking a recipe to the user who is creating it.
After the recipe is created and stored in the database, we redirect the user to the main page.

In order to complete the post, we need to see the implementation for the UserRecipeForm class:

class UserRecipeForm(FlaskForm):
    name = StringField('Recipe Name', validators=[DataRequired()])
    ingredients = StringField('Ingredients', validators=[DataRequired()])
    instructions = TextAreaField('Instructions', validators=[DataRequired()])
    submit = SubmitField('Submit')

As we can see, this is a Python class which represents the form that will be used to handle the recipe creation by a user.

As with most Flask ecosystem and its extensions, we first inherit from the FlaskForm superclass, and we create our own, more specialized form: it contains the fields that the user will need to input in order to create it's own recipe.

The data binding will occur together with the Jinja template, where the fields are references to the fields in the UserRecipeForm class, that will be responsible for passing the data to the back-end: that's how we retrieve the input values from the side of the flask endpoint.

With this in place, the whole flow for this feature is complete and the user can now input it's own recipes.

Conclusion

We learnt about how to create one-to-many entity relationships using the Pony ORM, and we saw how to leverage some of the existing Flask plugins to enhance the capabilities of our data model to allows to have recipes of users.
We saw how that enables us to do more interesting queries and how we can keep growing our application.

Stay tuned for more!

Discussion

pic
Editor guide