Je copie Hélène.
Vous avez jusqu’à 14h pour me donner en commentaires les liens spotify (pas de deezer ou autre svp) des chansons que vous voulez.
Je les mettrai dans ma playlist du jour et ça sera la musique que j’écouterai cet après midi.

[Edit : Il est 14h. C'est fini pour les suggestions. Si vous désirez également écouter la playlist, c'est ici]

Symfony
Vous en avez peut-être entendu parler, il y a quelques semaines de cela est sorti le premier livre Symfony en Français, aux éditions Eyrolles.

A vrai dire il s’agit de la version Française du Jobeet, qui a été publié en anglais comme calendrier de l’avent en décembre dernier.
Vu la qualité du Jobeet, je ne peux que vous conseiller ce livre.
Rédigé par Fabien Potencier, le créateur de Symfony et Hugo Hamon de Apprendre PHP, c’est le livre à acheter de la saison (non non j’ai aucune action).

Pour ma part je ne l’acheterai pas (ça, c’est dit).
Y’a Romain qui a fait la relecture de cet ouvrage et qui en a, par conséquent, reçu trois exemplaires gratuitement.
Il voulait au départ les encadrer au mur de sa chambre. Mais c’est pas assez grand. Donc comme il est trop cool, il m’en offre un.

Ca tombe bien. Après avoir développé l’API de Refstats avec rails; le site avec Zend Framework et l’interface de documentation (pas déployée encore) avec Django, je voulais justement faire l’interface de passage en mode professionnel avec Symfony :)

Si vous avez déjà essayé d’envoyer un email avec rails, vous avez constaté que c’est articulièrement aisé.
Mais ruby ce n’est pas que du rails !
Dans le cas du redéveloppement de Refstats par exemple, j’ai recodé le crawler de celui-ci, qui va récupérer de manière journalière les positions de chacun de vos sites.
Et je désire être rapidement être mis au courant si j’ai une erreur qui est apparue afin de pouvoir la corriger tout aussi vite.

Du coup si je soulève une exception, je veux m’envoyer un email.
Bien évidemment, je n’ai qu’une application console.
Pas de rails derrière, bien que j’utilise Active Record.

Du coup pas d’action mailer. Et pas de fonction « mail » comme en PHP.

Il est cependant assez aisé d’envoyer un email :
require 'net/smtp'
def send_email(from, to, subject, message)
  msg = "Subject: " + subject + "\r\n\r\n" + message
  Net::SMTP.start('localhost') do |smtp|
    smtp.send_message msg, from, to
  end
end

Nous déclarons une méthode send_email, qui prends les paramètres :
email de l’envoyeur; email du destinataire; sujet de l’email et son message.

Nous créons l’entête du message avec son sujet.
Puis nou instancions un Net::SMTP avec l’adresse du serveur SMTP (et en second paramètre son port si autre que 25).
Et on envoie notre message !
Pour plus de détails, notamment l’identification, je vous invite à vous référer à la documentation de Net::SMTP.

Si vous avez déjà touché à Zend Framework, vous vous êtes peut-être déjà pris la tête sur ses formulaires.
Et dans ce cas la, vous comprenez déjà probablement ce que je veux dire avec ce titre. Sinon, la suite détaille.

Voici un exemple de formulaire un petit peu complexe.
Vous pouvez en voir le résultat sur la page de gestion des mots clés de RefStats.

Comme vous pouvez le constater, il est assez difficile de comprendre l’intérêt d’un tel formulaire, ce qu’il affichera et même les champs qu’il contient.

Le problème des formulaires ZF est le même que dans beaucoup d’autres composants du framework : ils veulent trop en faire.
Le but d’un formulaire côté serveur est pourtant simple : faciliter la validation des données.
Que cela soit dans Rails ou ceux-ci sont directement inclus dans les modèles ou dans Django et Symfony ou ceux-ci ne servent que à faire la validation, à chaque fois, les formulaires ne génèrent pas d’HTML automatiquement.

