Data-driven web applications basics: Getting started with Flask

Python
Flask
Published

February 8, 2024

In this series of posts, we will explore some of the fundamental concepts of building data-driven web applications. To do so, we will create a simple web application that lets users share their favorite websites and view the most popular ones based on user input.

This application will consist of only two pages: (1) a home page where users can register their favorite websites, and (2) a page of popular sites where users can see a table displaying the top favorite websites. The envisioned design for these pages is illustrated in Figure 1.

To build this web application, we will follow a structured approach, breaking down the process across four comprehensive posts:

  1. Post 1: Building the Home page with Flask. We will start by creating the home page for our application using Flask, along with basic HTML and CSS. This page will allow users to submit a URL to our backend and receive a confirmation response. In this post, we will only show the confirmation without storing the URL; the storage functionality will be implemented in the next post.

  2. Post 2: Completing the interface and connecting to a database. We will complete the interface, adding a “Popular” page where users can view top favorite websites. We will connect our application to a PostgreSQL server to store user data, ensuring we can save and retrieve the registered websites.

  3. Post 3: Containerizing the application with docker. To facilitate easy deployment and collaborative development, we’ll containerize our application using Docker. This will allow us to create a consistent environment for running our application, simplifying both development and deployment processes.

  4. Post 4: Enhancing performance with Redis. Finally, we will enhance our application’s performance by integrating Redis. Redis will serve as a queue to manage high volumes of simultaneous user registrations, preventing our SQL server from being overwhelmed. Additionally, Redis will act as a cache for the “Popular” page, speeding up user interactions.

By following this series, you will learn how to:

Each part will provide detailed, step-by-step instructions, making it easy to follow along and build a simple, but functional and efficient web application.

Building the Home page with Flask

In this first post, we will lay the foundation for our web application by starting the user interface. Here, we will focus solely on developing the Home page. Users will be able to input and submit their favorite websites through this page. Our backend will handle these submissions and provide a confirmation response to the user. Figure 2 provides a visual representation of the Home page design, showcasing the intended user experience when submitting a website—confirming successful registration of the website inputted by the user.

Figure 2: Building the Home page: Favorite website registration and confirmation flow

To achieve this, we will utilize Flask—a micro-framework for web development in Python. Flask provides a versatile and lightweight way to build web applications, making it easy to get started with web development projects.

Flask will handle the backend behavior of our application, including routing, handling requests, and managing the server-side logic. For the frontend, we will use HTML and CSS to create and style the visual components of our application.

At this stage, we will not permanently save the website data inputted by the user; the backend will only acknowledge receipt of the submissions. In the subsequent post, we will expand our application to include the “Popular Websites” page and begin implementing data persistence.

Now that we’ve precisely defined the behavior of our home page, it’s time to get our hands dirty and start coding.

Loading initial libraries and initializing a Flask instance

To begin building our web application, we first import the essential components from the flask library. These include the Flask object, which is the core of our web app, the requests module for handling HTTP requests, and the render_template function, which allows us to render HTML files (or Flask templates).

from flask import Flask, request, render_template

After importing the requirements, we will create an instance of the Flask application:

app = Flask(__name__)

Now, let’s break down what’s happening here:

  • Flask: This object from the Flask framework helps us create an instance of a Flask web application.

  • __name__: A special variable in Python representing the name of the current module. It’s passed as an argument to locate resources like HTML templates and static files.

Therefore, what we are doing is creating an instance of the Flask application, passing __name__ as an argument so that Flask can locate necessary resources, such as HTML templates and static files (CSS, JavaScript, images). Finally, we assign this instance to an object called app, which we will later use to define the behavior of our web application.

Creating the Home page

Now that we have initialized our application, let’s turn our attention to the front-end, i.e., the part users will see and interact with.

We want a simple page with a text field where users can enter a URL and a button to register it as shown in Figure 3.

Figure 3: Envisioned design for the Home page

To begin creating this webpage, we will first establish its structure using HTML.

HTML for the Home page

As seen in Figure 3, our website has two key elements: (1) a title and (2) a user input form. The form allows users to enter a website URL via a text input field and submit it using a submit button.

To set the page title, we will use a top-level heading element, i.e., <h1>, containing the text “What is your favorite website?”. Therefore, the HTML code for the title will be as follows: <h1>What is your favorite website?</h1>

