Creating routes and navigations
Routing is a mapping of URL pattern(s) and other related details to a view function that’s done using Flask’s route decorators. On the other hand, the view function is a transaction that processes an incoming request from the clients and, at the same time, returns the necessary response to them. It follows a life cycle and returns an HTTP status as part of its response.
There are different approaches to assigning URL patterns to view functions. These include creating static and dynamic URL patterns, mapping URLs externally, and mapping multiple URLs to a view function.
Creating static URLs
Flask has several built-in route decorators that implement some of its components, and @route
decorator is one of these. @route
directly maps the URL address to the view function seamlessly. For instance, @route
maps the index()
view function presented in the project’s main.py
file to the root URL or /
, which makes index()
the view function of the root URL.
But @route
can map any valid URL pattern to any view function. A URL pattern is accepted if it follows the following best practices:
- All characters must be in lowercase.
- Use only forward slashes to establish site hierarchy.
- URL names must be concise, clear, and within the business context.
- Avoid spaces and special symbols and characters as much as possible.
The following home()
view function renders an introductory page of our ch01
application and uses the URL pattern of /home
for its access:
@app.route('/home') def home(): return ''' <html><head><title>Online Personal … System</title> </head><body> <h1>Online … Counseling System (OPCS)</h1> <p>This is a template of a web-based counseling application where counselors can … … …</em> </body></html> '''
Now, Flask accepts simple URLs such as /home
or complex ones with slashes and path-like hierarchy, including these multiple URLs.
Assigning multiple URLs
A view function can have a stack of @route
decorators annotated on it. Flask allows us to map these valid multiple URLs if there is no conflict with other view functions and within that stack of @route
mappings. The following version of the home()
view function now has three URLs, which means any of these addresses can render the home page:
@app.route('/home') @app.route('/information') @app.route('/introduction') def home(): return '''<html><head> <title>Online Personal … System</title> </head><body> <h1>Online … Counseling System (OPCS)</h1> … … … … … </body></html> '''
Aside from complex URLs, Flask is also capable of creating dynamic routes.
Applying path variables
Adding path variables makes a URL dynamic and changeable depending on the variations of the values passed to it. Although some SEO experts may disagree with having dynamic URLs, the Flask framework can allow view functions with changeable URL patterns to be implemented.
In Flask, a path variable is declared inside a diamond operator (<>
) and placed within the URL path. The following view function has a dynamic URL with several path variables:
@app.route('/exam/passers/list/<float:rate>/<uuid:docId>') def report_exam_passers(rating:float, docId:uuid4 = None): exams = list_passing_scores(rating) response = make_response( render_template('exam/list_exam_passers.html', exams=exams, docId=docId), 200) return response
As we can see, path variables are identified with data types inside the diamond operator (<>
) using the <type:variable>
pattern. These parameters are set to None
if the path variables are optional. The path variable is considered a string type by default if it has no associated type hint. Flask 3.x offers these built-in data types for path variables:
- string: Allows all valid characters except for slashes.
- int: Takes integer values.
- float: Accepts real numbers.
- uuid: Takes unique 32 hexadecimal digits that are used to identify or represent records, documents, hardware gadgets, software licenses, and other information.
- path: Fetches characters, including slashes.
These path variables can’t function without the corresponding parameters of the same name and type declared in the view function’s parameter list. In the previous report_exam_passers()
view function, the local rating
and docId
parameters are the variables that will hold the values of the path variables, respectively.
But there are particular or rare cases where path variables should be of a type different than the supported ones. View functions with path variables declared as list
, set
, date
, or time
will throw Status Code 500
in Flask. As a workaround, the Werkzeug bundle of libraries offers a BaseConverter
utility class that can help customize a variable type for paths that allows other types to be part of the type hints. The following view function requires a date
type hint to generate a certificate in HTML format:
@app.route('/certificate/accomp/<string:name>/ <string:course>/<date:accomplished_date>') def show_certification(name:str, course:str, accomplished_date:date): certificate = """<html><head> <title>Certificate of Accomplishment</title> </head><body> <h1>Certificate of Accomplishment</h1> <p>The participant {} is, hereby awarded this certificate of accomplishment, in {} course on {} date for passing all exams. He/she proved to be ready for any of his/her future endeavors.</em> </body></html> """.format(name, course, accomplished_date) return certificate, 200
accomplished_date
in show_certification()
is a date
hint type and will not be valid until the following tasks are implemented:
- First, subclass
BaseConverter
from thewerkzeug.routing
module. In the/converter
package of this project, there is a module calleddate_converter.py
that implements ourdate
hint type, as shown in the following code:from werkzeug.routing import BaseConverter from datetime import datetime class DateConverter(BaseConverter): def to_python(self, value): date_value = datetime.strptime(value, "%Y-%m-%d") return date_value
The given
DateConverter
will custom-handle date variables within our Flask application. BaseConverter
has ato_python()
method that must be overridden to implement the necessary conversion process. In the case ofDateConverter
, we needstrptime()
so that we can convert the path variable value in theyyyy-mm-dd
format into the datetime type.- Lastly, declare our new custom converter in the Flask instance of the
main.py
module. The following snippet registersDateConverter
toapp
:app = Flask(__name__) app.url_map.converters['date'] = DateConverter
After following all these steps, the custom path variable type – for instance, date
– can now be utilized across the application.
Assigning URLs externally
There is also a way to implement a routing mechanism without using the @route
decorator, and that’s by utilizing Flask’s add_url_rule()
method to register views. This approach binds a valid request handler to a unique URL pattern for every call to add_url_rule()
of the app
instance in the main.py
module, not in the handler’s module scripts, thus making this approach an external way of building routes. The following arguments are needed by the add_url_rule()
method to perform mapping:
- The URL pattern with or without the path variables.
- The URL name and, usually, the exact name of the view function.
- The view function itself.
The invocation of this method must be in the main.py
file, anywhere after its @route
implementations and view imports. The following main.py
snippet shows the external route mapping of the show_honor_dismissal()
view function to its dynamic URL pattern. This view function generates a termination letter for the counseling and consultation agreement between a clinic and a patient:
app = Flask(__name__) def show_honor_dissmisal(counselor:str, effective_date:date, patient:str): letter = """ … … … … … </head><body> <h1> Termination of Consultation </h1> <p>From: {} <p>Head, Counselor <p>Date: {} <p>To: {} <p>Subject: Termination of consultation <p>Dear {}, … … … … … … <p>Yours Sincerely, <p>{} </body> </html> """.format(counselor, effective_date, patient, patient, counselor) return letter, 200 app.add_url_rule('/certificate/terminate/<string:counselor>/<date:effective_date>/<string:patient>', 'show_honor_dissmisal', views.certificates.show_honor_dissmisal)
Binding URL mappings to views using add_url_rule()
is not only confined to the decorated function views but is also necessary for class-based views.
Implementing class-based views
Another way to create the view layer is through Flask’s class-based view approach. Unlike the Django framework, which uses mixin programming to implement its class-based views, Flask provides two API classes, namely View
and MethodView
, that can directly subclass any custom view implementations.
The most common and generic class to implement HTTP GET
operations is the View
class from the flask.views
module. It has a dispatch_request()
method that executes the request-response transactions like a typical view function. Thus, subclasses must override this core method to implement their view transactions. The following class, ListUnpaidContractView
, renders a list of patients with payments due to the clinic:
from flask.views import View class ListUnpaidContractView(View): def dispatch_request(self): contracts = select_all_unpaid_patient() return render_template("contract/ list_patient_contract.html", contracts=contracts)
select_all_unpaid_patient()
will provide the patient records from the database. All these records will be rendered to the list_patient_contract.html
template. Now, aside from overriding the dispatch_request()
method, ListUnpaidContractView
also inherits all the attributes and helper methods from the View
class, including the as_view()
static method, which creates a view name for the view. During view registration, this view name will serve as the view_func
name of the custom View
class in the add_url_rule()
method with its mapped URL pattern. The following main.py
snippet shows how to register ListUnpaidContractView
:
app.add_url_rule('/contract/unpaid/patients', view_func=ListUnpaidContractView.as_view('list-unpaid-view'))
If a View
subclass needs an HTTP POST
transaction, it has a built-class class attribute called methods
that accepts a list of HTTP methods the class needs to support. Without it, the default is the [ "GET" ]
value. Here is another custom View
class of our Online Personal Counselling System app that deletes existing patient contracts of the clinic:
class DeleteContractByPIDView(View): methods = ['GET', 'POST'] … … … … … … def dispatch_request(self): if request.method == "GET": pids = list_pid() return render_template("contract/ delete_patient_contract.html", pids=pids) else: pid = int(request.form['pid']) result = delete_patient_contract_pid(pid) if result == False: pids = list_pid() return render_template("contract/ delete_patient_contract.html", pids=pids) contracts = select_all_patient_contract() return render_template("contract/ list_patient_contract.html", contracts=contracts)
DeleteContractByPIDView
handles a typical form-handling transaction, which has both a GET
operation for loading the form page and a POST
operation to manage the submitted form data. The POST
operation will verify if the patient ID submitted by the form page exists, and it will eventually delete the contract(s) of the patient using the patient ID and render an updated list of contracts.
Other than the View
class, an alternative API that can also build view transactions is the MethodView
class. This class is suitable for web forms since it has the built-in GET
and POST
hints or templates that subclasses need to define but without the need to identify the GET
transactions from POST
, like in a view function. Here is a view that uses MethodView
to manage the contracts of the patients in the clinic:
from flask.views import MethodView class ContractView(MethodView): … … … … … … def get(self): return render_template("contract/ add_patient_contract.html") def post(self): pid = request.form['pid'] approver = request.form['approver'] … … … … … … result = insert_patient_contract(pid=int(pid), approved_by=approver, approved_date=approved_date, hcp=hcp, payment_mode=payment_mode, amount_paid=float(amount_paid), amount_due=float(amount_due)) if result == False: return render_template("contract/ add_patient_contract.html") contracts = select_all_patient_contract() return render_template("contract/ list_patient_contract.html", contracts=contracts)
The MethodView
class does not have a methods
class variable to indicate the HTTP methods supported by the view. Instead, the subclass can select the appropriate HTTP hints from MethodView
, which will then implement the required HTTP transactions of the custom view class.
Since MethodView
is a subclass of the View
class, it also has an as_view()
class method that creates a view_func
name of the view. This is also necessary for add_url_rule()
registration.
Aside from GET
and POST
, the MethodView
class also provides the PUT
, PATCH
, and DELETE
method hints for API-based applications. MethodView
is better than the View
API because it organizes the transactions according to HTTP methods and checks and executes these HTTP methods by itself at runtime. In general, between the decorated view function and the class-based ones, the latter approach provides a complete Flask view component because of the attributes and built-in methods inherited by the view implementation from these API classes. Although the decorated view function can support a flexible and open-ended strategy for scalable applications, it cannot provide an organized base functionality that can supply baseline view features to other related views, unlike in a class-based approach. However, the choice still depends on the scope and requirements of the application.
Now that we’ve created and registered the routes, let’s scrutinize these view implementations and identify the essential Flask components that compose them.