C’est, malheureusement, ce qu’essaye de faire Zend Framework.
Mais cela rends la chose illisible car pas ergonomique; impossible à modifier pour la première raison.
Et surtout, absolument pas DRY. Si vous avez besoin de placer deux fois le même formulaire pour les mêmes données. Mais les afficher différemment ou simplement ne pas permettre la modification de certaines données, il vous faut créer deux formulaires différents !
Alors qu’il serait tellement plus simple de ne faire qu’un seul formulaire mais deux pages HTML affichant celui-ci.

Du coup comme vous l’avez deviné, je ne vous conseille absolument pas d’utiliser ces formulaires.
Après, au niveau alternatives, je vous épargnerai Rails et Django. Tout autant que Symfony, qui requiert l’inclusion complète du framework.
En revanche Loic me souffle sur Twitter que JForms serait pas mal.
Mais il est l’un des développeurs du framework et je n’ai pas eu l’occasion de tester la chose. Donc vous êtes sans filet !

Après avoir démarré le serveur Open Office, voyons maintenant comment utiliser ce que celui-ci nous apporte.

Je ne vais pas détailler toutes les possibilitées offertes par celui-ci puisqu’elles sont simples : vous pouvez faire tout ce que vous faites avec Open Office en mode bureautique. Sauf que la, c’est du python et c’est automatique !

Commençons donc par ouvrir notre fichier.
cSourceDoc = "/path/to/your/file.html"
url = unohelper.systemPathToFileUrl(cSourceDoc)
args = (PropertyValue('FilterName', 0, 'HTML (StarWriter)', 0),)
model = desktop.loadComponentFromURL(url, "_blank", 0, args)

Nous précisons donc le nom du fichier; nous en définissons les paramètres d’ouverture en indiquant qu’il s’agit d’un document HTML.
Puis nous ouvrons.
Une liste de tous les FilterNames disponibles est accessibles ici.
Mais, malheureusement, comme un peu tout avec PyUNO, elle est très mal documentée.

Maintenant que nous avons ouvert notre document, il faut l’enregistrer dans un autre format.
theTargetFile = '/path/to/your/file.doc'
url = unohelper.systemPathToFileUrl(theTargetFile)
args = (PropertyValue('FilterName', 0, 'MS Word 97', 0), PropertyValue('Unpacked', 0, False, 0),)

model.storeAsURL(url, args)
Nous définissons donc le chemin vers le fichier; le format final et enregistrons.

A cela nous ajoutons la fermeture du client.
print('Closing the client ...')
model.close(True)
ctx.ServiceManager

Et, comme d’habitude, vous pouvez voir le code complet.

Open Office propose une solution permettant d’exécuter toutes les fonctions proposées par le logiciel de manière programmatique. Il s’agit de PyUNO.
Cet article est le premier d’une série qui visera à présenter la librairie Python permettant de manipuler ce serveur Open Office.

Dans ce premier article, nous allons voir comment, dans un script Python, se connecter au serveur et le lancer si cela n’est pas déjà fait.

def start_client():
  print('Starting the client ...')
  localContext = uno.getComponentContext()
  resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext)
  return resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")

Cette méthode start_client se connecte au serveur Open Office lancé sur le port 2002. Si cela est impossible, une exception sera soulevée.

Du coup si l’on a une exception de soulevée, on peut considérer que le serveur n’est pas démarré et le lancer.
try:
  ctx = start_client()
except Exception, exc:
print('... Server not Started. Starting it ...')
  status = os.system('"' + os.path.normpath(oo_path) + '" "-accept=socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" -nologo -headless -nofirststartwizard" &')
  time.sleep(2)
  ctx = start_client()

Nous démarrons le client. Si une exception est soulevée alors, nous démarrons le serveur, lui laissons deux secondes pour se lancer et redémarrons le client.

Pour que le serveur puisse démarrer, il vous faut renseigner la variable oo_path avec le chemin vers l’exécutable soffice