Moving on to the form section, we’ll create a form with the <form> element setting its method attribute to POST, i.e., <form method="post">. This ensures that the data submitted through the form is included within the body of the HTTP request, as opposed to being appended to the URL (like with the GET method). The form itself will consist of two key elements:

  1. Text input field for entering a URL: To create a text input field for entering a URL, we’ll use an <input> element. This element lets users input information into a form. Inputs can have different types, like checkboxes or text fields. Since we want a text input for URLs, we’ll specify the type attribute as "text". We’ll also add a placeholder using the placeholder attribute with the text "Enter URL" to give users a hint about what to input. The placeholder disappears once users start typing.
    To make sure users fill out this field before submitting the form, we’ll mark it as required using the required attribute. Additionally, we’ll assign a class to this input field called "form-text-input". This allows us to style this element and any others with the same class. Lastly, we’ll give the input field a name attribute, set to 'urlInput', so we can easily identify the text entered and retrieve it from the backend after form submission.
    Therefore, we would have the following HTML code for the text input:
    <input type="text" placeholder="Enter URL" class="form-text-input" name="urlInput" required>

  2. Button to submit the entered URL: Finally, to complete the form, we’ll add a submit button with the text “Register URL”. When users click this button, the form will be submitted, and the URL they entered will be registered. We’ll achieve this using a <button> element. Like input elements, buttons can come in different types, such as reset buttons, which reset the form-data to its initial values, or submit buttons, which submit form-data. Since we require a submit button, we’ll specify the type attribute as "submit". Furthermore, for consistent styling among related elements, we’ll assign to this element a class named "submit-btn".
    Thus, the HTML code for the submit button would be:
    <button type="submit" class="submit-btn">Register URL</button>

To enhance the structure of our page, we will also incorporate some additional elements like <div>. Using <div> elements helps organize and group content, making it easier to style and manage different sections of the webpage. Here’s the HTML code reflecting these additions:


<!DOCTYPE html>
<html>
    <body>
        <div class="content">
            <div class="container">
                <h1>What is your favorite website?</h1>
                <form method="post">
                    <input type="text" placeholder="Enter URL" class="form-text-input" name="urlInput" required>
                    <br>
                    <button type="submit" class="submit-btn">Register URL</button>
                </form>
            </div>
        </div>
    </body>
</html>

Next, let’s save this HTML code into a file named index.html. Afterward, you can simply open it using a web browser to view the rendered HTML, seeing what is shown in Figure 4.

Figure 4: Rendered HTML code

As we can see, the current appearance of our interface leaves much to be desired. It’s clear that our interface needs a makeover. But don’t worry! We’ve strategically assigned classes to elements within our HTML markup. This serves as the foundation for applying CSS styles, which will significantly enhance the visual appeal and user experience of our application. So, let’s start creating some CSS to improve the style of our application!

CSS for the Home page

To achieve the theme we envisioned for the homepage design, as described in Figure 3, we will utilize CSS. This theme predominantly employs gray for most elements and accentuates prominent features with green.

First, we will set the body background color to gray. This choice provides a neutral and clean canvas that is easy on the eyes and allows the more prominent elements to stand out. The green color will be applied specifically to the submit button, making it a focal point due to its contrasting hue. This not only enhances the button’s visibility but also emphasizes its importance and functionality within the form.

To enhance the minimalist aesthetic and ensure the form attracts the user’s attention, we will center the form on the screen. Centering the form not only creates a balanced and harmonious layout but also directs the user’s focus to this essential part of the page.

Additionally, we will add other tweaks such as margins and padding to make the layout more appealing and visually pleasing. Below you can see the complete CSS code:

body {
    font-family: Arial, sans-serif; /* Sets the text font for the entire document body */
    margin: 0; /* Removes the default margin */
    padding: 0; /* Removes the default padding */
    height: 100vh; /* Sets the body height to 100% of the viewport */
}

.content {
    display: flex; /* Uses the flexible box model for content */
    justify-content: center; /* Horizontally centers the content */
    align-items: center; /* Vertically centers the content */
    height: 100%; /* Sets the content height to 100% of the parent element */
    background-color: #f0f0f0; /* Sets the background color of the content */
    flex-direction: column; /* Arranges child elements in columns */
}

