30. Practical Exercise: Version 12
In this chapter, we will write a web application following the MVC (Model-View-Controller) architecture. The application will be able to return responses in three formats: JSON, XML, and HTML. There is a significant increase in complexity between what we are about to do and what we have done previously. We will reuse most of the concepts covered so far and will detail all the steps leading to the final application.
30.1. MVC Architecture
We will implement the MVC (Model–View–Controller) architectural pattern as follows:

Processing a client request will proceed as follows:
- 1 - Request
The requested URLs will be in the form http://machine:port/action/param1/param2/… The [Main Controller] will use a configuration file to "route" the request to the correct controller. To do this, it will use the [action] field of the URL. The rest of the URL [param1/param2/…] consists of optional parameters that will be passed to the action. The "C" in MVC here refers to the chain [Main Controller, Controller / Action]. If no controller can handle the requested action, the web server will respond that the requested URL was not found.
- 2 - Processing
- The selected action [2a] can use the parameters that the [Main Controller] has passed to it. These may come from two sources:
- the path [/param1/param2/…] of the URL,
- from parameters posted in the body of the client’s request;
- When processing the user's request, the action may require the [business] layer [2b]. Once the client's request has been processed, it may trigger various responses. A classic example is:
- an error response if the request could not be processed correctly;
- a confirmation response otherwise;
- the [Controller / Action] will return its response [2c] to the main controller along with a status code. These status codes will uniquely represent the current state of the application. It will be either a success code or an error code;
- 3 - Response
- Depending on whether the client requested a JSON, XML, or HTML response, the [Main Controller] will instantiate [3a] the appropriate response type and instruct it to send the response to the client. The [Main Controller] will pass it both the response and the status code provided by the [Controller/Action] that was executed;
- if the desired response is of the JSON or XML type, the selected response will format the response from the [Controller/Action] that was provided to it and send it [3c]. The client capable of processing this response can be a Python console script or a JavaScript script embedded in an HTML page;
- if the desired response is of the HTML type, the selected response will select [3b] one of the HTML views [Vuei] using the status code provided to it. This is the V in MVC. A single view corresponds to a single state code. This view V will display the response from the [Controller / Action] that was executed. It wraps the data from this response in HTML, CSS, and JavaScript. This data is called the view model. This is the M in MVC. The client is most often a browser;
Now, let’s clarify the relationship between MVC web architecture and layered architecture. Depending on how the model is defined, these two concepts may or may not be related. Let’s consider a single-layer MVC web application:

In the example above, the [Controller / Action] each incorporate parts of the [business] and [DAO] layers. In the [web] layer, we do have an MVC architecture, but the application as a whole does not have a layered architecture. Here, there is only one layer—the web layer—which handles everything.
Now, let’s consider a multi-layer web architecture:

The [Web] layer can be implemented without following the MVC model. We then have a multi-layer architecture, but the Web layer does not implement the MVC model.
For example, in the .NET world, the [web] layer above can be implemented with ASP.NET MVC, resulting in a layered architecture with an MVC-style [web] layer. Having done this, we can replace this ASP.NET MVC layer with a classic ASP.NET layer (WebForms) while keeping the rest (business logic, DAO, driver) unchanged. We then have a layered architecture with a [web] layer that is no longer MVC-based.
In MVC, we said that the M model was that of the V view, i.e., the set of data displayed by the V view. Another definition of the M model in MVC is given:

Many authors consider that what lies to the right of the [web] layer forms the M model of MVC. To avoid ambiguity, we can refer to:
- the domain model when referring to everything to the right of the [web] layer;
- the view model when referring to the data displayed by a view V;
In what follows, when we refer to the model, we will always be referring to the view model.
30.2. Client/Server Application Architecture
The web application will have the following architecture:

