Lab 03 - Advanced Flask

Objectives

  • Advanced Flask concepts
  • Jinja Templates
  • Cookies, Sessions & Authentication
  • Storing and retrieving data from databases
  • Advanced HTTP: file uploading

Contents

Introduction

This lab will take you through more advanced server-side concepts in Flask: templates, sessions, authentication and file upload (bonus).

Template Engines

A typical website has a common HTML design, with only portions of its code changing on a per-page basis with specific content. In order to prevent needless code duplication, a template engine is usually employed to obtain HTML documents from common layouts. A template is, basically, a HTML page interleaved with specific code blocks used to insert dynamically generated content from variables; many engines feature full programming languages that support loops and conditionals.

Flask readily integrates with the Jinja templating engine which uses Python-like statements to enrich a HTML page with programmatic content:

<!-- ... -->
<body>
    <h1>My Webpage is {{ awesome_variable }}</h1>
 
    <ul id="main-menu">
    {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
    </ul>
 
    {# a comment #}
</body>

The Jinja templates usually reside inside the project's templates/ directory (check the Flask documentation if you want to change it) and can be rendered using the render_template utility function.

Persisting sessions. User Authentication

HTTP is a stateless protocol, which means that any request (e.g., from a browser) must not assume that the server knows any of its historic actions.

To persist the state, servers often use the cookie feature to establish so-called sessions, simple key-value pairs which can either be stored inside the client's cache/memory (encrypted, of course) or on server-side (hence client will only store an unique identifier issued by the server, and the server will have a database entry for each client ID with its private session data).

When a session-based request arrives on the server, it reads the cookie, decodes the ID or retrieves the session data from its database and exposes session context variables to the application developers. In flask, the session global var is uses exactly for this:

from flask import ..., session
# ...
@app.route("/")
def account_page():
    if "authenticated" not in session:
        return redirect("/login", code=302) 
    # ...

Since the session data store is properly authenticated and encrypted, it is often used to provide a persistent authentication of the client (e.g., using the results from a login form), though server may also use its session to track the user's behavior (e.g., the pages it opened in the last minute, zones clicked etc.).

Persisting data on the server.

Sessions are often lost when either the server or the client browser is restarted (since they are stored in-memory or limited cache).

If a server wishes to store long-term persistent data, a database is the standard way to ensure data availability.

Databases are often fully-fledged applications in their own right and the web server communicates with them using various inter-process or socket-based channels (though client libraries are always provided), either using separate language (e.g., SQL) or specific/proprietary APIs (no-SQL).

But, for today, a simple disk-based file stored on the server will suffice (you should know how to do this in Python) ;)

Preparation

Reminder to create your virtualenv and install Flask inside:

# either you or your IDE should create the Python Virtual Environment, e.g.:
python3 -m venv .venv
# activate it (note: you will need to do this every time you change your shell)!
source .venv/bin/activate
# finally, install libraries using pip
python3 -m pip install flask

Tasks

01. [40p] Splitting web pages into Jinja2 templates

First, download the lab archive.

We now want to move the design from last time (a static html file) to use Flask Jinja2-based template rendering.

For this, you must:

  • Migrate / split the HTML code into templates/_base.html (base template) and templates/index.html (child);
    • Note: use Jinja2 template inheritance and content blocks;
    • Make sure to eliminate any duplicate code (the _base.html template should contain the common layout of all pages)!
  • Modify server.py to call render_template from router functions to actually serve our new design;
  • Also create second.html and fill the Flask function to serve it (fill it with whatever content you want, like in the previous lab);
  • Finally, fix the URLs in the base template's menu to point to the appropriate pages.

02. [30p] Mock Authentication

Now, it's the time to add authentication to our website.

  • Use a server-side session to store the user's state (e.g., an authenticated bool + username string keys).
    • Don't forget to set a secret_key for the session, otherwise it won't work!
  • Write the Flask functions for /login and /logout with the appropriate checks / actions.
  • You can either use the included login.html template page, or roll out your own!
    • Check out the input names inside the <form>!
  • Hint: you can also set the authenticated variable inside the Jinja template to conditionally display the user's status.
    • You can use a Flask context processor to avoid needless code duplication;
    • Alternative: the session is readily available inside Jinja2 templates!

03. [30p] Advanced: File Database

We want to store some of the user's personal data into a simple file-based “database” on the server.

  • Check the account-details.html template page for any TODOs there!
    • Ultimately, the form inputs should be pre-filled with the values stores inside the database!
    • Use the value HTML attribute on the <input> tags to write them from Jinja templates;
  • Implement the /account-details route handler to read / write to the database.txt file on the server;
    • You will have to support both GET and POST, so make sure to edit the decorator's arguments!

04. [20p] Bonus: file upload

Bonus task: add a upload image input inside the account-details that uploads a file to the public/images/profile subfolder (also: create it!):

  • Use type=“file” attribute on the <input>;
  • Use multipart/form-data encoding type attribute on the <form> (Google it!);
  • Inside the Flask route function:
  • Finally, add an <img> tag to display the profile photo on the user's account page when everything is done!
ii/labs/s2/03.txt · Last modified: 2024/04/01 21:58 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0