.form-text-input {
    width: 300px; /* Sets the width of the URL input field */
    padding: 10px; /* Adds padding around the input field */
    font-size: 16px; /* Sets the font size of the input field */
    border: 1px solid #ccc; /* Sets a 1px solid border with a light gray color */
    border-radius: 5px; /* Applies rounded corners to the input field */
    margin-bottom: 20px; /* Adds bottom margin to separate the input field from the button */
}

.submit-btn {
    background-color: #4CAF50; /* Sets the background color of the submit button */
    color: white; /* Sets the text color of the submit button */
    padding: 10px 20px; /* Adds padding around the button text */
    font-size: 16px; /* Sets the font size of the button text */
    border: none; /* Removes the border from the submit button */
    border-radius: 5px; /* Applies rounded corners to the submit button */
    cursor: pointer; /* Changes the cursor to a pointer when hovering over the button */
    transition: background-color 0.3s; /* Adds a smooth transition to the button's background color */
}

.submit-btn:hover {
    background-color: #3e8e41; /* Changes the button's background color when hovering over it */
}

.container {
    text-align: center; /* Centers the text inside the container */
    margin-bottom: 20px; /* Adds bottom margin to separate the form from the result */
}

As you can see, we have predominantly used class selectors to apply specific styles to various elements. For instance, the url-input class styles the URL input field, while the submit-btn class styles the form’s submit button. This approach allows for consistent styling across different pages, as these classes can be reused for similar elements, ensuring a uniform look and feel throughout the application.

Now we can link this CSS file to our HTML file by adding the following code right after the opening <html> tag:

<head>
<link rel="stylesheet" href="home.css">
</head>

With this, our initial page no longer looks like in Figure 4, but as shown in Figure 5.

Figure 5: Rendered HTML code with added CSS styling

Great, our application is starting to look nice!

Structuring our directory

When setting up a Flask application, it’s advisable to organize your files in a structured manner. Typically, you’ll start with a main folder that encompasses all the files related to your application. Within this main folder, you’ll find the Python file containing the Flask code. By convention, this file is often named app.py. Alongside app.py, you’ll also have two additional folders: templates and static.

The templates folder is where you’ll store your HTML files. These files contain the structure and layout of your web pages. Meanwhile, the static folder is reserved for files like CSS, JavaScript, images, and other resources that remain static and are not altered by your application’s code.

Therefore, this would be the file structure for our application:

- my_flask_app/            (Main folder of the application)
    |
    |- app.py              (Main file for the Flask application)
    |
    |- templates/          (Folder to store HTML templates)
    |    |
    |    |- index.html     (HTML template for the main page)
    |
    |- static/             (Folder to store static files such as CSS, JavaScript, images, etc.)
         |
         |- styles         (Folder to store style sheets, such as CSS files)
            |
            |- home.css    (CSS file to style the main page)

Note: Following the modification of the file structure, it’s important to update the link to the CSS file in our HTML file.

After having set up the HTML file and structured our Flask application, the next step is to integrate the HTML file with our backend. This means that our Python application will utilize the HTML file as the main page.

To accomplish this, we’ll create a function that automatically executes when a user accesses the root path of the application, denoted as “APPLICATION URL/”. We achieve this by utilizing the route() decorator with the argument “/” and defining a function to be executed. In this scenario, our function will render the HTML file, index.html, using render_template().

Additionally, we’ll utilize the methods parameter within the route decorator to enable the home() function to accommodate both GET and POST requests. GET requests are typically utilized to fetch data from the server, such as when a user initially accesses a webpage. Conversely, POST requests are commonly employed when users submit data to the server, as is the case when they provide their favorite website URL in our case.

Thus, the combined process can be translated into code as follows:

@app.route("/", methods=['GET', 'POST'])
def home():
    return render_template('index.html')

Note: We define the name of the function as home(), but we could choose any other name.

Adding functionalities

Right now, our application doesn’t do much, it merely renders the HTML file we’ve created. However, it lacks any interactive functionality. If we add text to the input field and click the “Register URL” button, nothing will occur. What we need is for our backend—our Python code—to capture the URL entered in the text field upon button click.

When a button within a form is clicked, it initiates a “request,” specifically in our case a POST request, sending the form’s values to our backend. Utilizing the previously imported request object, we can access these values and manipulate them within our backend.

The request object provides various attributes, including form, which facilitates the extraction of parameters submitted by a form. By using this attribute jointly with the get method, we can extract the desired data from the request. In this instance, we aim to retrieve the content entered by the user into the text field identified by the name urlInput.

