Applicazioni Web in Python: I controller

Applicazioni web in PythonCon questo nuovo articolo della serie, ci addentriamo nello sviluppo effettivo della nostra applicazione vedendo nel dettaglio il funzionamento dei Controller.

Per chi non fosse pratico del modello di sviluppo MVC (Model View Controller) faccio una breve premessa sull’argomento:

Modello MVC

Questo modello di sviluppo è molto diffuso nei framework object-oriented come Pylons e consiste nella separazione dei ruoli dei componenti software che compongono l’applicazione in tre parti:

  • Il modello si occupa di fornire i dati necessari all’applicazione, e in genere serve come interfaccia ai DBMS o a altre forme di storage.
  • La vista fornisce la rappresentazione finale dei dati che solitamente risulta in una pagina html.
  • Il controllore riceve le richieste fatte dal browser dell’utente e manipola modelli e viste per farli cooperare.

I Controller in Pylons

In pylons i controller risiedono nella cartella controllers dell’applicazione (nel caso del nostro esempio myapp/myapp/controllers), e consistono in una serie di classi che estendono BaseController (che a sua volta estende WSGIController).

La configurazione predefinita del routing della nostra applicazione prevede che ogni metodo di ogni classe Controller corrisponda ad una pagina web. Ad esempio è possibile eseguire il metodo world del controller hello puntando il browser all’indirizzo http://localhost:5000/hello/world.

Paster ci semplifica il compito di creare un nuovo controller fornendoci un comodo tool da riga di comando. Eseguendo:

$ paster controller hello
Creating [...]/myapp/myapp/controllers/hello.py
Creating [...]/myapp/myapp/tests/functional/test_hello.py

Verranno creati alcuni file, tra i quali c’è la definizione minimale del controller HelloWorld, che dovrebbe assomigliare a questa:

[python]
import logging

from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect

from myapp.lib.base import BaseController, render

log = logging.getLogger(__name__)

class HelloController(BaseController):

def index(self):
# Return a rendered template
#return render(‘/hello.mako’)
# or, return a response
return ‘Hello World’
[/python]

Modifichiamo il nome del metodo index, giusto per dimostrare quanto detto precedentemente in modo da farlo diventare:

[python]
class HelloController(BaseController):

def world(self):
return ‘Hello World’
[/python]

Eseguiamo l’applicazione col comando visto negli articoli precedenti:

$ paster serve --reload development.ini

E visitando la pagina http://localhost:5000/hello/world verremo accolti dalla scritta ‘Hello World’.

Dovrebbe quindi esservi chiaro ora che il risultato fornito al nostro browser dal controller non è altro che il valore di ritorno del metodo corrispondente alla richiesta effettuata.

Routing delle richieste

Abbiamo detto prima che questo comportamento dell’applicazione è definito dalla configurazione predefinita del routing, ma di cosa si tratta?

Quando una richiesta raggiunge la nostra applicazione, uno dei componenti più importanti del framework pylons, Routes, entra in azione.

Routes è la riscrittura in python del sistema di routing di Ruby on Rails e si occupa di smistare al metodo di uno dei controller disponibili la richiesta effettuata dal client e per farlo utilizza un set di regole che sono definite all’interno del file myapp/config/routing.py che si presenta così:

[python]
"""Routes configuration

The more specific and detailed routes should be defined first so they
may take precedent over the more generic routes. For more information
refer to the routes manual at http://routes.groovie.org/docs/
"""
from routes import Mapper

def make_map(config):
"""Create, configure and return the routes Mapper"""
map = Mapper(directory=config[‘pylons.paths’][‘controllers’],
always_scan=config[‘debug’])
map.minimization = False

# The ErrorController route (handles 404/500 error pages); it should
# likely stay at the top, ensuring it can always be resolved
map.connect(‘/error/{action}’, controller=’error’)
map.connect(‘/error/{action}/{id}’, controller=’error’)

# CUSTOM ROUTES HERE

map.connect(‘/{controller}/{action}’)
map.connect(‘/{controller}/{action}/{id}’)

return map
[/python]

La parte che ci interessa di questo file sono le varie chiamate a map.connect() che aggiungono le regole di routing a Routes.

Le regole funzionano secondo un criterio per cui la prima corrispondenza vince, quindi le regole definite in alto hanno una priorità maggiore rispetto alle successive in caso di ambiguità.

Nel nostro esempio precedente la regola che Routes ha scelto di utilizzare è stata /{controller}/{action}. Controller e action sono 2 clausole speciali che danno corrispondenza rispettivamente al nome di un controller e al nome di un metodo. La stessa regola si sarebbe potuta scrivere come:

[python gutter=”false”]
map.connect(‘/hello/world’, controller=’hello’, action=’world’)
[/python]

ma in questo caso solamente questo specifico indirizzo sarebbe stato valido e gli altri controller sarebbero stati irraggiungibili.

Ogni altro tipo di valore specificato tra graffe, come ad esempio {id} si traduce in un parametro del metodo del controller con quel nome.

File statici

C’è un’eccezione importante da tenere presente parlando di routing delle richieste, che riguarda i file statici serviti da pylons. Ogni file che si trova nella cartella myapp/public viene servito direttamente al browser, senza passare attraverso l’applicazione python (e quindi in maniera più rapida) e senza considerare le regole definite in routing.py.

Nel prossimo articolo analizzaremo le viste con il template engine Mako.