Generating Controllers (flask make:controller)¶
A simple controller¶
Use flask make:controller to scaffold a controller class under
app/controllers/ and register it in app/controllers/__init__.py.
flask make:controller RecipeController
This creates app/controllers/recipe_controller.py with a class stub:
class RecipeController:
pass
In fact, if you are following this tutorial from the beginning, you should receive a warning that this controller already exists. It does, and it’s much better than a simple class with a pass. Question: So why would we use the make:controller command? One answer: You might create a new set of routes that you need a plain controller for the logic. But I love flags so let’s see some real scaffolding come into play.
A Controller with RESTful actions (–crud)¶
Life is all about the options and --crud is a life saving option. This
option produces injects the seven RESTful actions, into the controller file,
and the routes, plus it wires everything up with matching templates.
For our cooking website suppose we need to have an ‘ingredient’ object. Here is one way to wire up a ton of content ready to use right away with the following command:
flask make:controller IngredientController --crud
With the --crud flag you receive:
A controller file under
app/controllers/ingredient_controller.pywith all seven RESTful methods ready for you.The controller file registered propertly in
app/controllers/__init__.py.A blueprint routes file under
app/routes/ingredientsThe routes file contains all seven RESTful routes.
The new blueprint registered with the create_app in
app/__init__.pyFour templates under
app/templates/ingredients(index,show,create,edit).
Here is what the controller file looks like:
from flask import render_template
from flask import redirect, url_for
class IngredientController:
@staticmethod
def index() -> str:
return render_template('ingredients/index.html')
@staticmethod
def show(ingredient_id: int) -> str:
return render_template('ingredients/show.html')
@staticmethod
def create() -> str:
return render_template('ingredients/create.html')
@staticmethod
def store() -> str:
return redirect(url_for('ingredients.index'))
@staticmethod
def edit(ingredient_id: int) -> str:
return render_template('ingredients/edit.html')
@staticmethod
def update(ingredient_id: int) -> str:
return redirect(url_for('ingredients.index'))
@staticmethod
def destroy(ingredient_id: int) -> str:
return redirect(url_for('ingredients.index'))
Here is what the routes file looks like:
from app.controllers import IngredientController
from app.routes.ingredients import bp
@bp.route('/ingredients', methods=['GET'])
def index():
return IngredientController.index()
@bp.route('/ingredients/<int:ingredient_id>', methods=['GET'])
def show(ingredient_id: int):
return IngredientController.show(ingredient_id)
@bp.route('/ingredients/create', methods=['GET'])
def create():
return IngredientController.create()
@bp.route('/ingredients', methods=['POST'])
def store():
return IngredientController.store()
@bp.route('/ingredients/<int:ingredient_id>/edit', methods=['GET'])
def edit(ingredient_id: int):
return IngredientController.edit(ingredient_id)
@bp.route('/ingredients/<int:ingredient_id>', methods=['POST'])
def update(ingredient_id: int):
return IngredientController.update(ingredient_id)
@bp.route('/ingredients/<int:ingredient_id>/delete', methods=['POST'])
def destroy(ingredient_id: int):
return IngredientController.destroy(ingredient_id)
This is awesome!!! I can litterly see you jumping up and down shouting with celebration in joy 🥳. But wait, what about an Ingrediant model?
A controller with a Model (-m / –model)¶
Often a controller is tied to a model, like in your ingredient example. You
have two options here you can either use an optional variable --model
or have Flask-Commands infer your model with a generator flag -m. The
following two commands are equivlant
flask make:controller IngredientController --model Ingredient
and
flask make:controller IngredientController -m
both will sub out a plain Ingredient controller class and an Ingrediant model.
Here is what the controller file looks like:
class IngredientController:
pass
Here is what the model file looks like:
from app import db
from datetime import datetime, timezone
class Ingredient(db.Model):
__tablename__ = 'ingredients'
# Columns
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime(timezone=True),
index=True,
default=lambda: datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc))
def store_in_database(self):
db.session.add(self)
db.session.commit()
def delete_from_database(self):
db.session.delete(self)
db.session.commit()
def __repr__(self):
"""Model representation for Code Debugging"""
return f'<Ingredient id:{self.id}>'
Ok now you are all set to combine these optional variables and create nested datastructures, right 🤔? What did I hear you say, you want all the RESTful actions nested over multiple models!!!! Ya, of course you want to connect a recipe to it’s ingrediants with RESTful actions.
Nested Controllers with –crud¶
If we wanted to build the relationship Recipe → Ingredient Flask-Commands is up for the task. In this case we would PascalCase case (UppserCamelCase) the controller and then add the crud flag as in this command:
flask make:controller RecipeIngredientController --crud
With this small change you recieve a lot of structure benefits in your code.
- A controller file under
app/controllers/recipe_ingredient_controller.pywith all seven RESTful methods ready for you. The controller file registered propertly in
app/controllers/__init__.py.
- A controller file under
- A blueprint routes folder under
app/routes/recipes/ingredients The routes file contains all seven RESTful routes.
The new ingredients blueprint registered in the recipes blueprint
- A blueprint routes folder under
Four templates under
app/templates/recipes/ingredients(index,show,create,edit).
Here is what the controller file looks like:
from flask import render_template
from flask import redirect, url_for
class RecipeIngredientController:
@staticmethod
def index(recipe_id: int) -> str:
return render_template('recipes/ingredients/index.html')
@staticmethod
def show(recipe_id: int, ingredient_id: int) -> str:
return render_template('recipes/ingredients/show.html')
@staticmethod
def create(recipe_id: int) -> str:
return render_template('recipes/ingredients/create.html')
@staticmethod
def store(recipe_id: int) -> str:
return redirect(url_for('recipes.ingredients.index', recipe_id=recipe_id))
@staticmethod
def edit(recipe_id: int, ingredient_id: int) -> str:
return render_template('recipes/ingredients/edit.html')
@staticmethod
def update(recipe_id: int, ingredient_id: int) -> str:
return redirect(url_for('recipes.ingredients.index', recipe_id=recipe_id))
@staticmethod
def destroy(recipe_id: int, ingredient_id: int) -> str:
return redirect(url_for('recipes.ingredients.index', recipe_id=recipe_id))
Here is what the routes file looks like:
from app.controllers import RecipeIngredientController
from app.routes.recipes.ingredients import bp
@bp.route('/recipes/<int:recipe_id>/ingredients', methods=['GET'])
def index(recipe_id: int):
return RecipeIngredientController.index(recipe_id)
@bp.route('/recipes/<int:recipe_id>/ingredients/<int:ingredient_id>', methods=['GET'])
def show(recipe_id: int, ingredient_id: int):
return RecipeIngredientController.show(recipe_id, ingredient_id)
@bp.route('/recipes/<int:recipe_id>/ingredients/create', methods=['GET'])
def create(recipe_id: int):
return RecipeIngredientController.create(recipe_id)
@bp.route('/recipes/<int:recipe_id>/ingredients', methods=['POST'])
def store(recipe_id: int):
return RecipeIngredientController.store(recipe_id)
@bp.route('/recipes/<int:recipe_id>/ingredients/<int:ingredient_id>/edit', methods=['GET'])
def edit(recipe_id: int, ingredient_id: int):
return RecipeIngredientController.edit(recipe_id, ingredient_id)
@bp.route('/recipes/<int:recipe_id>/ingredients/<int:ingredient_id>', methods=['POST'])
def update(recipe_id: int, ingredient_id: int):
return RecipeIngredientController.update(recipe_id, ingredient_id)
@bp.route('/recipes/<int:recipe_id>/ingredients/<int:ingredient_id>/delete', methods=['POST'])
def destroy(recipe_id: int, ingredient_id: int):
return RecipeIngredientController.destroy(recipe_id, ingredient_id)
If you are missing all the recipe_id’s it’s because you are missing the recipe model. From the top you would do
flask make:controller RecipeController --crud -m
Notice the -m generator flag to create the Recipe Model, and then you would follow it with the command from above (probably with a model generator flag)
flask make:controller RecipeIngredientController --crud -m
Wrap-up¶
That is the controller story in brief: start simple, add --crud when you
want the full set of actions, and use -m when you want a matching model
scaffolded alongside the controller. The next command, flask make:model,
will cover the rest of the story and make those relationships flag generators feel
feel deliberate rather than accidental.