Therefore, we could write the following code to extract the value sent from that field and store it in an a variable called url:

url = request.form.get('urlInput')

Using templates

Keep in mind that up to the point we’ve utilized the render_template() function to display a static HTML file, specifically for rendering our index.html page. However, this function is named render_template rather than render_html for a reason.

Templates in Flask serve as dynamic canvases, enabling the incorporation of placeholders that the application dynamically populates. These placeholders, known as template variables, are encapsulated within double opening and closing brackets, like so: { template_variable }. This lets us include variables right into the HTML, and our backend can then change them on the fly.

Additionally, in Flask templates, we can utilize control structures to manage the flow of content. These include conditionals like if-else statements, for-loops, as well as advanced features such as macros and blocks. These control structures are enclosed within {% %} brackets, denoted as {% control_structure %}, and are concluded with {% end_control_structure %}. For example, these control structures allow us to show or hide different parts of the template based on certain conditions.

Jinja 2

The template engine used by Flask is Jinja2. You can find more information about template creation on its website: https://jinja.palletsprojects.com/

One of the most basic applications of templates is to dynamically manage the locations of static files, such as CSS files. Hardcoding these paths can create issues if the file locations change over time. Instead, it’s preferable to use Flask’s URL generation function, url_for(). This function automatically generates URLs for specified routes or resources, which is particularly useful when project URLs undergo changes during deployment.

For example, during development, URLs may follow a certain structure. However, upon deployment, it might be necessary to prefix all URLs with a specific identifier. With Flask’s url_for() function, this transition is handled without manual intervention. The function automatically adjusts the generated URLs to include the designated prefix without the need for manual code alterations.

Consequently, we can enhance our HTML file to incorporate the link to the CSS file using url_for():

<link rel="stylesheet" href="{{ url_for('static', filename='/styles/home.css')}}">

However, templates offer more than just dynamically setting the CSS path; they enable us to add engaging features. For example, we can leverage templates to define the behavior of the confirmation message indicating that the entered URL has been successfully registered.

To implement this, let’s first update our HTML file by introducing a new <div> element to contain the confirmation message. This message should only appear if the user has entered a URL, meaning the url variable holds a value. If the variable exists, we’ll display its value. Here’s how we can accomplish this:

<div id="confirmationContainer">
  {% if url %}
    <p>You have registered the following URL: {{ url }}</p>  <!-- Display URL registration confirmation -->
  {% endif %}
</div>

In this code snippet, we’re utilizing an if control structure to conditionally display content based on whether the url variable holds a value. The content we want to display is enclosed within {% if url %} and {% endif %} tags. Inside the {% if url %} block, we include a message confirming the URL registration. To dynamically insert the value of the url variable into the message, we use double curly braces { url } within a paragraph (<p>) element.

Passing variables to templates

To make sure our template can access the url value, we need to transfer it from the backend to the frontend. In other words, we must make this variable available within the template. We can accomplish this by adjusting our app.py file, specifically by modifying the render_template() function.

The render_template() function facilitates the passing of variables to the template we are rendering. To pass the url value to the index.html page and make it accessible with the same name (url), we employ the following code: render_template('index.html', url=url). This ensures that the url variable is transmitted to the index.html page, where it can be utilized under the same name.

However, we only intend to transfer the url value to the frontend if the user submits a URL via a POST request. In such instances, we render our HTML file while transmitting the provided URL value to it. Conversely, if the user does not submit a URL, we render the HTML file without transmitting any variables to it. To implement this logic, we need to adjust the home() function in app.py, as we show below:

@app.route("/", methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        url = request.form.get('urlInput')
        return render_template('index.html', url=url)  # Pass the value of 'urlInput' to the template
    else:
        return render_template('index.html')

Reviewing what we have

So, now we have three fairly complete files. Let’s directly list the code we have so far.

Backend

Flask application: app.py
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        url = request.form.get('urlInput')
        return render_template('index.html', url=url)  # Pass the value of 'urlInput' to the template
    else:
        return render_template('index.html')

Front-end

HTML file: index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='/styles/home.css')}}">
</head>
    <body>
        <div class="content">
            <div class="container">
                <h1>What is your favorite website?</h1>
                <form method="post">  <!-- Specify method="post" for form submission -->
                    <input type="text" name="urlInput" placeholder="Enter URL" class="form-text-input" required>
                    <br>
                    <button type="submit" class="submit-btn">Register URL</button>
                </form>
                <div id="confirmationContainer">
                  {% if url %}
                  <p>You have registered the following URL: {{ url }}</p>  <!-- Display URL registration confirmation -->
                  {% endif %}
                </div>
            </div>
        </div>
    </body>
