19 minutes read

We've discussed how to perform routing with decorators. Let's expand this and study a different approach to this task. You can also classes to restructure your code and utilize several object-oriented programming concepts.

MethodView class

First thing first, we need to import a new class. It determines the methods such as GET, POST, PUT and so on.

from flask.views import MethodView

In general, we need to:

  1. Create a class inherited from the MethodView class. It handles requests in a much more convenient way. This class also represents a view function, a Python function that takes a web request and returns a web response;

  2. Then specify actions for each HTTP method inside it;

  3. Convert the class into a view function;

  4. Bound view function to the corresponding route.

The pros of using the class-based views (CBV) approach based is:

  1. Flexibility. Easier code adaptation for other templates and models. Classes have inheritance, so why not use it?

  2. Code organization. HTTP methods are divided and it's easier to check whether the method is present or not. It also helps to avoid templates;

  3. Reuse your code. You can adapt view functions to bound them to other routes.

Hands down, this might be a bit hard to understand, so let's dive into the little task. Consider an example of uploading, retrieving, and deleting animal names from the site.

Basic routing

First, create some kind of a basic prototype. Look at the two code snippets below:

First one:

from flask import Flask
from flask.views import MethodView

app = Flask('main')

class Animal(MethodView):
    def get(self):
        return 'Soon there will be a list of animals'

app.add_url_rule('/animals', view_func = Animal.as_view('animal'))

app.run()

Second one:

from flask import Flask, request

app = Flask('main')

@app.route('/animals'):
def main(): # 1
    if request.method == 'GET':
        return 'Soon there will be a list of animals'

app.run()

These two code snippets are similar. When we access /animals, we send an empty GET request to this URL. Then this request is passed to the view function, which returns Soon there will be a list of animals. Let's see which parts of the "old" routing correspond to the new one based on classes.

Classic way

Class-based way

Advantages

Defining routes and methods. Binding it to the view function.

@app.route('/animal'): def main():

app.add_url_rule('/animal', view_func = Animal.as_view('animal'))

Better code organization. We can divide code into parts, one part where we specify all view functions, and other parts with binding them to routes, which is impossible in routing via decorators because a view function must be declared after defining a route

Defining a VF and specifying a response on each HTTP method

def main(): if request.method == 'GET' :

class Animal(MethodView): def get(self):

MethodView checks the HTTP method by itself

Let's complete the first part of our code. Create the global animals variable to store all the animals that come our way.

from flask import Flask, request, redirect
from flask.views import MethodView

app = Flask('main')

animals = []

class Animal(MethodView): 
    # retrieving all animals and displaying a form to upload new ones
	def get(self): 
        # Assembling a string consisting of the all saved animal names
		all_animals = ''
		for animal_name in animals:
			all_animals += (animal_name+ ' ')

        # This form sends POST request with name of the animal to the '/animals'
		template = """
		<form method='POST'>
		<h1> All animals: {} <h1>
		<input type='text' name = 'animal_name' placeholder='New animal name'>
		<input type='submit' value='Add'>
		</form>
		"""
		return template.format(all_animals) 
 

	def post(self):
        # retrieving string from request and putting it in the animals list
		animals.append(request.form.get('animal_name')) 
        # Redirection is covered in separate topic, so consider this is for instant "switching" the page
		return redirect('/animals') 

app.add_url_rule('/animals', view_func = Animal.as_view('animal'))

app.run()

Note that the string you pass to the as_view() method is the name of the endpoint that the view function will then have.

Variables

Let's build a route where we can pass a variable (an animal name to get extended information) in the HTTP request. We will need to adjust the previous code a bit. First, let's change the animals variable type from list to dict, so it can store additional information about animals. Second, let's add another input line to the initial template.

Finally, we will create the additional view function AnimalDetails and register a separate route for it. The new route takes one more parameter for animal_name and returns a value from the animals dictionary if the requested key exists.

animals = {}

class Animal(MethodView):
    def get(self):
        all_animals = ""
        for animal_name in animals.keys():
            all_animals += animal_name + " "
        template = """
		<form method='POST'>
		<h1> All animals: {} <h1>
		<input type='text' name = 'animal_name' placeholder='New animal name'>
        <input type='text' name = 'animal_info' placeholder='Information about the animal'>
		<input type='submit' value='Add'>
		</form>
		"""
        return template.format(all_animals)

    def post(self):
        animal_name = request.form.get("animal_name")
        animal_info = request.form.get("animal_info")
        animals[animal_name] = animal_info
        return redirect("/animals")

class AnimalDetails(MethodView):
    # note that now get function requires one more parameter
    def get(self, animal_name):
        return animals[animal_name]

app.add_url_rule(
    "/animals",
    view_func=Animal.as_view("animal")
)
# here we specify route with variable
app.add_url_rule(
    "/animals/<animal_name>", view_func=AnimalDetails.as_view("animal_detail"), methods=["GET"]
)

Utilizing CBV approach

This is an example of utilizing the class-based routing application architecture. We can expand our Animal class to a higher level of abstraction. Now, we need to specify the name of the category and its basic contents. How do we implement this? When we are using the as_view() method inherited from MethodView, we define a new class instance. So, we can add the __init__ method inside our class to modify instance attributes. This code is not much different from the previous snippets. We've added the __init__ method, and all variable names have been changed to more abstract ones.

class MyClass(MethodView):
    def __init__(self, basic_list, category_name):
        self.basic_list = basic_list
        self.category_name = category_name

    # retrieving all self.basic_list and displaying a form to upload new one's
    def get(self):
        # Assembling a string consisting of the all saved animal names
        all_elements = ""
        for category_element_name in self.basic_list:
            all_elements += category_element_name + " "
        # this form sends POST request to the route with name of the animal
        template = """
		<form method='POST'>
		<h1> {} <h1>
		<input type='text' name = 'category_element_name' placeholder='New {} name...'>
		<input type='submit' value='Add'>
		</form>
		"""
        return template.format(all_elements, self.category_name)

    def post(self):
        # retrieving string from request
        self.basic_list.append(request.form.get("category_element_name"))
        # redirection will be covered later, for now consider this for instant "reloading" of page
        return redirect("/{}".format(self.category_name))


endpoint_name = "animal"
category_name = "animals"
basic_category_contents = ["cat", "dog", "tiger"]

# This is a place where we create new class instance, so we have to specify
# more parameters in as_view() method
new_view = MyClass.as_view(endpoint_name, basic_category_contents, category_name)


app.add_url_rule(
    "/{}".format(category_name),
    view_func=new_view
)

Conclusion

Well done, now you are familiar with a completely different approach to routing! Important steps are:

  • Define a class;

  • Inherit it from MethodView class;

  • Define the response for each type of HTTP request and;

  • Convert this class into a view function and bind it to the route. Don't forget to specify available methods for the route.

8 learners liked this piece of theory. 4 didn't like it. What about you?
Report a typo