Table of Contents

Laborator 5 - Python Flask

Obiective

În cadrul acestul laborator vom explora framework-ul web Flask și vom dezvolta o aplicație web minimală.

Introducere

Un framework oferă unui programator o structură predefinită cu ajutorul căruia acesta poate dezvolta rapid aplicații. De exemplu, un framework web implementează logica necesară stabilirii conexiunilor dintre server și clienți, și îi permite astfel programatorului să se concentreze pe logica aplicației și conținutul pe care îl oferă clienților.

Folosind Flask putem dezvolta o aplicație de tip server web folosind limbajul Python.

Instalare

Pentru a instala Flask vom folosi managerul de pachete al limbajului Python, pip:

$ pip install flask

Pentru a verifica instalarea, deschidem interpretorul python, și importăm modulul flask:

$ python
 
>>> import flask
>>>

Dacă nu vă merge (diferite setup-uri), încercați una dintre comenzile echivalente:

pip3 install ...  # pip pentru python3, dacă este aliasat
python3 -mpip install ...  # pentru python3
 
python -mpip install ...  # pentru python2

Hello World

Vom crea un director numit hello-flask în directorul nostru HOME:

$ mkdir ~/hello-flask
$ cd ~/hello-flask

În cadrul acestui director, creăm un fișier hello.py cu conținutul de mai jos:

hello.py
from flask import Flask
 
app = Flask(__name__)
 
@app.route("/")
@app.route("/index")
def hello():
    return "Hello World from Flask"
 
if __name__ == "__main__":
    # Only for debugging while developing
    app.run(host='0.0.0.0', debug=True, port=5000)

Apoi pornim serverul web:

$ python hello.py
 * Serving Flask app "hello" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 850-713-158

Acum putem accesa adresele http://127.0.0.1:5000 și http://127.0.0.1:5000/index.

Să trecem pas cu pas prin cod:

from flask import Flask
 
app = Flask(__name__)

Snippetul de mai sus importă framework-ul Flask și îl inițializează.

Partea cu adevărat interesantă se întâmplă în liniile următoare:

@app.route("/")
@app.route("/index")
def hello():
    return "Hello World from Flask"

Serverul nostru este găzduit pe mașina noastră locală, și este accesibil la adresa 127.0.0.1 folosind portul 5000; deci pagina noastră este accesibilă la adresa http://127.0.0.1:5000.

Folosind sintaxa @app.route definim funcțiile care vor fi apelate în funcție de calea pe care a introdus-o utilizatorul în browser. Astfel:

Pentru ambele căi, atunci când utilizatorul va accesa pagina, serverul va întoarce rezultatul definit de funcție, în cazul de față “Hello World from Flask”.

Sintaxa @app.route poartă numele de decorator în Python.

Decoratorii nu intră în scopul laboratorului, dar vă încurajăm să citiți mai multe despre aceștia aici.

Observăm că putem adăuga mai multe căi pentru o funcție.

Numele asociat instanței framework-ului Flask este important pentru sintaxa route.

Dacă instanțiam cu alt nume, ex. myapp = Flask(name), atunci trebuie să folosim acest nume (@myapp.route) pentru definirea căilor.

În final, pornim serverul:

    app.run(host='0.0.0.0', debug=True, port=5000)

Templates

În secțiunea anterioară, am văzut cum putem face serverul să servească conținut clienților. Deocamdată conținutul este unul banal: textul “Hello World from Flask”. În continuare vom vedea cum putem returna pagini întregi.

Folosind template-uri putem servi pagini HTML complete, cu CSS și JavaScript.

Creăm directorul templates în directorul hello-flask:

$ cd ~/hello-flask
$ mkdir templates

Acum vom crea fișierul templates/index.html cu următorul conținut:

index.html
<html>
    <head>
        <title> {{ title }} </title>
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

Fișierul de mai sus este un template Jinja2. Template-urile Jinja ne oferă un mod simplu prin care putem să generăm conținut folosind cod python.

În exemplul de mai sus, textul din interiorul {{}} va fi înlocuit cu variabile pe care le vom trimite din codul nostru python. Pentru aceasta, modificăm funcția hello() în modul următor:

@app.route("/")
@app.route("/index")
def hello():
    user = {'username': 'Student'}
    return render_template('index.html', title='Home', user=user)

Funcția render_template() va prelucra template-ul Jinja2 definit mai sus, va înlocui variabilele din cadrul {{}} și va oferi clientului pagina HTML validă.

După ce am făcut modificările, accesăm pagina http://127.0.0.1:5000.