</html>
CSS file: home.css
body {
    font-family: Arial, sans-serif; /* Sets the text font for the entire document body */
    margin: 0; /* Removes the default margin */
    padding: 0; /* Removes the default padding */
    height: 100vh; /* Sets the body height to 100% of the viewport */
}

.content {
    display: flex; /* Uses the flexible box model for content */
    justify-content: center; /* Horizontally centers the content */
    align-items: center; /* Vertically centers the content */
    height: 100%; /* Sets the content height to 100% of the viewport */
    background-color: #f0f0f0; /* Sets the background color of the content */
    flex-direction: column; /* Arranges child elements in columns */
}

.form-text-input {
    width: 300px; /* Sets the width of the URL input field */
    padding: 10px; /* Adds padding around the input field */
    font-size: 16px; /* Sets the font size of the input field */
    border: 1px solid #ccc; /* Sets a 1px solid border with a light gray color */
    border-radius: 5px; /* Applies rounded corners to the input field */
    margin-bottom: 20px; /* Adds bottom margin to separate the input field from the button */
}

.submit-btn {
    background-color: #4CAF50; /* Sets the background color of the submit button */
    color: white; /* Sets the text color of the submit button */
    padding: 10px 20px; /* Adds padding around the button text */
    font-size: 16px; /* Sets the font size of the button text */
    border: none; /* Removes the border from the submit button */
    border-radius: 5px; /* Applies rounded corners to the submit button */
    cursor: pointer; /* Changes the cursor to a pointer when hovering over the button */
    transition: background-color 0.3s; /* Adds a smooth transition to the button's background color */
}

.submit-btn:hover {
    background-color: #3e8e41; /* Changes the button's background color when hovering over it */
}

.container {
    text-align: center; /* Centers the text inside the container */
    margin-bottom: 20px; /* Adds bottom margin to separate the form from the result */
}

Running our application

With the initial parts of the code in place, we’re ready to run our application. To do so, we can open a terminal, navigate to the folder where our app.py file is located, and then execute the command flask run. After running this command, we will see an output with a local IP address (by default, 127.0.0.1:5000), as you can see in the Figure 6. This is the IP address where our application is running.

Figure 6: Executing the flask run command

Now we can navigate to this address in our web browser to access our application. Once there, we can input a URL (for instance: www.google.com) into the text field, click the submit button, and we’ll then observe a confirmation message generated by the previously established template, as you can see in Figure 7.

Figure 7: Registering www.google.com in our application

This message confirms that our action of registering a URL has been successful. Essentially, it signifies that the user’s POST request has been correctly captured by the backend, transmitted to the front-end, and then integrated into the template.

A first issue: When refreshing the page, our request is sent again

If we refresh the page, for instance, by pressing F5, we’ll notice that the request we initially entered, such as www.google.com, is automatically resent, as you can see in Figure 8.

Figure 8: The request is automatically resent upon page refresh

This behavior is typical when a page has been loaded through a POST request, often encountered when working with forms.

To tackle this issue, there are various strategies we could employ. One of the most prevalent is the Post/Redirect/Get (PRG) strategy (you can find more information here). Essentially, after making a POST request, we redirect to a new page, which then retrieves (GETs) the confirmation of our current state.

Implementing this strategy in our application is relatively straightforward. We need to make two key adjustments:

  1. Instead of rendering our template with the URL submitted by the user upon detecting a POST request, we redirect to a new page.

  2. To take this redirection into effect, we incorporate additional code using the @app.route decorator, specifying the behavior of the route to which we redirect.

To do this, we need to import two additional functions from the Flask library: redirect and url_for. What these functions do is quite obvious from their names. The redirect function redirects us to a URL we specify, while the url_for function allows us to dynamically create URLs within our application. If you recall, we previously utilized the url_for function to dynamically link the index.html file to our stylesheet.

