Master1 ère année Réseaux avancés I TP nº5 filière ISICG Utilisation de la microplateforme Bottle et du framework AngularJS La «micro web-framework» Bottle pour Python Les principales caractèristiques : Routing : lien entre URLs dynamique et appels de fonctions ; Templating : intégration de template et support de bibliothèques externes de templating comme jinja ; Utilities : accès facilité aux données d un formulaire, à la transmission de fichier, aux cookies, et entêtes et méta-données HTTP ; Server : serveur intégré HTTP pour le développement et intégration possible dans des serveurs supportant le WSGI, «Web Server Gateway Interface», protocole permettant la communciation entre serveur Web et application externes ; disponible sur http://bottlepy.org/docs/dev/ ; facile à installer : $ sudo apt-get install python-setuptools $sudo easy_install bottle Exemple d utilisation : 1 from bottle import route, run, template 3 @route('/hello/<name>') 4 def index(name='world'): 5 return template('<b>hello 6 {{name}}</b>!', name=name) 7 8 run(host='', port=8080, debug = True) Accès dans le navigateur : http://localhost:8080/hello/world Utilisation de SQLite dans Python SQLite permet d utiliser un sous-ensemble de SQL pour gérer une BD réduite à un seul fichier. Il est très utilisé pour des applications légères et/ou embarquées (IOS, Android, etc.). Exemple tiré de http://zetcode.com/db/sqlitepythontutorial/ : 1 #!/usr/bin/python 3 import sqlite3, sys 4 5 try: 6 connexion = sqlite3.connect('voitures.db') 7 cur = con.cursor() 8 cur.execute("create TABLE Cars(Id INT, Name TEXT, Price INT)") 9 cur.execute("insert INTO Cars VALUES(1,'Audi',564)") 10 cur.execute("insert INTO Cars VALUES(,'Mercedes',5717)") 11 cur.execute("insert INTO Cars VALUES(3,'Skoda',9000)") 1 cur.execute("insert INTO Cars VALUES(4,'Volvo',9000)") 13 cur.execute("insert INTO Cars VALUES(5,'Bentley',350000)") 14 cur.execute("insert INTO Cars VALUES(6,'Citroen',1000)") 15 cur.execute("insert INTO Cars VALUES(8,'Volkswagen',1600)") 16 connexion.commit() 17 connexion.close() 18 except sqlite3.error, message : 19 print message 0 sys.exit(1) Vous utiliserez ce programme pour remplir la BD pour l application d exemple. Pour consulter le contenu de la BD : Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! 1/6
1 #!/usr/bin/python 3 import sqlite3, sys 4 5 try: 6 connexion = sqlite3.connect('voitures.db') 7 8 cur = connexion.cursor() 9 cur.execute('select Id, Name, Price FROM Cars') 10 nom_colonnes = [d[0] for d in cur.description] 11 print nom_colonnes 1 for ligne in cur: 13 print ligne 14 connexion.close() 15 except sqlite3.error, message: 16 print message 17 sys.exit(1) Pour consulter le contenu de la BD et le traduire en représentation JSON : 1 #!/usr/bin/python 3 import sqlite3, json, sys 4 5 try: 6 connexion = sqlite3.connect('voitures.db') 7 8 cur = connexion.cursor() 9 cur.execute('select Id, Name, Price FROM Cars') 10 11 nom_colonnes = [d[0] for d in cur.description] 1 table = [dict(zip(nom_colonnes, ligne)) for ligne in cur] 13 print json.dumps(table) 14 15 except sqlite3.error, message: 16 print message 17 sys.exit(1) 18 19 connexion.close() L affichage obtenu de la représentation JSON : [{"Price": 564, "Id": 1, "Name": "Audi"}, {"Price": 5717, "Id":, "Name": "Mercedes"}, {"Price": 9000, "Id": 3, "Name": "Skoda"}, {"Price": 9000, "Id": 4, "Name": "Volvo"}, {"Price": 350000, "Id": 5, "Name": "Bentley"}, {"Price": 1000, "Id": 6, "Name": "Citroen"}, {"Price": 1600, "Id": 8, "Name": "Volkswagen"}] Utilisation d AngularJS AngularJS est un framework Javascript proposé par Google permettant de disposer d un MVC, «Model-View-Control». Il permet de : réaliser du «data-binding», c-à-d de lier dynamiquement des valeurs à leur représentation dans le navigateur Web : toute modification de l interface modifie les données et vice-versa ; d automatiser la création des éléments constituants l interface dans le navigateur ; de gérer les échanges entre l application serveur et l interface au travers du format d échange JSON ; d accrocher et de déclencher des événements lors de la modification des éléments (par exemple : déclencher l envoi d une requête HTTP lors de l appui d un bouton). Voici l arborescence d une application d exemple d utilisation :. -- creation_bd.py -- static -- gestion_voitures.html -- gestion_voitures.js -- voiture.py -- voitures.db Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! /6
Soit le contenu du fichier gestion_voitures.js 1 angular.module('voituresapp',['ui.bootstrap']); 3 function AfficherVoitures($scope,$rootScope,$http) { 4 $rootscope.infos = { voiture_selectionnee : "aucune", liste_voitures : '', 5 transmission : ''}; 6 $http.get("/liste_voitures").success(function(data) { 7 $rootscope.infos.liste_voitures = data; }); 8 transmettre = function() { 9 $http.get("/selectionner/"+$rootscope.infos.voiture_selectionnee).success(function() 10 { $rootscope.infos.transmission = "Ok"; }); 11 } 1 $scope.$watch('infos.voiture_selectionnee', transmettre); Ce fichier est à mettre dans le sous répertoire static. Explications : ligne 1 : le nom de l application AngularJs qui est repris dans la ligne 1 du document HTML associé et la liste des modules utilisés : ici les éléments d interface de ui.bootstrap ; ligne : on définit une fonction que l on va associer à un controlleur AngularJS (ligne 10 du document HTML associé) : en paramètres, on utilise des éléments d AngularJS : $scope : accès aux variables du «scope», c-à-d celles définies dans le contexte du controlleur AngularJS courant ; $rootscope : accès aux variables du «scope» racine, c-à-d en dehors de tous les controlleurs existants ; $http : accès aux requêtes http et récupération de contenu au format JSON ; ligne 4 : on défini un dictionnaire infos qui recevra : «liste_voitures» : un dictionnaire correspondant au contenu de la BD ; «voiture_selectionnee» : une variable d échange entre l interface et l application Web ; ligne 6 : on récupére le contenu de la BD au format JSON fournie par l application Web ; Ce contenu va être utilisé pour construire l interface dans le navigateur. ligne 8 : on crée une fonction qui sera appelée à chaque modification de la variable «infos.voiture_selectionnee» elle récupère le contenu de la variable modifiée ; elle le transmet à l application Web ; ligne 11 : le «watch» accroche la fonction précédente aux modifications de la variable. Soit le contenu du fichier gestion_voitures.html : 1 <html ng-app="voituresapp"> <head> 3 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> 4 <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.3.0.js"></script> 5 <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/.3.1/css/bootstrap-combined.min.css" 6 rel="stylesheet"> 7 <script src="static/gestion_voitures.js"></script> 8 </head> 9 <body> 10 <h1> Selectionner voiture </h1> 11 <div ng-controller="affichervoitures"> 1 <div class="btn-group"> 13 <button type="button" ng-repeat="voiture in infos.liste_voitures" 14 class="btn btn-primary" ng-model="infos.voiture_selectionnee" btn-radio="voiture.name"> 15 {{voiture.name}}</button> 16 </div> 17 <br/> 18 <tt>voiture selectionnee = {{infos.voiture_selectionnee}}</tt> 19 </br> 0 <tt>transmission = {{infos.transmission}}</tt> 1 <br/> <button type="button" ng-model="infos.voiture_selectionnee" btn-radio="'vide'">reset</button> 3 </div> 4 </body> 5 </html> Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! 3/6
Ce fichier est à mettre dans le sous répertoire static. Explications : ligne 1 : on associe un élément du DOM, «Document Object Model», à l application AngularJS (ligne 1 du fichier javascript précédent) ; ligne 3, 4, 5 : on récupère les fichiers Javascript mettant en œuvre le framework AngularJS ; ligne 6 : on inclus le fichier javascript précédent ; ligne 10 : on définit le controlleur qui va automatiquement s associer à la fonction de la ligne 3 du fichier javascript précédent ; ligne 11 : on définie un groupe de boutons et on utilise l opération AngularJS «ng-repeat» pour créer automatiquement les boutons dans le groupe : le «ng-repeat» parcours la table javascript contenu dans la variable «infos.liste_voitures» et associe la variable «voiture» à une case du tableau (pour rappel, une ligne du tableau correspond à une ligne de la BD, c-à-d, les champs «Id», «Name» et «Price» d un enregistrement voiture) et va créer, pour chaque ligne, un bouton : le bouton courant va récupérer comme label le nom de la voiture (champs Name) ; le bouton va modifier la variable indiquée par «ng-model», «infos.voiture_selectionnee», en lui affectant, lorsqu il sera activé, la valeur courante «voiture.name» ; ligne 17 : on affiche le contenu de la variable «infos.voiture_selectionnee» qui est modifée par un bouton de l interface ; ligne 18 : on affiche le contenu de la variable «infos.transmission» qui est modifié dans le fichier javascript. Le contenu programme Python associé voiture.py : 1 #!/usr/bin/python # encoding=utf-8 3 4 import commands, sqlite3, json 5 from bottle import route, run, static_file 6 7 table_voitures = None 8 9 @route('/liste_voitures') 10 def renvoyer_liste_voitures(): 11 return json.dumps(table_voitures) 1 13 def recuperer_liste_voitures(): 14 global table_voitures 15 connexion = sqlite3.connect('voitures.db') 16 curseur = connexion.cursor() 17 curseur.execute('select Id, Name, Price FROM Cars') 18 nom_colonnes = [d[0] for d in curseur.description] 19 table_voitures = [dict(zip(nom_colonnes, ligne)) for ligne in curseur] 0 print table_voitures 1 @route('/') 3 def accueil(): 4 return static_file('gestion_voitures.html',root='static/') 5 6 @route('/selectionner/:nom') 7 def selectionner_voiture(nom): 8 print "Voiture selectionnee :",nom 9 return {} 30 31 @route('/static/<filename:path>') 3 def static(filename): 33 return static_file(filename, root='static/') 34 35 recuperer_liste_voitures() 36 run(host='', port=8080, debug=true) Explications : ligne 9 : une «décoration» permet d associer une URL à une fonction Python : ici la récupération au format JSON du contenu de la table ; ligne 13 : une fonction exécutée une seule fois pour récupérer le contenu de la BD et le fournir en JSON ; ligne : la «racine» du site ; Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! 4/6
ligne 6 : on récupére le paramètre passé dans une URL sous la forme d un chemin de ressource et on le transmets à la fonction Python associée ; ligne 31 : on fournit de manière transparente les ressources du répertoire «static» au client Web (ressources CSS, images, fichier javascript). Utilisation de MongoDB MongoDB est une base de données «NoSQL», c-à-d «Not Only SQL» : les contraintes strictes liées aux bases de données relationnelles SQL sont relâchées voire, pour certaines, supprimées. Le but recherché est de permettre au développeur de définir le modèle de données qui correspond le mieux à flux de données dans son application. MongoDB : est basé sur un modèle de documents où les objets contenant des données sont stockés dans une collection ; offre des bonnes performances, une haute disponibilité et une extensibilité automatique ; utilise une représentation des données basées sur JSON : voiture = {"Name": "Audi", "Id": 1, "Price": 564} Pour son utilisation dans Python : il faut installer MongoDB sudo apt-get install mongodb ; pour lancer MongoDB dans son répertoire personnel /home/toto/bd : mongod --dbpath /BD ; pour stopper MongoDB : pef@olympus:~$ mongo MongoDB shell version:.4.9 connecting to: test Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ Questions? Try the support group http://groups.google.com/group/mongodb-user Server has startup warnings: Tue Nov 11 10:56:54.958 [initandlisten] Tue Nov 11 10:56:54.958 [initandlisten] ** NOTE: This is a 3 bit MongoDB binary. Tue Nov 11 10:56:54.958 [initandlisten] ** with --journal). Tue Nov 11 10:56:54.958 [initandlisten] ** Tue Nov 11 10:56:54.958 [initandlisten] > use admin switched to db admin > db.shutdownserver() Tue Nov 11 10:59:10.4 DBClientCursor::init call() failed server should be down... Tue Nov 11 10:59:10.5 trying reconnect to 17.0.0.1:7017 Tue Nov 11 10:59:10.5 reconnect 17.0.0.1:7017 ok 3 bit builds are limited to less than GB of data (or less See http://dochub.mongodb.org/core/3bit Tue Nov 11 10:59:10.7 Socket recv() errno:104 Connection reset by peer 17.0.0.1:7017 Tue Nov 11 10:59:10.7 SocketException: remote: 17.0.0.1:7017 error: 9001 socket exception [RECV_ERROR] server [17.0.0.1:7017] Tue Nov 11 10:59:10.7 DBClientCursor::init call() failed > il faut disposer du module Python pymongo sudo easy_install pymongo ; Pour l utilisation dans Python : Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! 5/6
pef@olympus:~$ python Python.7.6 (default, Mar 014, :59:38) [GCC 4.8.] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pymongo import MongoClient >>> c=mongoclient() Tue Nov 11 11:05:08.001 [initandlisten] connection accepted from 17.0.0.1:5013 #1 (1 connection now open) # pour créer la BD ou s'y connecter >>> db = c['voitures-db'] # obtenir une collection ou la créer (~ table dans SQL) >>> mes_voitures = db['voitures'] # pour insérer un enregistrement >>> une_voiture = mes_voitures.insert({"name": "Audi","Id": 1,"Price": 564}) Tue Nov 11 11:09:1.333 [FileAllocator] allocating new datafile /home/pef/bd/voitures-db.ns, filling with zeroes... Tue Nov 11 11:09:1.334 [FileAllocator] creating directory /home/pef/bd/_tmp Tue Nov 11 11:09:1.335 [FileAllocator] done allocating datafile /home/pef/bd/voitures-db.ns, size: 16MB, 0 secs Tue Nov 11 11:09:1.335 [FileAllocator] allocating new datafile /home/pef/bd/voitures-db.0, filling with zeroes... Tue Nov 11 11:09:1.336 [FileAllocator] done allocating datafile /home/pef/bd/voitures-db.0, size: 16MB, 0 secs Tue Nov 11 11:09:1.339 [conn1] build index voitures-db.voitures { _id: 1 } Tue Nov 11 11:09:1.339 [FileAllocator] allocating new datafile /home/pef/bd/voitures-db.1, filling with zeroes... Tue Nov 11 11:09:1.339 [conn1] build index done. scanned 0 total records. 0 secs Tue Nov 11 11:09:1.341 [FileAllocator] done allocating datafile /home/pef/bd/voitures-db.1, size: 3MB, 0 secs >>> une_voiture ObjectId('5461e048341e763159a746e') # toutes les collections de la BD >>> db.collection_names() [u'system.indexes', u'voitures'] # faire une requête >>> mes_voitures.find_one({"name" : "Audi"}) {u'price': 564, u'_id': ObjectId('5461e048341e763159a746e'), u'name': u'audi', u'id': 1} # parcourir la liste des enregistrements de la collection (ou d'une requête) >>> for v in mes_voitures.find():... v... {u'price': 564, u'_id': ObjectId('5461e048341e763159a746e'), u'name': u'audi', u'id': 1} # obtenir le nombre d'enregistrements de la collection (~table) >>> mes_voitures.count() 1 # obtenir le nombre d'enregistrements d'une requête >>> mes_voitures.find().count() 1 # Pour passer d'un identifiant chaine de caractères vers ObjectId >>> from bson.objectid import ObjectId >>> rep_chaine = str(une_voiture) >>> rep_chaine '5461e048341e763159a746e' >>> ObjectId(rep_chaine) ObjectId('5461e048341e763159a746e') >>> mes_voitures.find_one({"_id": ObjectId(rep_chaine)}) {u'price': 564, u'_id': ObjectId('5461e048341e763159a746e'), u'name': u'audi', u'id': 1} >>> Plus de détails peuvent être obtenus sur http://api.mongodb.org/python/current/tutorial.html. Travail demandé 1 Passage de SQL à NoSQL : a. Mettre en œuvre la version de l application basée sur SQLite. b. Adapter cette version à l utilisation de MongoDB à la place de SQLite. Étendre le programme proposé pour permettre : a. l inclusion d une image pour chaque voiture ; b. l affichage du prix de la voiture dans l interface ; c. un bouton de réservation avec une mise à jour de la BD et de l interface. took took took Resp. UE : P-F. Bonnefoi, http://libpfb.so/, «TP nº5 filière ISICG» version du 17 novembre 014, rédigé avec ConTEXt Don t Panic! 6/6