====== Python Web amb Flask ====== Python és un llenguatge amb una gran implantació en l'àmbit acadèmic, de recerca, en particular en intel·ligència artificial, però que també té excel·lents eines per treballar el desenvolupament web. A més, és el llenguatge que més ha prosperat en els darrers anys. Aquest article resumeix una petita introducció ràpida a com utilitzar el [[https://flask.palletsprojects.com/|microframework Flask]] sobre una màquina Linux Ubuntu/Debian. Python disposa de molts frameworks i microframeworks per web. Alguns dels més coneguts: - **Flask** : essencial, però amb tot el necessari i ben mantingut - **Django** : //the framework for perfectionists with deadlines// , permet el desenvolupament molt ràpid, amb eines com el ORM i un admin panel molt potent. - **CherryPy** : microframework - **Bottle** : microframework Referències: * Per desenvolupar en Python necessitarem [[Python venv]] * L'article segueix a [[Python Web Test]] {{tag> #Daw #DawMpDual #DawMp07Uf1 #DawMp07Uf02 #DawMp07Uf03 #DawMp07Uf04 #DawMp07Uf1 #DawMp07Uf02 #DawMp07Uf03 #DawMp07Uf04 #Ciber #CiberMp03 #Ceti #CetiMp03 python web flask }} \\ ===== Videotutorials ===== Aquest article també el tens en [[https://www.youtube.com/watch?v=bHg1b0XcU3g|dues parts en format vídeotutorial a Youtube]] explicat pas a pas. {{youtube>bHg1b0XcU3g}} {{youtube>pv1_CPLg4xw}} \\ ===== Instal·lació ===== Necessitem Python 3, que ja sol venir instal·lat. Si no fos així, pots instal·lar-ho amb: $ sudo apt install python3 Necessitem crear un [[python_venv|entorn virtual de Python]]. La manera més senzilla és: $ python -m venv env I podem activar el nostre entorn amb: $ source env/bin/activate Per saber més sobre els entorns virtuals de Python visiteu l'article [[python_venv]]. \\ ===== Creant el primer projecte ===== Iniciarem el nostre projecte web creant una simple carpeta: $ mkdir pyweb1 $ cd pyweb1 Dins el nostre projecte crearem un virtualenv on posarem les nostres llibreries: $ python -m venv env $ source env/bin/activate (env) $ pip3 install flask Ara ja podem mirar [[https://flask.palletsprojects.com/quickstart|el quickstart de la documentació de Flask]]. Crearem l'arxiu amb el codi super simple de //Hello World// a l'arxiu ''web.py'': (env) $ gedit web.py & I ara podrem arrencar la aplicació //Hello World// amb: (env) $ flask --app web run Podeu visitar la web //Hello World// apuntant el vostre navegador a ''http://localhost:5000'' \\ ===== Avançant amb Flask ===== ==== Routing ==== Si arribeu a la secció [[https://flask.palletsprojects.com/en/1.1.x/quickstart/#routing|Routing]] entendreu com fer que la nostra aplicació web pugui tenir el què anomenem diversos **punts d'entrada**. Ara podem entrar a http://localhost:5000/ i per http://localhost:5000/hello Com podeu veure a l'exemple, per crear un nou punt d'entrada (accés per una URL) hem de crear una funció (**ULL! no repetir nom de funcions!!**) precedida per un **decorator (app.route)**: #... codi previ... @app.route('/hello') def hello(): return 'Hello again!' #...mes codi... ==== Mètodes [GET,POST,+] ==== A més, cal tenir en compte que cada URL a la que accedim, ho podem fer amb diferents mètodes. A la pràctica ho tractem com si fossin URLs independents. L'exemple típic és el d'un formulari en 2 parts: * GET: és el mètode per defecte, i sol mostrar un formulari estàtic * POST: un cop omplert el formulari, processem les dades Per accedir a les dades que se'ns envia via POST, hem d'accedir mitjançant l'[[https://flask.palletsprojects.com/quickstart/#the-request-object|objecte request de la pròpia llibreria de Flask]], tal i com es pot veure en el següent exemple, utilitzant ''request.form['nom']'': # si no especifiquem res al decorator, és el mètode GET @app.route('/formulari') def formulari_get(): # mostrem el formulari return """
Introdueix el teu nom:
""" # important importar la request per accedir a les dades adjuntes from flask import request @app.route('/formulari', methods=['POST']) def formulari_post(): # processem les dades del formulari nom = request.form["nom"] return "Salut, {}".format(nom)
==== Exercici ==== * Crea un formulari que demani usuari i contrasenya. El formulari s'accedeix per mètode GET però envia les dades per mètode POST. * Crea una pàgina (per POST) que respongui al formulari d'usuari i contrasenya. Crea un diccionari global amb noms d'usuari i contrasenyes vàlids (//hardcoded//). * Assegura't que la pàgina de resposta al formulari respon amb un missatge que contingui un "OK" a la pàgina, i si el nom d'usuari és erroni respon adequadament amb un missatge que contingui "ERROR". \\ ===== Variables globals (shhht! q no ho senti ningú!) ===== En un entorn web no és recomanable utilitzar variables globals. O més aviat, no podem utilitzar-les com solem fer-ho en aplicacions d'escriptori. Les aplicacions web s'executen en algun servidor web (Apache, Nginx) o servidor d'aplicacions (uwsgi, gunicorn, Heroku). De fet, sí que es poden fer servir, però no es pot confiar en què en la següent execució la variable global es mantingui. El servidor web, depenent de la seva càrrega, pot destruir el procés d'execució de la teva aplicació web, i quan tornes a sol·licitar la web, es torna a posar en marxa tot des de zero (i, per tant, destruint les variables globals). A més, també hi ha una qüestió de concurrència: el servidor web pot disposar de diferents //threads// o fils d'execució per tal de paral·lelitzar les sol·licituds en diferents CPUs (amb el què aconseguim més capacitat de processament). Si féssim servir variables globals, la informació en diferents instants ens donaria resultats incoherents. Conclusió: si volem **persistència** en un entorn web, estem obligats a utilitzar fitxers o bases de dades. \\ ===== Utilitzant plantilles (templates) ===== Necessitem un mecanisme més sofisticat per crear contingut HTML i que no sigui fent un return d'un string. **Una de les bones pràctiques més transversals a la majoria de //frameworks// és separar nítidament el codi de processament de la renderització HTML**. Per tant, no és una bona pràctica posar contingut HTML dins l'arxiu .py en el què estem treballant. Per això, separarem les pàgines HTML en les plantilles o //templates// i que renderitzarem des del codi. Per veure un exemple i un resum de les possibilitats que ens ofereixen les plantilles Jinja2, podeu mirar-vos aquestes fonts: * L'apartat [[https://flask.palletsprojects.com/quickstart/#rendering-templates|"rendering templates" del quickstart de Flask]]. * La [[https://jinja.palletsprojects.com/templates/|documentació oficial de les plantilles Jinja2]] , que són les que Flask fa servir per defecte. {{youtube>pv1_CPLg4xw}} ==== Exemple : carro de compra ==== Com a exemple d'ús de les plantilles agafarem un carro de la compra d'un e-commerce. No serà del tot funcional, ja que ens falta el mecanisme de les sessions per poder gestionar la concurrència al servidor, i distingir entre els diferents clients. Farem 2 rutes: * /compra GET : ens mostrarà el formulari. * /compra POST : processarem les dades enviades al formulari. Crea una aplicació mínima com la de l'exemple i afegeix aquestes dues rutes: @app.route('/compra') def compra_get(): return render_template('compra_form.html') @app.route('/compra',methods=["POST"]) def compra_post(): return render_template('compra_post.html') Com podeu veure, ara ja no creem el HTML dins el codi de l'arxiu '.py' sinó que el posarem en **arxius HTML que cal que estiguin a la carpeta ''/templates''** . Aquests son els arxius compra_form.html i compra_post.html

Gràcies per la compra!

==== Renderitzar dades a una plantilla amb {{ ... }} ==== Per millorar l'exemple primer de tot personalitzarem el missatge POST on recollim el producte comprat. El què voldrem és dir-li a l'usuari quin producte i quantitat han estat afegits al carro: from flask import request @app.route('/compra',methods=["POST"]) def compra_post(): prod = request.form['producte'] quant = request.form['quantitat'] return render_template('compra_post.html', producte=prod, quantitat=quant)

Gràcies per la teva compra!

Has comprat {{quantitat}} kg de {{producte}}

Ara pots provar l'aplicació i veuràs que les dades que s'envien del formulari es mostren a la 2a pàgina. Observa que per passar les dades el què fem és passar-les com a //keyworded arguments// al ''render_template'', i els renderitzem amb {{}} ==== Utilitzar codi dins de les plantilles amb {% ... %} ==== Pot semblar una contradicció que hi hagi codi dins d'una plantilla, però en realitat és necessari per renderitzar amb facilitat. No és codi de processament, és codi relacionat amb la renderització. Pel nostre cas, es tracta de mostrar els productes que tenim disponibles de forma dinàmica. Per seguir, convé fer-li un cop d'ull a la [[http://jinja.pocoo.org/|documentació de les plantilles Jinja2]]. Observa l'exemple que surt: en poques línies ens demostren la majoria de funcions que ens cal per a les plantilles. Pel nostre exemple de la botiga, farem una simulació de que disposem d'una llista de productes (en una variable global ''productes'') i l'enviarem a la plantilla per tal que sigui renderitzada en el ''select'' del formulari: productes = ['Cogombres','Pomes','Kiwis'] @app.route('/compra') def compra_get(): return render_template('compra_form.html', prods=productes) I renderitzarem la llista de productes en les diferents ''options'' dins la plantilla ''compra_form.html'':

Comprova que, en efecte funciona la plantilla, canviant la llista de productes i fent-la més llarga. ==== Plantilles like a PRO ==== Les plantilles Jinja2 ofereixen moltes possibilitats tal i com s'explica a la [[https://jinja.palletsprojects.com/en/2.11.x/templates/|doc oficial de Jinja Templates]]. Destaco els 2 recursos més habituals d'utilitzar, sobretot quan es tracta de tenir una mateixa imatge corporativa en totes les pàgines de l'aplicació: * [[https://jinja.palletsprojects.com/en/2.11.x/templates/#include|Includes]] és una senzilla manera d'afegir capçalera i //footers// iguals a totes les nostres pàgines. Us recomano utilitzar això per començar. * [[https://jinja.palletsprojects.com/en/2.11.x/templates/#template-inheritance|Herència de plantilles]] és un mètode una mica més sofisticat amb alguns avantatges, però costa una mica més d'entendre. Cal definir una pàgina //master// dividida en ''blocks'' o seccions, i després derivar les pàgines filles (on podem definir o sobreescriure cada ''block''). \\ ===== Per a saber-ne més ===== Hem fet un repàs de les funcions bàsiques del //framework// Flask. Ens queda un parell de temes per cobrir tots els temes essencials, i que podeu seguir investigant en el propi [[https://flask.palletsprojects.com/quickstart/|quickstart de Flask]]: * Persistència en base de dades * Sessions També hi ha aquest cursos que poden ser interessants: * [[https://courses.miguelgrinberg.com/p/flask-mega-tutorial|mega-tutorial de Flask de Miguel Grinberg]] que té un índex prometedor. Podeu fer un cop d'ull a les introduccions, la resta del curs és de pagament. * [[https://platzi.com/cursos/flask/|Aquest curs de Flask de Plazi.com]] pinta molt bé, amb videotutorials en castellà. \\ ===== Exercici : login ===== Pàgina de login de l'app: * Crea un formulari que demani usuari i contrasenya. El formulari s'accedeix per mètode GET però envia les dades per mètode POST. * Crea una pàgina (per POST) que respongui al formulari d'usuari i contrasenya. Crea un diccionari global amb noms d'usuari i contrasenyes vàlids (//hardcoded//). * Assegura't que la pàgina de resposta al formulari respon amb un missatge que contingui un "OK" a la pàgina, i si el nom d'usuari és erroni respon adequadament amb un missatge que contingui "ERROR". * **Comprova manualment el punt anterior. Abans de fer els tests automàtics cal fer-los manuals PER A TOTS ELS CASOS** (tant si funciona OK, com si dona error). \\ ===== Testing i + ===== Aquest article continua a [[Python Web Test]] i a [[Docker Flask]]. \\