By using these functions, we can adjust our code so that when we get a POST request from a user registering a URL, instead of simply refreshing our template, we redirect them to a new URL, like app_url/display_url/url, where url refers to the URL provided by the user. This redirection is made possible with the line of code: redirect(url_for('display_url', url=url)). Here, url_for generates a URL pointing to the display_url view within the Flask application and passes the value of the url variable as a route parameter, yielding the desired URL. Thus, the Python code for our application appears as follows:

@app.route("/", methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        url = request.form.get('urlInput')
        return redirect(url_for('display_url', url=url))
    else:
        return render_template('index.html')

Dynamic URL handling with Flask converters

The code we’ve just implemented redirects users when they input a URL to app_url/display_url/url, where url refers to the URL inputted by the user. However, this specific URL hasn’t been defined within our application yet. We need to clarify what actions should occur when this URL is accessed. However, looking at the structure of the URL, we can see it has two main parts: one that remains constant (app_url/url) and another that varies (url) based on user input. Does this mean we need to define a route for every possible URL a user might input? Thankfully, no. We don’t need to define a route for every potential URL variation. Instead, we can use Flask converters to define a dynamic route that captures any URL input by the user. This allows us to handle any URL entered by the user without needing to predefine each one individually.

Converters are defined using angle brackets < >, which serve as placeholders for dynamic content. We can define a dynamic route for this page using converters, such as /display_url/<url>, where url is the name of our converter that takes whatever value is placed in its position. Converters also allow us to capture these variable parts of the URL into Python variables. For example, url in the route /display_url/<url> captures whatever value is provided in place of <url> and allows us to access its value within Python by referring to the url variable.

Moreover, we can specify the type we want to allow for these converter values by specifying the type followed by a colon (:) and then the name we want to assign to this converter. By default, they are set to strings (without slashes). However, since we want to accommodate URLs, which may contain slashes, we should specify the type as path, like <path:url>.

Therefore, we can now proceed to define the route in our Flask application to which users will be redirected after inputting a URL, i.e., /display_url/<path:url>:

@app.route("/display_url/<path:url>", methods=['GET'])

Now we need to specify the behavior for this route. In other words, we need to write the function that will execute each time the user is redirected to this URL. Specifically, we want to render our home page, index.html, showing a confirmation message that the inputted URL has been registered correctly.

As previously mentioned, converters such as <path:url> allow us to capture dynamic parts of the URL and use them as Python variables. This means we can access the value of the URL parameter directly within the view function defined by the @app.route decorator.

Therefore, in this page we can do the following. First, we can check if the url variable contains a value. If url is not None (meaning a URL segment has been provided), we proceed to render the index.html template by using the render_template function and passing the url variable to the template. In this way, the confirmation message will be displayed to the user.

However, if the url variable is None, indicating that no URL segment was provided, we redirect the user to the home page of the application. This is done using the redirect function in combination with url_for, generating a redirect response to the URL defined by url_for('home'). This redirection is specified to handle cases where the user manually sets the URL to app_url/display_url/.

Taking all this into account, the code for this route would look like the following:

@app.route("/display_url/<path:url>", methods=['GET'])
def display_url(url=None):
    if url:
        return render_template('index.html', url=url)
    else:  
        return redirect(url_for('home'))

Notice that in this case, we are solely transmitting information. However, envision a scenario where the user might continue registering new URLs, indicating a flow where we receive input from the user. This aspect is not presently accounted for in our code.

To address this gap in functionality, we need to expand our code to capture the URLs that users intend to register when they visit a page with the route /display_url/<path:url>. We can accomplish this by adapting the code we’ve already used for the main route, /. Specifically, we’ll detect if the user is submitting a POST request. If so, we’ll retrieve the submitted URL and redirect to the confirmation page. Below, you’ll find the expanded code for the route /display_url/<path:url>:

@app.route("/display_url/<path:url>", methods=['GET', 'POST'])
def display_url(url=None):
    if url:
        if request.method == 'POST':
            url2 = request.form.get('urlInput')
            return redirect(url_for('display_url', url=url2))
        else:
            return render_template('index.html', url=url)
    else:
        return redirect(url_for('home'))

Therefore, our updated Python code will look as follows:

from flask import Flask, request, render_template, redirect, url_for

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        url = request.form.get('urlInput')
        return redirect(url_for('display_url', url=url))
    else:
        return render_template('index.html')


@app.route("/display_url/<path:url>", methods=['GET', 'POST'])
def display_url(url=None):
    if url:
        if request.method == 'POST':
            url2 = request.form.get('urlInput')
            return redirect(url_for('display_url', url=url2))
        else:
            return render_template('index.html', url=url)
    else:
        return redirect(url_for('home'))

Great! now if we register a URL and refresh the page, the POST request won’t be made again.

Adding style to our confirmation container

Finally, let’s enhance the visual presentation of the confirmation message. Currently, it’s displayed as plain text directly below the “Register URL” without any clear distinction, as depicted in Figure 7. If you recall from section Section 1.4.1, we encapsulated the confirmation message within a <div> element that we identified with the id confirmationContainer. Hence, we can utilize this unique identifier (id) to delineate a distinctive style for this element within our CSS. For instance, we could envelop the confirmation message within a rounded-corner box (specified by border-radius, for example, 5px) embellished with shadowed borders (defined by box-shadow, such as 0 2px 4px rgba(0, 0, 0, 0.1)), featuring a slightly variant shade of gray compared to other application components (e.g., setting the background-color to #f0eaea), and adding some top margin to visually separate it from the form. Below, you can find the style defined for this ID:

#confirmationContainer {
    padding: 20px; /* Adds padding around the content of the result container */
    background-color: #f0eaea; /* Sets the background color of the result container */
    border: 1px solid #ccc; /* Sets a 1px solid border with a light gray color */
    border-radius: 5px; /* Applies rounded corners to the result container */
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Adds a shadow effect to the result container */
    margin-top: 20px; /* Adds top margin to visually separate the confirmation container from the form */

}

With this addition to the CSS file, our confirmation text will now be displayed within a clearly defined container. For instance, upon registering www.google.com, the confirmation message verifying the successful registration of this URL will be neatly enclosed in a well-defined box, as depicted in Figure 9.

Figure 9: Confirmation message container after updating CSS file

However, implementing this change will introduce a minor issue. Upon returning to the main page—by removing the /display_url/www.google.com portion from the URL and pressing ENTER—you’ll notice that the confirmation container still remains despite no confirmation being displayed, as illustrated in Figure 10.

Figure 10: Unintended confirmation container visibility

This issue can be addressed simply by adjusting our HTML template. Specifically the part of the template which shows the confirmation message. Currently, that part appears as follows:

<div id="confirmationContainer">
  {% if url %}
      <p>You have registered the following URL: {{ url }}</p>  <!-- Display URL registration confirmation -->
  {% endif %}
</div>

In our current template, the text “You have registered the following URL: [entered URL]” is displayed only when a user submits a URL. However, the div element with the id confirmationContainer enclosing this text is shown in all cases, regardless of whether a URL is provided or not.

What we aim for is to display both the text and its enclosing box only when a URL is entered. If there’s no URL, neither the text nor the box should appear. Nevertheless, right now, the confirmation box is always displayed as depicted in Figure 10.

To rectify this issue, we only need to relocate the control structure {% if url %} {% endif %} to not just wrap around the paragraph containing the text “You have registered the following URL: [entered URL]”, but also around the enclosing div. Hence, the part of template responsible for displaying the confirmation message would now appear as follows:

{% if url %}
  <div id="confirmationContainer">
        <p>You have registered the following URL: {{ url }}</p>  <!-- Display URL registration confirmation -->
  </div>
{% endif %}

By implementing this adjustment, if we navigate to the main page without registering any URL, the container won’t be visible.

Final code

Here’s the finalized code implementation incorporating all the necessary adjustments.

Final file structure

- my_flask_app/            (Main folder of the application)
    |
    |- app.py              (Main file for the Flask application)
    |
    |- templates/          (Folder to store HTML templates)
    |    |
    |    |- index.html     (HTML template for the main page)
    |
    |- static/             (Folder to store static files such as CSS, JavaScript, images, etc.)
         |
         |- styles         (Folder to store style sheets, such as CSS files)
            |
            |- home.css    (CSS file to style the main page)

Python - app.py

from flask import Flask, request, render_template, redirect, url_for

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        url = request.form.get('urlInput')
        return redirect(url_for('display_url', url=url))
    else:
        return render_template('index.html')


@app.route("/display_url/<path:url>", methods=['GET', 'POST'])
def display_url(url=None):
    if url:
        if request.method == 'POST':
            url2 = request.form.get('urlInput')
            return redirect(url_for('display_url', url=url2))
        else:
            return render_template('index.html', url=url)
    else:
        return redirect(url_for('home'))

if __name__ == "__main__":
    app.run(debug=True)
A small change

Notice that in this block of code, we have added two additional lines that we hadn’t mentioned before:

if __name__ == "__main__":
    app.run(debug=True)

This modification indicates that when the Python script is executed directly, for instance using the command python app.py, the application will initiate in debugging mode. Importantly, these lines will remain inactive when the application is run through other methods, such as flask run, ensuring they are executed only under specific conditions.

HTML - index.html

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='/styles/home.css')}}">
</head>
    <body>
        <div class="content">
            <div class="container">
                <h1>What is your favorite website?</h1>
                <form method="post">  <!-- Specify method="post" for form submission -->
                    <input type="text" name="urlInput" placeholder="Enter URL" class="form-text-input" required>
                    <br>
                    <button type="submit" class="submit-btn">Register URL</button>
                </form>
                
                {% if url %}
                  <div id="confirmationContainer">
                      <p>You have registered the following URL: {{ url }}</p>  <!-- Display URL registration confirmation -->
                  </div>
                {% endif %}
            </div>
        </div>
    </body>
