Magazine Internet

Python et Webpy : Partie 2

Publié le 29 avril 2010 par Mikebrant

Vous pouvez récupérer le code de la partie 1 ici , le sql là et la conf pour Apache ici (n’oubliez pas de modifier vos hosts).

Dans cette partie, on va créer le formulaire de connexion&compagnie.

Avant de commencer, il faut rajouter dans conf.py (ainsi que conf.template.py) pour site/ et files/ ceci :

BD_TYPE = « mysql »
BD_HOTE = « localhost »
BD_NOM = « quisaura »
BD_USER = « quisaura »
BD_PASS = « 123456″

Comme on veut faire la connexion,
Il faut commencer par créer le modèle Utilisateur.
Mais avant, on va créer la classe Mère DbTable. Pourquoi ?
Parce que nos modèles auront beaucoup de méthodes communes : insert(),save(),delete(),etc.

Donc on crée le fichier utils/bd.py :

#-*- coding:utf-8 -*-
import web
from conf import BD_TYPE, BD_HOTE, BD_NOM, BD_USER, BD_PASS class DbConnection:
   «  »" Gère une connexion à la base de donées.
   La connexion n’est ouverte qu’une seule fois pour toutes les requêtes d’une même page
   «  »"
   _db = None

   @staticmethod
   def getDb():
   «  »" Retourne _db s’il vaut pas None, sinon on créer la connexion «  »"
   if DbConnection._db == None:
   DbConnection._db = web.database(host=BD_HOTE,dbn=BD_TYPE, user=BD_USER, pw=BD_PASS, db=BD_NOM)
   return DbConnection._db

class DbTable(DbConnection):
   «  »" La classe mère, qui va fournir tout ce qui faut à nos modèles, y compris le saucisson «  »"

   tableName = «  » #nom de la table
   columns = ()#les colonnes de la table
   primaryKey = «  » # primaryKey de la table
   debug = False

   def __init__(self, row):
   self._row = row#c’est un dico représentant une ligne de la table=> nomColonne:valeur

   def __getattr__(self, name):
   if (self._row.has_key(name)):
   return self._row[name]
   raise AttributeError, name

   def getRow(self):
   return self._row

   @classmethod
   def _query(cls, query, vars={}):
   rowIter = cls.getDb().query(query, vars=vars, _test=cls.debug)
   if (cls.debug):
   print (« SQL:  » + rowIter)
   return []

   if (isinstance(rowIter,long)):return rowIter
   results = [cls(row) for row in rowIter]
   return results

   @classmethod
   def getRowById(cls, id):
   if (cls.primaryKey):
   return cls._getRow(where=cls.primaryKey + ‘=’ + str(id))
   else:
   return None

   @classmethod
   def _getRow(cls, columns=[], where= », order= », vars={}):
   «  »" Construit une requête SELECT et renvoie la première ligne
   columns: la liste éventuelle des colonnes que l’on veut prendre
   where: clause WHERE de la requête SQL
   order: clause ORDER BY de la requête SQL
   vars: dictionnaire d’argument à remplacer dans la requête »" »

   results = cls._getRows(columns, where, order, ’0,1′, vars)
   return results[0] if len(results) else None

   @classmethod
   def _getRows(cls, columns=[], where= », order= », limit= », vars={}):
   «  »"Construit une requête SELECT et renvoie le résultat
   columns: la liste éventuelle des colonnes que l’on veut prendre
   where: clause WHERE de la requête SQL
   order: clause ORDER BY de la requête SQL
   limit clause LIMIT de la requête SQL
   vars: dictionnaire d’argument à remplacer dans la requête »" »

   if columns :
   query = « SELECT  » + « , ».join(columns)
   else:
   query = « SELECT  » + « , ».join(cls.columns)

   query +=  » FROM ` » + cls.tableName + « ` »
   if (where):
   query +=  » WHERE  » + where

   if (order):
   query +=  » ORDER BY  » + order

   if (limit):
   query +=  » LIMIT    » + limit

   return cls._query(query, vars)

   @classmethod
   def save(cls, values):
   «  »" Crée un nouvel objet dans la table
   values: Dictionaire (nom_colonne: valeur)
   «  »"
   columns = ‘,’.join(['%s' %(i) for i in values.keys()])
   val = ‘,’.join(['$%s' %(i) for i in columns.split(',')])
   query = « INSERT INTO `%s` (%s) VALUES(%s) » % (cls.tableName,columns,val)
   cls._query(query,vars=values)
  
   @classmethod
   def delete(cls,values):
   «  »"supprime une ligne dans la table »" »
   condition =  » AND « .join(['%s=$%s' % (i,i) for i in values.keys()])

   return cls.getDb().delete(« `%s` » %cls.tableName,where=condition,vars=values,_test=cls.debug)

   @classmethod
   def update(cls, values, id):
   «  »" Met à jour un objet existant dans la table
   values: Dictionaire (nom_colonne: valeur)
   «  »"
   conditions = ‘,’.join(["%s=$%s"%(i,i) for i in values.keys()])
   query = « UPDATE `%s` SET %s WHERE %s=%s » % (cls.tableName,conditions,cls.primaryKey,str(id))
   return cls._query(query, vars=values)