Structuri de control

În cadrul template-urilor Jinja putem folosi și structuri de control precum if și for. Sintaxa pentru acestea este următoarea:

<html>
    <head>
        {% if title %}
        <title>{{ title }}</title>
        {% else %}
        <title>Welcome</title>
        {% endif %}
    </head>
    <body>
        {% for user in users %}
        <div><p>{{ user.username }}</p></div>
        {% endfor %}
    </body>
</html>

În cadrul {{}} folosim cod python.

Extinderea unor template-uri de bază

Majoritatea paginilor unei aplicații web conțin componente comune (cum ar fi bara de navigație). Pentru a evita scrierea repetată a componentelor comune în fiecare pagină, putem folosi template inheritance: componentele comune sunt scrise într-un template de bază care este apoi extins de fiecare pagină.

Pentru aceasta, în directorul ~/hello-flask/templates definim fișierul base.html, ca mai jos:

base.html
<html>
    <head>
      {% if title %}
      <title>{{ title }}</title>
      {% else %}
      <title>Welcome</title>
      {% endif %}
    </head>
    <body>
        <div><a href="/index">Home</a></div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

Fișierul template de mai sus este acum baza noastră. El definește structura generală a paginii. Orice pagină nouă o să extindă această bază și apoi trebuie să definească doar blocul content.

Astfel, modificăm fișierul ~/hello-flask/templates/index.html ca mai jos:

index.html
{% extends "base.html" %}
 
{% block content %}
    <h1>Hello, {{ user.username }}!</h1>
{% endblock %}

Bootstrap

Bootstrap este un framework foarte popular folosit pentru a crea interfețe cu utilizatorul (UI).

Pentru a folosi Bootstrap, adăugăm următoarele linii în fișierul base.html

base.html
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
 
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
        <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" rel="stylesheet">
 
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
 
        {% if title %}
        <title>{{ title }}</title>
        {% else %}
        <title>Welcome</title>
        {% endif %}
    </head>
    <body>
        <div><a href="/index">Home</a></div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

Acum putem folosi Bootstrap pentru a ne înfrumuseța pagina. Începem prin a centra textul din fișierul index.html:

index.html
{% extends "base.html" %}
 
{% block content %}
    <h1 class="text-center">Hello, {{ user.username }}!</h1>
{% endblock %}

Observăm că a fost necesar doar să adăugăm clasa text-center elementului h1. Acesta este modul de funcționare Bootstrap: tot ce trebuie făcut este să includem elementele în clasa pe care ne-o dorim.

Bootstrap împarte (imaginar) pagina în 12 coloane. Noi apoi decidem, prin intermediul atributului class, câte din aceste coloane vor ocupa elementele noastre.

De exemplu:

<div class="container">
  <div class="row">
    <div class="col-sm">
      One of three columns
    </div>
    <div class="col-sm">
      One of three columns
    </div>
    <div class="col-sm">
      One of three columns
    </div>
  </div>
</div>

Exemplul de mai sus va împărți lățimea rândului în mod egal între cele trei coloane.

Bootstrap Icons

Bootstrap ne oferă și un set de imagini SVG pe care le putem folosi pe post de icons.

Ca să înlocuim textul Home din fișierul base.html cu un icon, trebuie să:

Astfel, textul

<div><a href="/index">Home</a></div>

Devine

<div><a href="/index"><i class="bi bi-house"></i></a></div>

Exerciții

O să scrieți o aplicație web care are rolul unui CV. CV-ul trebuie să conțină secțiunile: Work Experience, Education, Projects și Languages and Technologies

1. [10] Aplicația trebuie să aibă două pagini: Home (CV-ul) și Contact

2. [30] Informațiile secțiunilor din CV sunt populate de către server, și nu sunt scrise în template-ul Jinja. Informațiile secțiunilor sunt stocate sub formă de dicționar. De exemplu:

work_exp = [ { position: "swe", company: "xyz", period: "Jun 2020 - Oct 2020" } ]

3. [10] Folosiți icon-uri pentru Home și Contact

4. [20] Folosiți componenta Bootstrap Navbar pentru butoanele Home și Contact

5. [30] Pagina de contact trebuie să conțină și un formular prin intermediul căruia un utilizator să vă poată contacta. Formularul trebuie să conțină câmpurile: Nume, Adresă de e-mail și Mesaj. Atunci când utilizatorul apasă butonul Trimite al formularului, serverul flask vă va [simula că] trimite un e-mail cu informațiile formularului prin scrierea unui fișier pe server (e.g., contact-mail.txt).