- In [1], the web server will have two types of clients:
- in [2], a console client that will exchange JSON and XML with the server;
- in [3], a browser that will receive HTML from the server and display it;
- The web server [1] retains the [business] and [DAO] layers from previous versions;
- the web client [2] will be updated to account for the web application’s new service URLs;
- The HTML application displayed by the browser must be written from scratch;
We will develop the application in several phases:
- We will develop the JSON version of the server. We will test the server’s service URLs one by one using a Postman client. This method allows us to build the framework of the web server without worrying about the application’s views (=HTML);
- After testing the JSON server with Postman, we will test it with a console client;
- then we will move on to the XML version of the server. We have seen that switching from JSON to XML is straightforward;
- finally, we will move on to the HTML version of the server. We will build an MVC architecture and define the views to be displayed. The HTML application will be tested using both the Postman client and a standard browser;
30.3. The server code directory structure

- in [1: the web server as a whole;
- in [2]: for now, we will ignore the [static, templates, tests_views] folders, which pertain to the HTML version of the server. Outside of this folder, we will find the main script [main] and its configuration;
- in [3], the web server controllers. These will be class instances;
![]() | ![]() |
- in [4], the server’s HTTP response will be handled by classes;
- in [5], we retain the log file from the previous servers;
When we build the HTML version of the server, other folders will come into play:
![]() | ![]() |
- in [6], the static elements of the HTML application;
- in [7], the HTML application templates broken down into views [9] and view fragments [8];
- in [9], the classes implementing the view models;
30.4. The application’s service URLs
To build the web server, we will proceed as follows:
- Based on the views of the HTML application, we will define the actions that the web application must implement. We will use the actual views here, but these could simply be views on paper;
- Based on these actions, we will define the HTML application’s service URLs;
- we will implement these service URLs using a server that returns JSON. This allows us to define the framework of the web server without worrying about the HTML pages to be served. We will test these service URLs using Postman;
- We will then test our JSON server with a console client;
- Once the JSON server has been validated, we will move on to writing the HTML application;
The first view will be the authentication view:

- the action leading to this first view will be called [init-session] [1];
- Clicking the [Validate] button will trigger the [authenticate-user] action with two posted parameters [2-3];
The tax calculation view:

- In [1], the [authenticate-user] action that led to this view;
- at [2], clicking the [Validate] button triggers the execution of the [calculate-tax] action with three posted parameters [2-5];
- Clicking the link [6] triggers the action [list-simulations] without parameters;
- Clicking the link [7] triggers the [end-session] action without parameters;
The third view displays the simulations performed by the authenticated user:

- in [3], the action [list-simulations] that led to this view;
- in [2], clicking the [Delete] link triggers the [delete-simulation] action with a parameter: the number of the simulation to be deleted from the list;
- clicking the [3] link triggers the [display-tax-calculation] action without parameters, which re-displays the tax calculation view;
- Clicking the [4] link triggers the [end-session] action without parameters;
With this initial information, we can define the server’s various service URLs:
Action | Role | Execution Context |
/init-session | Used to set the type (json, xml, html) of the desired responses | GET request Can be sent at any time |
/authenticate-user | Authorizes or denies a user's login | POST request. The request must have two posted parameters [user, password] Can only be issued if the session type (json, xml, html) is known |
/calculate-tax | Performs a tax calculation simulation | POST request. The request must have three posted parameters [married, children, salary] Can only be issued if the session type (json, xml, html) is known and the user is authenticated |
/list-simulations | Request to view the list of simulations performed since the start of the session | GET request. Can only be issued if the session type (json, xml, html) is known and the user is authenticated |
/delete-simulation/number | Deletes a simulation from the list of simulations | GET request. Can only be issued if the session type (json, xml, html) is known and the user is authenticated |
/display-tax-calculation | Displays the HTML page for the tax calculation | GET request. Can only be issued if the session type (json, xml, html) is known and the user is authenticated |
/end-session | Ends the simulation session. | Technically, the old web session is deleted and a new session is created Can only be issued if the session type (json, xml, html) is known and the user is authenticated |
These various service URLs will be used for both the HTML server and the JSON or XML servers. Two URLs will be used exclusively for the latter two servers: these are the URLs from the previous version of the client/web server that we are reusing here:
Action | Role | Execution context |
/get-admindata | Returns tax data used to calculate the tax | GET request. Used only if the session type is json or xml. The user must be authenticated |
/calculate-taxes | Calculates the tax for a list of taxpayers posted in JSON | GET request. Used only if the session type is json or xml. The user must be authenticated |
All controllers associated with these actions will proceed in the same way:
- they will check their parameters. These are found in the object:
- [request.path] for parameters present in the URL in the form [/action/param1/param2/…];
- in the [request.form] object for those transmitted as [x-www-form-urlencoded] in the request body;
- in the [request.data] object for those transmitted as JSON in the request body;
- A controller is similar to a function or method that checks the validity of its parameters. For the controller, however, it’s a bit more complicated:
- the expected parameters may be missing;
- The parameters retrieved by the controller are strings. If the expected parameter is a number, then the controller must verify that the parameter’s string actually represents a number;
- Once verified that the expected parameters are present and syntactically correct, you must verify that they are valid in the current execution context. This context is present in the session. The authentication example is an example of an execution context. Certain actions should only be processed once the client has been authenticated. Generally, a key in the session indicates whether this authentication has taken place or not;
- once the previous checks have been completed, the secondary controller can proceed. This parameter verification process is very important. We cannot accept a client sending us arbitrary data at any point during the application’s lifecycle. We must maintain full control over the application’s lifecycle;
- Once its work is done, the secondary controller returns a dictionary with the keys [action, state, response] to the main controller that called it:
- [action] is the action that has just been executed;
- [state] is a three-digit number indicating the result of the action’s processing:
- [x00] indicates successful processing;
- [x01] indicates a processing failure;
- [response] is the dictionary of results in the form {‘response’:object}. The object will have different structures depending on the action being processed;
We will now review the various controllers—or, in other words, the different actions these controllers handle—that drive the web application’s workflow.
30.5. Server Configuration

The database configuration [config_database] and the server layer configuration [config_layers] are identical to those in previous versions. The [config] file now includes new information:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
- Up to line 41, we see standard elements;
- lines 43–66: by line 43, the server’s Python Path is defined. We can then import the project’s dependencies:
- lines 45–55: the list of controllers;
- lines 57–60: the list of HTTP responses;
- lines 62–66: the list of view templates;
- lines 68–189: the application configuration with a series of constants;
- lines 71–98: we are already familiar with these lines from previous versions;
- lines 101–122: the controller dictionary:
- the keys are the names of the actions;
- the values are an instance of the controller responsible for handling that action. Each controller is instantiated as a single instance (singleton). The same instance will be executed by different server threads. Therefore, care must be taken with shared data that each controller might want to modify;
- lines 125–129: the dictionary of the three possible HTTP responses:
- the keys are the type of response requested by the client (JSON, XML, HTML);
- the values are an instance of the HTTP response. Each response generator is instantiated as a single instance (singleton). The same generator will be executed by different server threads. Care must therefore be taken with shared data that each generator might want to modify;
- lines 132–186: configuration of HTML views. For now, we’ll ignore these lines;
- lines 191–202: we have already encountered these lines in previous versions;
30.6. The path of a client request within the server

We will follow the path of a client request arriving at the server through to the HTTP response sent back. It follows the MVC server’s flow.
30.6.1. The [main] script
The [main] script is identical in many ways to that of previous versions. We are nevertheless providing it in its entirety to ensure we start on the right foot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | |
- lines 1–92: all these lines have already been covered and explained;
- line 92: the server will manage a session. We therefore need a secret key. For each user, we will store two pieces of information in the session:
- whether the user has successfully authenticated;
- Every time it performs a tax calculation, the results of that calculation will be placed in a list called the user's simulation list. This list will be stored in the session;
- Lines 100–151: the list of the server’s service URLs. The associated functions act as a filter: any URLs not present in this list will be rejected by the Flask server with a [404 NOT FOUND] error. Once this filtering is complete, the request is systematically forwarded to a ‘Front Controller’ implemented by the [front_controller] function in lines 94–98, which we will discuss shortly;
- Lines 100–103: handling the [/] route. The entry point for the web application will be the URL on line 107. Therefore, on line 103, we redirect the client to this URL:
- The [url_for] function is imported on line 18. It has two parameters here:
- the first parameter is the name of one of the routing functions, in this case the one on line 107. We can see that this function expects a parameter [type_response], which is the response type (json, xml, html) requested by the client;
- the second parameter takes the name of the parameter from line 107, [type_response], and assigns a value to it. If there were other parameters, we would repeat the operation for each of them;
- it returns the URL associated with the function designated by the two parameters provided to it. Here, this will return the URL from line 106, where the parameter is replaced by its value [/init-session/html];
- The [redirect] function was imported on line 18. Its role is to send an HTTP redirect header to the client:
- the first parameter is the URL to which the client should be redirected;
- the second parameter is the status code of the HTTP response sent to the client. The code [status.HTTP_302_FOUND] corresponds to an HTTP redirect;
The [ front_controller] function in lines 94–98 performs the initial processing of the client’s request:
- Lines 1–57: We are familiar with this code. For example, this was the code for the function named [main] in the [main] script of the previous version. One thing to note is the controller used on lines 25–26:
- line 25: we retrieve the controller instance associated with the name [main-controller] from the configuration. These are the following lines:
- (continued)
- line 10 above, note that we are retrieving an instance of the class;
- line 26: we ask the controller [MainController] to process the request;
- lines 30–45: the response returned by the [MainController] is sent to the client. We’ll come back to these lines a little later;
The job of the [front_controller] function and then the [MainController] class is to handle the tasks common to all requests:
In the diagram above, we are still in phase 1 of request processing. The main controller [MainController] will continue with step 1.

30.6.2. The main controller [MainController]
The main controller [MainController] continues the work started by the [front_controller] function:
All controllers implement the following [InterfaceController] [2] interface:

from abc import ABC, abstractmethod
from werkzeug.local import LocalProxy
class InterfaceController(ABC):
@abstractmethod
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
pass
- The [InterfaceController] interface defines only the single [execute] method on line 8. This method takes three parameters:
- [request]: the client’s request;
- [session]: the client’s session;
- [config]: the application configuration;
The [execute] method returns a two-element tuple:
- the first is the results dictionary in the form {‘action’: action, ‘status’: status, ‘response’: results};
- the second is the HTTP status code to return to the client;
The main controller [MainController] [1] implements the [InterfaceController] interface as follows:
The [MainController] performs the initial checks to validate the request.
- lines 11–13: The controller begins by retrieving the action requested by the client. Recall that service URLs are of the form [/action/param1/param2/…] and that this URL is in [request.path];
- lines 17–23: the [init-session] action is used to initialize the response type (json, xml, html) requested by the client. This information is stored in the session under the key [responseType]. Therefore, if the action is not [init-session], the session must contain the key [responseType]; otherwise, the request is invalid;
- lines 21-22: the structure of the result returned by each controller, in this case an error result:
- [action]: is the name of the current action. This will allow us to retrieve its name when logging the request result;
- [status]: is a three-digit status code:
- [x00] for a success;
- [x01] for a failure;
- [response]: is the response to the request. Its nature is specific to each request;
- lines 24–30: the [authenticate-user] action is used to authenticate the user. If successful, a [user=True] key is added to the user’s session. Certain service URLs are accessible only to an authenticated user. This is what is checked here;
- line 26: only the [init-session] and [authenticate-user] actions can be performed by a user who has not yet been authenticated;
- lines 28–29: the response to send in case of an error;
- lines 32–34: if either of the two previous errors occurred, then the error response is sent to the client with HTTP status 400 BAD REQUEST;
- lines 35–39: if no error occurred, control is passed to the controller responsible for handling the current action. Its instance is found in the application configuration;
The [MainController] class continues the work of the [front_controller] function: together, they handle everything that can be factored out of request processing, waiting until the last moment to pass the request to a specific controller. The division of code between the [front_controller] function and the [MainController] class is entirely subjective. Here I wanted to preserve the structure of the previous version: the [front_controller] function already existed under the name [main]. In practice, one could:
- put everything in the [front_controller] function and eliminate the [MainController] class;
- put everything in the [MainController] class and eliminate the [front_controller] function. I would tend to choose this solution because it has the advantage of streamlining the code of the main script [main];
30.7. Action-specific processing
Let’s return to the application’s MVC architecture:

We are still at step 1 above. If there were no errors, step 2 will begin. The request has been forwarded to the controller specific to the action requested by the request. Let’s assume this action is [/init-session] defined by the route:
This action is linked to a controller in the [config] configuration:
# actions autorisées et leurs contrôleurs
"controllers": {
# initialisation d'une session de calcul
"init-session": InitSessionController(),
…
},
The [InitSessionController] (line 4) then takes over. Its code is as follows:
- line 6: like the other controllers, the [InitSessionController] implements the [InterfaceController] interface;
- line 10: the URL is of type [/init-session/type_response]. We retrieve the [init-session] action and the desired response type;
- line 15: the desired response type can only be one of those present in the response configuration:
# les différents types de réponse (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
- if this is not the case, an error response 701 is prepared (line 17);
- lines 20–25: case where the desired response type is valid;
- line 22: the desired response type is stored in the session. This is because we will need to remember it for subsequent requests;
- lines 23–24: prepare a 700 success response;
- line 25: the success response is returned to the caller;
- line 27: if an error occurred, the error response is returned to the caller;
30.8. Generating the server’s HTTP response
Let’s return to the application’s MVC architecture:

We have just covered steps 1 and 2. We encountered three status codes:
- 700: /init-session succeeded;
- 701: /init-session failed;
- 101: invalid request, either because the session has not been initialized or because the user is not authenticated;
Let’s examine how the server’s response will be sent to the client during step 3 above. This happens in the [front_controller] function of the [main] script:
- We are now on line 26: the main controller has returned its error response;
- lines 27–29: regardless of the main controller’s response (success or failure), this response is logged in the log file;
- lines 30–33: as in previous versions, if the HTTP status is [500 INTERNAL SERVER ERROR], we send an email to the application administrator with the error log;
- lines 34–39: We send the HTTP response, and the result returned by the controller is placed in the body of this response. We need to know in what format (JSON, XML, HTML) the client wants this response. We look for the desired response type in the session. If it is not there, we arbitrarily set this type to JSON;
- lines 40–43: the HTTP response is constructed;
In the configuration file, each response type (json, xml, html) has been associated with a class instance:
# les différents types de réponse (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
The response classes are located in the [responses] folder of the server directory tree:

Each response class implements the following [InterfaceResponse] interface:
from abc import ABC, abstractmethod
from flask.wrappers import Response
from werkzeug.local import LocalProxy
class InterfaceResponse(ABC):
@abstractmethod
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
pass
- lines 8–11: the [InterfaceResponse] interface defines a single method [build_http_response] with the following parameters:
- [request, session, config]: these are the parameters received by the action controller;
- [result, status_code]: these are the results produced by the action controller;
We will now present the JSON response. It is generated by the following [JsonResponse] class:
We are familiar with this code, which we have encountered many times. It is the code for the [json_response] function in the [myutils] module.
30.9. Initial tests
In the code we examined, we encountered three status codes:
- 700: /init-session succeeded;
- 701: /init-session failed;
- 101: invalid request, either because the session has not been initialized or because the user is not authenticated;
We will try to trigger these with a JSON session.
- We start the web server, the DBMS, and the mail server;
- We launch a Postman client;
Test 1
First, we’ll demonstrate an invalid request because the session hasn’t been initialized:

- [1-2]: The request [POST http://localhost:5000/authentifier-utilisateur] is a valid route:
# authenticate-user
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# execute the controller associated with the action
return front_controller()
but it is only accepted if the session has been initialized beforehand with the [/init-session] action.
Let’s execute the request and see the result sent by the server:

- [1-2]: we received a JSON response. When the response type has not yet been specified by the client, the server uses JSON to respond;
- [3-5]: the JSON dictionary of the response;
- [action]: the action that was executed;
- [status]: the response status code. A code [x01] indicates an error;
- [response]: is tailored to each action. Here it contains an error message;
Now let’s initialize a session with an incorrect response type:

- [1-2] is a valid route:
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# execute the controller associated with the action
return front_controller()
It will therefore enter the MVC server's request processing pipeline. However, it should be rejected during this processing because the requested session type is incorrect.
The response is as follows:

- in [4], an error code [x01];
- in [5], the error explanation;
Now, let’s initialize a JSON session:

The response is as follows:

Now, let’s initialize an XML session. The JSON response will be replaced by an XML response generated by the following [XmlResponse] class:
This is code we’re familiar with—it’s from the [xml_response] function in the shared [myutils] module.
We initialize an XML session:

The server’s response is then as follows:

We get the same response as in JSON, but this time the response is formatted as XML.
30.10. The [authenticate-user] action
The [authenticate-user] action allows you to authenticate a user who wishes to use the tax calculation application. Its route is defined as follows in the [main] script:
The server expects two POST parameters:
- [user]: the user's ID;
- [password]: their password;
The list of authorized users is defined in the [config] configuration:
# utilisateurs autorisés à utiliser l'application
"users": [
{
"login": "admin",
"password": "admin"
}
],
Here, we have a list with a single element.
The [authenticate-user] action is handled by the following [AuthentifierUtilisateurController] controller:
- line 14: retrieve the POST parameters;
- line 19: the list of errors found in the request;
- lines 20–24: we verify that there are indeed two posted parameters;
- lines 27–31: check for the presence of a [users] parameter;
- lines 32–36: check for the presence of a [password] parameter;
- lines 38–39: if the posted parameters are incorrect, prepare an HTTP 400 BAD REQUEST response;
- lines 40–58: verify that the credentials [user, password] belong to a user authorized to use the application;
- lines 51–55: if the user (user, password) is not authorized to use the application, prepare an HTTP 401 UNAUTHORIZED response;
- lines 56–58: if the user is authorized, we record in the session using the [user] key that they have authenticated;
Note that if the user was authenticated with credentials [credentials1] and fails to authenticate with credentials [credentials2], they remain authenticated with credentials [credentials1].
Let’s run some Postman tests:
- We start the web server, the DBMS, and the mail server;
- Using the Postman client:
- start a JSON session;
- then authenticate;
Here are different scenarios.
Case 1: POST without posted parameters

- In [3-5], the POST has no body;
The result of the request is as follows:

- In [2], we received an HTTP 400 BAD REQUEST response;
- In [5], we received an error code [201];
Case 2: POST with incorrect credentials

- In [6], the credentials are incorrect;
The server sends the following response:

- in [2], the HTTP 401 UNAUTHORIZED response;
- In [5], the error response;
Case 2: POST with correct credentials

- In [6], the credentials are correct;
The server's response is as follows:
- in [2], an HTTP 200 OK response;
- in [5], the success response;
30.11. The [calculate_tax] action
The [calculate_tax] action calculates a taxpayer’s tax. Its route is defined as follows in the [main] script:
The server expects three POST parameters:
- [married]: yes / no;
- [children]: number of children of the taxpayer;
- [salary]: taxpayer's annual salary;
The [CalculateTaxController] controller handles the [calculate_tax] action:
- line 13: retrieve the name of the current action;
- line 17: we collect the errors in a list;
- line 19: retrieve the posted parameters. These are posted in the form [x-www-form-urlencoded], which is why we retrieve them from [request.form]. If they had been posted as JSON, we would have retrieved them from [request.data];
- lines 21–24: we verify that there are indeed three posted parameters;
- lines 27–36: check for the presence and validity of the posted parameter [married];
- lines 37–48: checking for the presence and validity of the posted parameter [children];
- lines 49–60: check for the presence and validity of the posted parameter [salary];
- lines 62–66: if there was an error, a 400 BAD REQUEST error response is sent with a status code [301];
- lines 69–71: if there was no error, prepare to calculate the tax. To do this,
- line 70: retrieve a reference from the [business] layer;
- line 71: retrieve data from the tax authority in the server configuration;
- lines 72–74: the taxpayer’s tax is calculated;
- lines 75–77: we count the number of tax calculations performed by the user;
- line 76: retrieve the number of the last calculation performed from the session. Here, we refer to the result of a calculation as [simulation];
- line 77: the number of the last simulation is incremented;
- line 78: this number is saved to the session;
- lines 79–84: to track the calculations performed by the user, we will store the list of simulations they have performed in their session;
- line 80: a simulation will be the dictionary of a TaxPayer object whose [id] property will have the value of the simulation number;
- lines 82–84: the current simulation is added to the list of simulations in the session;
- lines 86-87: we prepare an HTTP success response;
- line 90: we return the result;
Let’s run some tests: the web server, the DBMS, the mail server, and a Postman client are launched.
Case 1: performing a tax calculation while the session is not initialized

The response is as follows:

Case 2: performing a tax calculation without being authenticated
First, we start a JSON session with [/init-session/json]. Then we make the same request as before. The response is as follows:

Case 3: Performing a tax calculation with missing parameters
We initialize a JSON session, authenticate, and then make the following request:

- in [5], the [married] parameter is missing;
The response is as follows:
Case 4: Calculating tax with incorrect parameters


The server’s response is as follows:

Case 4: Performing a tax calculation with correct parameters

The server's response is as follows:

30.12. The [list-simulations] action
The [list-simulations] action allows a user to view the list of simulations they have performed since the start of the session. Its route is defined as follows in the [main] script:
The server does not expect any parameters. The [lister-simulations] action is handled by the following [ListerSimulationsController]:
- line 13: the list of simulations is retrieved from the session;
- lines 15-16: a success response is returned;
Let’s run the following Postman test:
- We start a JSON session;
- We authenticate;
- Perform two tax calculations;
- We request the list of simulations;
The request is as follows:
- in [3], there are no parameters;
The server's response is as follows:

- in [4], the user's list of simulations;
30.13. The [delete-simulation] action
The [delete-simulation] action allows a user to delete one of the simulations from their simulation list. Its route is defined as follows in the [main] script:
The server expects a single parameter: the number of the simulation to be deleted. The [delete-simulation] action is handled by the following [DeleteSimulationController]:
- line 10: retrieve the two elements of the request path. They are retrieved as strings;
- line 13: the [number] parameter is converted to an integer. We know this is possible because of the route’s signature,
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
We also know that it is an integer >=0. Indeed, we cannot have a URL like [/delete-simulation/-4]. This is rejected by the Flask server;
- line 15: we retrieve the list of simulations from the session;
- line 16: using the [filter] function, we search for the simulation with id==number. We obtain a [filter] object that we convert to a [list];
- lines 17–20: if the filter returns nothing, then the simulation to be deleted does not exist. We return an error response indicating this;
- lines 21–23: we delete the simulation returned by the filter;
- line 25: We restore the new list of simulations to the session;
- line 27: return the new list of simulations in the response;
We perform a success test and a failure test. We run simulations and then request the list of simulations:

- The simulations here have numbers 2 and 3;
We request that the simulation with number 3 be removed.

The response is as follows:
Now, let’s repeat the same operation (deleting the simulation with id=3). The response is then as follows:


30.14. The [end-session] action
The [end-session] action allows a user to end their simulation session. Its path is defined as follows in the [main] script:
The server expects no parameters. The action is handled by the following [FinSessionController]:
- Line 13: Delete all keys from the session. This deletes:
- [typeResponse]: the type of HTTP responses (json, xml, html);
- [simulation_id]: the ID of the last simulation performed;
- [simulations]: the list of the user’s simulations;
- [user]: the indicator that the user has been authenticated;
- return the response;
One might wonder how the HTTP response from line 15 will be returned, now that the response type is no longer in the session. To find out, we need to go back to the |front_controller| function in the main script [main] and modify it as follows:
…
# on not# note the type of response required if this information is in the session
type_response1 = session.get('typeResponse', None)
# forward the request to the main controller
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# we log the result sent to the customer
log = f"[front_controller] {résultat}\n"
logger.write(log)
# was there a fatal error?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# send an e-mail to the application administrator
send_adminmail(config, log)
# determine the desired type of response
type_response2=session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# the session type has not yet been set - it will be jSON
type_response = 'json'
elif type_response2 is not None:
# the type of response is known and in the session
type_response = type_response2
else:
type_response=type_response1
# build the response to be sent
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# we send the answer
return response, status_code
- line 3: the type of the response currently in session is stored;
- line 6: the action is executed. If it is:
- [end-session], the [typeResponse] key is no longer in the session;
- [init-session], the [typeResponse] key in the session may have changed value;;
- lines 14–20: the HTTP response must be sent. We need to know in what form:
- lines 16–18: if the response type is not defined by either [type_response1] in line 3 or [type_response2] in line 15, then the response type was not defined either before or after the action. We then use JSON (line 18);
- lines 19–21: if [type_response2] exists—the response type in the session after the action—then that is the type to use;
- lines 22–23: otherwise, [type_response1], the response type before the action (which must be [end-session]), is the one to use;
30.15. The [get-admindata] action
We will now discuss the two URLs reserved for the JSON and XML services:
Action | Role | Execution context |
/get-admindata | Returns the tax data used to calculate the tax | GET request. Used only if the session type is json or xml. The user must be authenticated |
/calculate-taxes | Calculates the tax for a list of taxpayers posted in JSON | GET request. Used only if the session type is json or xml. The user must be authenticated |
The URL [/get-admindata] is defined in the routes of the main script [main] as follows:
The route [/get-admindata] is handled by the following [GetAdminDataController]:
- lines 13-21: we check that we are in a JSON or XML session;
- line 24: return the tax administration data dictionary, which was placed in the configuration when the server started:
# admindata sera une donnée de portée application en lecture seule
config["admindata"] = config["layers"]["dao"].get_admindata()
Let’s use a Postman client and request the URL [/get-admindata], after starting a JSON session and authenticating:

The server response is as follows:

30.16. The [calculate-taxes] action
The [calculate-taxes] action calculates the taxes for a list of taxpayers found in the request body as a JSON string. We are already familiar with this action: it was called [calculate_tax_in_bulk_mode] in the previous version.
Its route is as follows:
This action is handled by the following [CalculateTaxesController]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
- lines 16-24: we verify that we are indeed in a JSON or XML session
- lines 26–120: this code is generally familiar to us. It is from the |index_controller| function in version 10 of the application, which has been adapted to meet the specifications of the implemented [InterfaceController] interface;
- lines 104–115: code added to account for this controller’s new environment. We have just performed tax calculations. We need to store the results in the list of simulations maintained in the session;
- line 105: we retrieve the list of simulations in the session;
- line 106: we retrieve the number of the last simulation performed;
- lines 107–112: we iterate through the list of dictionaries containing the tax calculation results; we assign a simulation ID to each one, and each dictionary is added to the list of simulations;
- lines 113–115: the new list of simulations and the number of the last simulation performed are returned to the session;
We perform the following Postman test after initializing a JSON session and authenticating:


The server response is as follows:

If we now request the list of simulations:
Note that in the results list for [/calcul-impots], taxpayers do not have an [id] attribute, whereas in the list of simulations, each simulation has a number that identifies it.