Bon, j’explique pas le code de DbTable, puisque c’est pas le but de ce billet, puis il y a assez de commentaires je pense.

Maintenant, on crée notre premier modèle : models/utilisateur.py :

#-*- coding:utf-8 -*-
from utils.bd import *
from hashlib import sha1

class Utilisateur(DbTable):
   «  »"Represente un Utilisateur (table Utilisateur) «  »"

   tableName = ‘utilisateur’
   columns = (‘idUser’,'login’,'password’,'bloque’)
   primaryKey = ‘idUser’

   @classmethod
   def identification(cls, login, mdp):
   «  »" tente de logguer l’utilisateur »" »
   mdp = sha1(mdp).hexdigest()
   utilisateur = cls._getRow( where=’`login` = $login AND `password` = $mdp’,
   vars={‘login’:login, ‘mdp’:mdp} )
   return utilisateur

Pour créer un modèle, rien de plus simple :
- on fait hériter la classe de DbTable
- on indique dans tableName, le nom de la table
- on indique dans columns, les colonnes
- on indique dans primaryKey, la clé primaire
- et voilà !

Maintenant que notre modèle Utilisateur est créé , on va pouvoir faire notre première Action.
Mais en fait non, puisque l’on va avant créer la classe BaseAction dans utils/coeur.py .
Elle va être la classe mère de toutes nos actions. Pourquoi ?
Parce qu’à chaque fois que l’on change de pages(donc d’actions) on a besoin d’informations récurrentes , comme la session de l’utilisateur,un objet représentant l’utilisateur, l’objet qui va retourner notre template,etc.

Donc dans le fichier utils/coeur.py , on rajoute :

import web
from conf import CHEMIN_VUES
class BaseAction(object):
   def __init__(self):
   self.session =  web.ctx.environ['beaker.session']
   self.render = web.template.render(CHEMIN_VUES,globals={‘_’: _})# _ : pour l’i18n

   if self.session.has_key(‘user’):
   from models.utilisateur import Utilisateur
   self.session['user'] = Utilisateur.getRowById(self.session['user'].idUser)

Voilà, donc chaque action pour accéder à la session depuis self.session, à l’utilisateur depuis self.session['user'], et au template depuis self.render.

Maintenant, on peut réellement commencer à créer nos actions.

Place à l’indexAction, qui retourne la page d’accueil, une fois qu’on est connecté .
Fichier site/controleurs/IndexControleur.py :

# -*- coding:utf-8 -*-
from conf import *
from utils.coeur import *
from utils.decorateurs import *

class IndexAction(BaseAction):
   «  »"action pour afficher la page d’accueil »" »
   __metaclass__ = MetaClass

   url = ‘/’

   @estConnecte
   def GET(self):
   utilisateur = self.session['user']
   return self.render.main(utilisateur=utilisateur)

Créer une action est simple :
- on lui définit la métaclasse MetaClass
- on lui indique l’url à partir de laquelle elle est accessible (ca peut être aussi un tuple).
- et on crée la méthode, selon le type de la requête HTTP : si c’est du GET alors on crée la méthode GET(), si c’est du POST=>POST(), OPTIONS=>OPTIONS(),etc.
- on retourne la vue via self.render
- et voilà !

Ici l’action est très simple : si l’utilisateur est connecté, on retourne la vue main.html.

Maintenant comme vous l’avez constaté, il nous reste à créer le décorateur estConnecte.
Donc on crée le fichier utils/decorateurs.py et on rajoute :

#-*- coding:utf-8 -*-
import web

def estConnecte(maMethode):
   «  »"Indique si l’utilisateur est loggué ou pas »" »
   def parametres(*params):
   if params[0].session.has_key(‘user‘):
   if params[0].session['user'] != None:
   return maMethode(*params)
   return web.redirect(‘/login‘)
   return parametres

C’est un décorateur simple (cf mon article dessus).
Si l’utilisateur n’a pas la clé user dans sa session, alors il n’est pas connecté, et donc on le redirige sur /login.

Retour à IndexAction.
Dans la méthode GET() , on retourne self.render.main(),cad main.html.
Donc voici le fichier site/vues/main.html :

$def with (utilisateur)
<p>$_(« tu_es_connecte »)</p>
<p><a href= »/logout »>$_(« se_deconnecter »)</a></p>

Comme on a passé à main.html un paramètre, il faut aussi le déclarer dans le template.

Pour finir ce billet, on va créer le LoginControleur, qui va permettre à l’utilisateur de se dé/connecter.
Fichier site/controleurs/LoginControleur.py :

#-*- coding:utf-8 -*-
from utils.coeur import *
from utils.decorateurs import *
from models.utilisateur import Utilisateur
import web

class LoginAction(BaseAction):
   «  »"action responsable pour le login »" »
   __metaclass__ = MetaClass

   url = ‘/login

   def GET(self):
   «  »"page de login seul »" »
   return self.render.index()

   def POST(self):
   «  »"si c’est du post, on est pas loggué où on travaille à la poste «  »"
   login,mdp =  web.input()['login'], web.input()['password']

   utilisateur = Utilisateur.identification(login, mdp)
   if utilisateur :
   self.session['user'] = utilisateur
   return web.redirect(‘/’)
   return self.render.index()

class LogoutAction(BaseAction):
   «  »"action pour se délogguer »" »
   __metaclass__ = MetaClass

   url = ‘/logout

   @estConnecte
   def GET(self):
   «  »"on se déco »" »
   self.session.delete()
   return web.redirect(‘/’)

Je commence par LogoutAction.
Sa méthode GET() ne fait rien de plus que de supprimer la session de l’utilisateur, puis de le rediriger à la racine du site.

Et place à LoginAction.
Quand on arrive sur /login, alors la méthode GET() est appelée. Elle ne sert qu’à retourner la vue index (site/vues/index.html), que voici(c’est un simple formulaire, donc pas de commentaire) :

<form id= »connexion_form » method= »POST » action = « /login »>
   <fieldset>
   <legend>$_(« connexion »)</legend>
   <label for= »login »>$_(« login »)<input type= »text » name= »login » /></label>
   <label for= »password »>$_(« password »)<input type= »password » name= »password » /></label>
   <input type= »submit » value= »$_(‘ok’) » />
   </fieldset>
</form>

Ensuite, quand on valide ce formulaire, c’est donc la méthode POST() de LoginAction qui est appelée.
On récupère les valeurs du formulaire via web.input()["name_de_l_input"].
Ensuite via la méthode de classe identification() du modèle Utilisateur, on  tente de logguer l’utilisateur, et selon le résultat, on redirige sur IndexAction ou pas.

Et voilà, on peut se connecter, se deconnecter, fin du billet !

Dans la partie 3, on verra comment uploader un fichier.

Pour récupérer les sources de la partie 2 c’est ici, le sql là et la conf pour Apache ici (n’oubliez pas de modifier vos hosts).

PS1 : je cherche un stage d’au moins à 3 mois à partir de juillet dans le dévelopmment Python (ou C)  /Administration Linux, plus d’infos ici.

PS2 : depuis quelques mois, je développe une plateforme de partage de contenus multimédia en python (webpy) : http://prod.qui-saura.fr (vous pouvez vous inscrire, le design est assez moche)
C’est plutôt bien avancé, mais je recherche un « associé »,  quelqu’un qui serait intéressé à continuer ce projet avec moi, donc s’il y a des intéressés faites-moi signe.


Retour à La Une de Logo Paperblog

A propos de l’auteur


Mikebrant 9 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte

Dossier Paperblog