This shows you the differences between two versions of the page.
|
ii:lab:laborator5 [2016/10/03 20:42] iulian_gabriel.radu [Exerciții și aplicații] |
ii:lab:laborator5 [2021/01/20 08:51] (current) florin.stancu [Exerciții] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Laborator 5 - GDB ====== | + | ====== 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''). | ||