</html>

CSS - home.css

body {
    font-family: Arial, sans-serif; /* Sets the text font for the entire document body */
    margin: 0; /* Removes the default margin */
    padding: 0; /* Removes the default padding */
    height: 100vh; /* Sets the body height to 100% of the viewport */
}

.content {
    display: flex; /* Uses the flexible box model for content */
    justify-content: center; /* Horizontally centers the content */
    align-items: center; /* Vertically centers the content */
    height: 100%; /* Sets the content height to 100% of the viewport */
    background-color: #f0f0f0; /* Sets the background color of the content */
    flex-direction: column; /* Arranges child elements in columns */
}

.form-text-input {
    width: 300px; /* Sets the width of the URL input field */
    padding: 10px; /* Adds padding around the input field */
    font-size: 16px; /* Sets the font size of the input field */
    border: 1px solid #ccc; /* Sets a 1px solid border with a light gray color */
    border-radius: 5px; /* Applies rounded corners to the input field */
    margin-bottom: 20px; /* Adds bottom margin to separate the input field from the button */
}

.submit-btn {
    background-color: #4CAF50; /* Sets the background color of the submit button */
    color: white; /* Sets the text color of the submit button */
    padding: 10px 20px; /* Adds padding around the button text */
    font-size: 16px; /* Sets the font size of the button text */
    border: none; /* Removes the border from the submit button */
    border-radius: 5px; /* Applies rounded corners to the submit button */
    cursor: pointer; /* Changes the cursor to a pointer when hovering over the button */
    transition: background-color 0.3s; /* Adds a smooth transition to the button's background color */
}