Sous debian avec Open Office installé via apt-get, je place :
oo_path = '/usr/bin/soffice'
Et sous Windows :
oo_path = 'C:/Program Files/OpenOffice.org 3/program/soffice.exe'

Suite à cela, vous pouvez récupérer l’instance de Open Office ouverte et la manipuler (créer un nouveau document, l’enregistrer, le modifier, lui changer son format, …)
smgr = ctx.ServiceManager
desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)

Si vous désirez voir le code indiqué dans cet article dans on ensemble, c’est par ici.
Et pour un exemple d’utilisation du client que nous venons de démarrer, vous pouvez lire l’article ouvrir un document HTML et l’enregistrer en DOC

Un petit article rapide. Lorsque vous testez vos applications, l’envoi d’email est toujours embettant. Vous n’avez pas envie de l’email parte vraiment et souhaitez vérifier qu’il pars correctement.

Du coup il faut utiliser un Mock, qui va simuler l’envoi d’email mais n’enverra, en fait, rien.
Et qui vous permettra tout de même de surveiller l’email envoyé et de vérifier qu’il est correct.

Commençons, dans notre vue, par envoyer notre email :
from django.core.mail import send_mail
def envoi(self):
  send_mail(
    'A propos des tests',
    'C\'est fun quand même',
    'from@example.com',
    'to@example.com'
)

Et testons notre méthode :
from django.core import mail
def test_send(self):
  response = self.client.post('/envoi')
  self.failUnlessEqual(response.status_code, 302)
  self.assertEquals(len(mail.outbox), 1)
  self.assertEquals(mail.outbox[0].subject, 'A propos des tests')

Ainsi lorsque nous lançons notre test, en supposant que l’url /envoi soit configurée pour diriger vers notre action envoi, celle-ci sera exécutée, l’email sera envoyé de manière fictive.
Et vous aurez accès à un tableau dans mail.outbox qui contiendra tous les emails envoyés avec leur contenu, leur sujet et les personnes à qui ils ont été envoyés.

A vous, par la suite, d’écrire les tests appropriés au bon fonctionnement de votre application.

Le générateur automatique d’administration de Django est super sympa. En trois lignes de code, vous avez une interface pour ajouter, modifier et supprimer des données de votre modèle.
Cependant dans le cas de textareas, il peut arriver d’avoir besoin d’avoir un éditeur de texte avancé.

Nous allons donc ici voir comment implémenter TinyMCE aux textareas d’une page de l’administration particulière.
Mais cette technique peut également très bien fonctionne avec FCKEditor ou tout autre éditeur WYSIWYG. Le tout est, après, de changer le code javascript.

Prenons le fichier admin.py de notre application :
from django.contrib import admin
from project.application.models import Page
class PageAdmin(admin.ModelAdmin):
  list_display = ('name', 'url')
  fieldsets = [
    (None, {'fields': ['name', 'url']}),
    ('Content', {'fields': ['content']}),
  ]
admin.site.register(Page, PageAdmin)

Nous définissons donc une interface d’administration pour notre modèle Page qui contient les champs name, url et contenu.
Ce dernier champ est celui qui va prendre un textarea.

Commencons par ajouter des documents javascript externes spécifiquement pour ce modèle.
Après fieldsets[], ajouter
class Media:
  js = (
    '/js/tiny_mce/tiny_mce.js',
    '/js/admin_pages.js'
  )

Nous incluons donc les deux documents javascript supplémentaires dans la page des pages. Et uniquement celle-ci.

Installez tinymce dans /js/. Et créer le fichier /js/admin_pages.js
Dans lequel vous placerez
tinyMCE.init({
  mode : 'textareas',
  theme : "simple"
});

Ainsi, vous transformez tous les textareas de la page en champs tinymce (mais dans notre cas, il s’agit d’un seul).
Redémarrez votre application et vous verrez votre bel éditeur de texte enrichi :)

Libre à vous par la suite d’enrichir votre administration avec du javascript de cette manière.

