This shows you the differences between two versions of the page.
ii:lab:laborator5 [2016/10/07 17:33] iulian_gabriel.radu |
ii:lab:laborator5 [2021/01/20 08:51] (current) florin.stancu [Exerciții] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 5 - Multimedia Logic ====== | + | ====== Laborator 5 - Python Flask ====== |
+ | |||
+ | ===== Obiective ===== | ||
+ | |||
+ | În cadrul acestul laborator vom explora framework-ul web [[https://flask.palletsprojects.com/en/1.1.x/ | Flask]] și vom dezvolta o aplicație web minimală. | ||
===== Introducere ===== | ===== Introducere ===== | ||
- | TODO | + | 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**: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | $ pip install flask | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Pentru a verifica instalarea, deschidem interpretorul **python**, și importăm modulul **flask**: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | $ python | ||
+ | |||
+ | >>> import flask | ||
+ | >>> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | <note important> | ||
+ | Dacă nu vă merge (diferite setup-uri), încercați una dintre comenzile echivalente: | ||
+ | <code bash> | ||
+ | pip3 install ... # pip pentru python3, dacă este aliasat | ||
+ | python3 -mpip install ... # pentru python3 | ||
+ | |||
+ | python -mpip install ... # pentru python2 | ||
+ | </code> | ||
+ | </note> | ||
+ | |||
+ | ===== Hello World ===== | ||
+ | |||
+ | Vom crea un director numit **hello-flask** în directorul nostru **HOME**: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | $ mkdir ~/hello-flask | ||
+ | $ cd ~/hello-flask | ||
+ | |||
+ | </code> | ||
+ | |||
+ | În cadrul acestui director, creăm un fișier **hello.py** cu conținutul de mai jos: | ||
+ | |||
+ | <code python 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) | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Apoi pornim serverul web: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | $ 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 | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | from flask import Flask | ||
+ | |||
+ | app = Flask(__name__) | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Snippetul de mai sus importă framework-ul **Flask** și îl inițializează. | ||
+ | |||
+ | Partea cu adevărat interesantă se întâmplă în liniile următoare: | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | @app.route("/") | ||
+ | @app.route("/index") | ||
+ | def hello(): | ||
+ | return "Hello World from Flask" | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | * **@app.route("/")** corespunde căii principale către pagina noastră web, [[http://127.0.0.1:5000/]] | ||
+ | * **@app.route("/index")** corespunde căii [[http://127.0.0.1:5000/index]] | ||
+ | |||
+ | 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". | ||
+ | |||
+ | <note tip> | ||
+ | |||
+ | 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 [[https://pythonguide.readthedocs.io/en/latest/python/decorator.html | aici]]. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | Observăm că putem adăuga mai multe căi pentru o funcție. | ||
+ | |||
+ | <note warning> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | În final, pornim serverul: | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | app.run(host='0.0.0.0', debug=True, port=5000) | ||
+ | |||
+ | </code> | ||
+ | |||
+ | ===== 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**: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | $ cd ~/hello-flask | ||
+ | $ mkdir templates | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Acum vom crea fișierul **templates/index.html** cu următorul conținut: | ||
+ | |||
+ | <code html index.html> | ||
+ | |||
+ | <html> | ||
+ | <head> | ||
+ | <title> {{ title }} </title> | ||
+ | </head> | ||
+ | <body> | ||
+ | <h1>Hello, {{ user.username }}!</h1> | ||
+ | </body> | ||
+ | </html> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Fișierul de mai sus este un template [[https://jinja.palletsprojects.com/en/2.11.x/|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: | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | @app.route("/") | ||
+ | @app.route("/index") | ||
+ | def hello(): | ||
+ | user = {'username': 'Student'} | ||
+ | return render_template('index.html', title='Home', user=user) | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code html> | ||
+ | |||
+ | <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> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | <note tip> | ||
+ | |||
+ | În cadrul **{****{** ... **}****}** folosim cod python. | ||
+ | |||
+ | </note> | ||
+ | |||
+ | ==== 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: | ||
+ | |||
+ | <code html 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> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code html index.html> | ||
+ | |||
+ | {% extends "base.html" %} | ||
+ | |||
+ | {% block content %} | ||
+ | <h1>Hello, {{ user.username }}!</h1> | ||
+ | {% endblock %} | ||
+ | |||
+ | </code> | ||
+ | |||
+ | ===== Bootstrap ===== | ||
+ | |||
+ | [[https://getbootstrap.com/docs/5.0/getting-started/introduction/|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** | ||
+ | |||
+ | <code 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> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Acum putem folosi Bootstrap pentru a ne înfrumuseța pagina. | ||
+ | Începem prin a centra textul din fișierul **index.html**: | ||
+ | |||
+ | <code html index.html> | ||
+ | |||
+ | {% extends "base.html" %} | ||
+ | |||
+ | {% block content %} | ||
+ | <h1 class="text-center">Hello, {{ user.username }}!</h1> | ||
+ | {% endblock %} | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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 [[https://getbootstrap.com/docs/5.0/layout/grid/|coloane]]. | ||
+ | Noi apoi decidem, prin intermediul atributului **class**, [[https://getbootstrap.com/docs/5.0/layout/grid/|câte din aceste coloane vor ocupa elementele noastre]]. | ||
+ | |||
+ | De exemplu: | ||
+ | |||
+ | <code html> | ||
+ | |||
+ | <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> | ||
+ | |||
+ | </code> | ||
+ | |||
+ | Exemplul de mai sus va împărți lățimea rândului în mod egal între cele trei coloane. | ||
- | ===== Rulare GDB ===== | + | ==== Bootstrap Icons ==== |
- | TODO | + | Bootstrap ne oferă și un set de imagini SVG pe care le putem folosi pe post de [[https://icons.getbootstrap.com/|icons]]. |
- | ===== Comenzi de bază GDB ===== | + | Ca să înlocuim textul **Home** din fișierul **base.html** cu un icon, trebuie să: |
+ | * identificăm icon-ul unei [[https://icons.getbootstrap.com/icons/house/|case]] | ||
+ | * înlocuim textul **Home** cu textul din **Icon font**. | ||
- | TODO | + | Astfel, textul |
+ | |||
+ | <code html> | ||
+ | <div><a href="/index">Home</a></div> | ||
+ | </code> | ||
+ | |||
+ | Devine | ||
+ | |||
+ | <code html> | ||
+ | <div><a href="/index"><i class="bi bi-house"></i></a></div> | ||
+ | </code> | ||
- | ===== Exerciții și aplicații ===== | ||
- | - Folosiți gdb pentru a depana următorul program C: <code c divide.c> | + | ===== Exerciții ===== |
- | #include <stdio.h> | + | |
- | int divide(int a, int b) | + | 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 |
- | printf("Dividing %d by %d\n", a, b); | + | |
- | return a / b; | + | |
- | } | + | |
- | int main(void) | + | 1. **[10]** Aplicația trebuie să aibă două pagini: Home (CV-ul) și Contact |
- | { | + | |
- | int x, y, result; | + | |
- | x = 10; y = 2; | + | 2. **[30]** Informațiile secțiunilor din CV sunt populate de către server, și nu sunt scrise în template-ul Jinja. |
- | result = divide(x, y); | + | Informațiile secțiunilor sunt stocate sub formă de dicționar. |
- | printf("%d\n", result); | + | De exemplu: |
+ | <code python> | ||
+ | work_exp = [ { position: "swe", company: "xyz", period: "Jun 2020 - Oct 2020" } ] | ||
+ | </code> | ||
- | x = 5; y = 0; | + | 3. **[10]** Folosiți icon-uri pentru Home și Contact |
- | result = divide(x, y); | + | |
- | printf("%d\n", result); | + | |
- | return 0; | + | 4. **[20]** Folosiți componenta Bootstrap [[https://getbootstrap.com/docs/5.0/components/navbar/|Navbar]] pentru butoanele Home și Contact |
- | }</code> | + | |
- | - TODO | + | |
+ | 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''). |