is52027c lab 20: blueprints
you can use the code provided in lab-20/step-0 as the starter code for this lab.
the exercise is to break out the code using blueprints.
don't forget, you need to replace the value in vs_url_for.py with the ID number of your virtual server: mine is 253 so my file contains
URL_PREFIX = '/usr/253'
in this lab we will learn how flask enables modular applications through blueprints.
there is a good description in the flask documentation: http://flask.pocoo.org/docs/0.12/blueprints/
we'll start from the pre-sqlalchemy code i.e. the version of the app we got to at the end of lab 17. blueprints also work fine with sqlalchemy but we'll implement them like this first.
we will apply blueprints to the mytwits app; this isn't strictly necessary as it is only a small app, but the same methods can be applied to much larger applications.
blueprints are also useful as reusable components.
in this lab we'll
- separate our twit operations into a twits_blueprint (dealing with a database connection issue in the process)
- separate our login operations into a login_blueprint (dealing with an app state issue in the process)
- show how to include templates with the login_blueprint, making it more portable (the template folder structure is a bit non-intuitive)
step 1: twits_blueprint
put the twit views in to a blueprint
-
put the routes + views for /, /add_twit, /edit_twit and /delete_twit in to a separate file called twits_blueprint.py
-
import all the modules needed for those views at the start of twits_blueprint.py
-
import the Blueprint module
from flask import Blueprint
-
use Blueprint to define the file as a blueprint called 'twits_blueprint' - see the 'My First Blueprint' example in http://flask.pocoo.org/docs/0.12/blueprints/
twits_blueprint = Blueprint('twits_blueprint', __name__, template_folder = 'templates')
-
change the routes to @twit_blueprint routes e.g.
@twits_blueprint.route('/add_twit', methods = ['GET', 'POST'])
-
register the blueprint in the main mytwits file - see 'Registering Blueprints' in http://flask.pocoo.org/docs/0.12/blueprints/
database problem
-
you will hit a problem here; the database class is instantiated in the main app file i.e.
db = DBHelper()
-
but you need it in blueprint; on the other hand if you instantiate it in the blueprint it won't be available in the main file
-
the solution is to instantiate the db class in a separate file and import it in to both the blueprint and the main app
-
as we already define the DBHelper class in a separate file, we can simply instantiate the app at the end of that,
db = DBHelper()
-
then import into twits_blueprint.py and mytwits_mysql.py
from dbhelper import db
-
check that the app works OK
step 2: login_blueprint
-
move the login functions to their own file called login_blueprint.py
-
as before make sure you import the modules you will need
-
use Blueprint to define the file as a blueprint called 'login_blueprint' - see the 'My First Blueprint' example in http://flask.pocoo.org/docs/0.12/blueprints/
-
change the routes to @login_blueprint routes
-
register the blueprint in the main mytwits file - see 'Registering Blueprints' in http://flask.pocoo.org/docs/0.12/blueprints/
app state
-
the problem we hit here is that we instantiate our login_manager in logins_blueprint
login_manager = LoginManager()
-
but we need it to be initiated after the main app is instantiated i.e.
app = Flask(__name__) login_manager.init_app(app)
-
the way to get round this is to make use of the record_once method of the Blueprint Object http://flask.pocoo.org/docs/0.12/api/#blueprint-objects
-
this registers a function that will be called the first time a blueprint is registered on the application. the function is called with the state as the argument http://flask.pocoo.org/docs/0.12/api/#flask.blueprints.BlueprintSetupState
-
this provides a reference to the current app through flask.blueprints.BlueprintSetupState.app
-
the long and short of it is that the following code in the login_blueprint.py file means that the login manager will be correctly initialised against the app when the blueprint is registered:
@login_blueprint.record_once def on_load(state): login_manager.init_app(state.app)
-
this means you no longer need to initialise login_manager in your main app, so delete the line
login_manager.init_app(app)
-
check that your application now works OK
-
your main app should now consist mainly of the flask app itself, your blueprints and the if ___name__ == '__main__' clause
step-3 (extension)
it's possible to include templates with blueprints, and they will get added to the search path for the app's templates (see http://flask.pocoo.org/docs/0.12/blueprints/#templates).
this means you can parcel up the blueprint in a way that is more self-contained. it can exist in its own folder, with its own templates. this makes it more easy to add to a new project, if you need the same set of functionalities.
however, the way this needs to be done is very specific and could be a bit confusing at first sight. rather than simply having a templates folder inside your blueprints folder, you need to add a subfolder inside that. this is to ensure the blueprint templates don't get overwritten by the app templates (as blueprint templates are essentially 'pulled' in to the main blueprints folder by jinja behind the scenes).
so if we moved the login bloeprint in to its own login folder, and we wanted to include the login.html template with it, we need a templates/login folder inside the login blueprint folder, and we need to refer to the template as login/login.html i.e.
render_template('login/login.html',form=form)
the folder structure is:
/login
login_blueprint.py
__init__.py
/templates
/login
note that i have included an (empty) __init__.py file in the login directory; this is so the login blueprint directory is considered by python to be a package and i can import the login blueprint from it. i will use the following line at the start of my main app to import it:
from login.login_blueprint import login_blueprint
so the recipe for packaging up the login blueprint along with its template is:
-
make a login dir
-
move login_blueprint.py in to it
-
create a blank init file in the directory so python recognises it as a package
touch login/__init__.py
-
create a templates directory inside the login directory
-
create a login directory inside the templates directory(!)
-
move the login.html template in to the login/templates/login directory
-
in the login blueprint, change the reference to that template to login/login.html
step 4: homework
find out how to use templates with the sqlalchemy version of the code. this is actually the more common version shown in online examples, as most people who are at the level of using blueprints will also be using sqlalchemy with flask.
that's all folks