Un développeur Ruby/Rails Américain de passage à Lyon jusqu’à demain m’a contacté pour que l’on prenne un verre ensemble ce soir.
Alors oui, je préviens au dernier moment.
Mais comme je suis pas élitiste et que plus y’a de fous, moins y’a de bière dans les cuves après, vous êtes tous invités à vous joindre à nous !

Le rendez-vous est :
Jeudi 7 mai 2009 à partir de 19h30.
Café Perl

Vous pouvez bien évidemment arriver plus tard auquel cas je vous invite à prendre mon numéro, disponible sur mon cv.
Je ne garantis pas que nous resterons au même endroit toute la soirée.

N’hésitez pas non plus à laisser un commentaire pour avertir de votre présence !
A ce soir :)

Après avoir vu la mise en place de l’environnement de test et des tests unitaires avec Django, voyons maintenant comment mettre en place nos tests fonctionnels.
En écrivant cet article, je considère que vous avez lu l’article précédent et que vous avez donc compris ma méthode de hiérarchisation des tests.

Voici un test d’exemple (j’allais dire « de test ». Mais c’est un chouilla trop répétitif).
from django.test import TestCase
from django.test.client import Client
from projet.pages.models import Pager
class PagesTest(TestCase):
  def setUp(self):
    self.client = Client()
  def test_view_page(self):
    Page(
      name='Conditions d\'utilisation',
      url='conditions',
      content='nothing for now'
    ).save()
    response = self.client.get('/conditions')
    self.failUnlessEqual(response.status_code, 200)
  def test_view_unknown_page(self):
    response = self.client.get('/nothing/')
    self.failUnlessEqual(response.status_code, 404)

Et analysons le code.
Nous commençons par importer les diverse classes nécessaires à ces tests :

  • TestCase, le framework de test
  • Client, le client HTTP de test
  • Page, le modèle qui nous permet d’ajouter des données permettant ici les tests

Puis nous commençons les tests, en initialisant self.client à une nouvelle instance de Client afin de pouvoir faire des requêtes HTTP.
Dans le premier test, nous créons un élément Page puis nous testons que lorsque nous appellons celui-ci, notre page est bien rendue.
Et dans le second, nous vérifions que nous avons une bien une erreur 404 si nous appellons une page qui n’existe pas.

Vous noterez la méthode self.client.get() permettant de faire un appel HTTP avec la méthode du même nom.

Plusieurs méthodes sont ainsi disponibles pour faire les appels HTTP.

  • get(path, data={}, follow=False) – Fait une requête HTTP get
  • post(path, data={}, follow=False) – Fait une requête HTTP post
  • head(path, data={}, follow=False) – Fait une requête HTTP head
  • options(path, data={}, follow=False) – Fait une requête HTTP options
  • put(path, data={}, follow=False) – Fait une requête HTTP put
  • delete(path, data={}, follow=False) – Fait une requête HTTP delete

A cela s’ajoutent deux méthodes permettant d’identifier et de déconnecter l’utilisateur.

  • login(options) – Identifie l’utilisateur et retourne true si celle-ci a réussi.
    Exemple : self.client.login(username='test', password='test')
  • logout() – Déconnecte l’utilisateur auparavant connecté

Lorsque vous faites une requête quelconque, vous pouvez en tester le résultat. Voici les différentes méthodes disponibles.

  • client – Le client de test qui a été utilisé pour faire la requête
  • content – Le contenu de la page rendue
  • context – L’instance de la classe Context qui a été utilisée pour afficher le contenu de la page. Si il y a eu plusieurs templates de rendus, il s’agira d’une liste de contextes triés par ordre de rendu.
  • request – Les données request qui ont servi à faire la requête
  • status_code – Le code HTTP retourné par la page
  • template – L’instance de template qui a été utilisée afin d’afficher le contenu de la page. Si il y en a eu plusieurs, un tableau de templates triés par ordre d’affichage sera retourné.

Du coup vous avez maintenant toutes les clés en main pour commencer à faire vos tests fonctionnels. Y’a plus qu’à coder !

 
Fork me on GitHub