.submit-btn:hover {
    background-color: #3e8e41; /* Changes the button's background color when hovering over it */
}

.container {
    text-align: center; /* Centers the text inside the container */
    margin-bottom: 20px; /* Adds bottom margin to separate the form from the result */
}

#confirmationContainer {
    padding: 20px; /* Adds padding around the content of the result container */
    background-color: #f0eaea; /* Sets the background color of the result container */
    border: 1px solid #ccc; /* Sets a 1px solid border with a light gray color */
    border-radius: 5px; /* Applies rounded corners to the result container */
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Adds a shadow effect to the result container */
    margin-top: 20px; /* Adds top margin to visually separate the confirmation container from the form */

}

Summary

In the first post of the data-driven web applications basics, we built the foundation of our web application, by starting its Home page using Flask. We implemented a simple form allowing users to submit their favorite websites, providing them with confirmation upon successful submission.

To achieve this functionality, we leveraged Flask’s routing capabilities to define how the application responds to user interactions. We captured the submitted URL and used Flask’s templating system to display a confirmation message. Additionally, we implemented the Post/Redirect/Get (PRG) pattern to prevent accidental resubmissions when users refresh the page. This involved using Flask’s redirect and url_for functions, along with Flask converters.

While currently, submitted URLs are not stored, the next post will delve into connecting our application to a database. This will enable us to save user-submitted URLs, allowing us to create a new page that dynamically displays the top favorite websites among users.

Back to top