Recentemente mi è capitato di dover sviluppare un sistema che permettesse visualizzare e modificare da Plone dati registrati in un db relazionale (nel mio caso sqlite). Volevo quindi integrare all'interno di Plone i dati provenienti da un database relazionale in modo tale da poter trattare questi dati come fossero normali oggetti Zope.
Il punto di partenza consiste in tre prodotti fondamentali in questo caso:
- pysqlite
- SQLAlchemy
- collective.lead
Il database utilizzato per questo tutorial (agenda.sqlite) contiene una sola tabella persone composta da tre campi:
- id
- nome
- descrizione
Per questo tutorial ho creato un pacchetto su cui lavorare chiamato demo.sqlitems. Come prima cosa descrivo il mio oggetto "Persona" attraverso un'apposita interfaccia all'interno del file interfaces.py.
class IPersona(Interface):
id = schema.TextLine(
title = _(u"Id"),
required = True,
description =_(u"Identificativo univoco della persona"))
nome = schema.TextLine(
title = _(u"Nome"),
required = True,
description =_(u""))
descrizione = schema.Text(
title = _(u"Descrizione"),
required = True,
description =_(u"descrizione breve della persona"))
All'interno del file persona.py creo l'oggetto Persona che implementa la precedente interfaccia; è importante che la classe Persona erediti da SimpleItem o "qualcosa di simile" in modo da poter gestire tali oggetti come se fossi su ZODB.
from OFS.SimpleItem import SimpleItem
...
class Persona(SimpleItem):
implements(IPersona)
__name__ = None
__parent__ = None
def getId(self):
return str(self.id)
def setId(self, id):
self.id = id
id = property(getId, setId)
...
All'interno del file database.py posso creare, attraverso collective.lead, la connessione al db e definire la mappatura tra la tabella persone e l'oggetto Persona
import sqlalchemy as sa
from collective.lead import Database
...
class DBAgenda(Database):
@property
def _url(self):
return sa.engine.url.URL(
drivername = 'sqlite',
database = "path_assoluto_al_database/agenda.sqlite")
def _setup_tables(self, metadata, tables):
tables['persona'] = sa.Table('persone', metadata,
sa.Column('id', sa.Integer,
primary_key=True),
sa.Column('nome', sa.Text),
sa.Column('descrizione', sa.Text))
def _setup_mappers(self, tables, mappers):
mappers['persona'] = sa.orm.mapper(Persona, tables['persone'])
...
A questo punto vorrei creare un sistema di viste e di form che mi permettano di ottenere informazioni sulle persone presenti nel database secondo una precisa struttura di url;
- Visualizzare le persone presenti nel database all'indirizzo: http://sitoplone/persone
- Visualizzare una singola persona all'indirizzo: http://sitoplone/persone/1/view (dove il numero corrisponde all'id della persona su database)
- Modificare i dati di una persona direttamente da web all'indirizzo: http://sitoplone/persone/1/edit
- Aggiungere nuove persone secondo la medesima logica di indirizzi...
Il primo caso è il più semplice, creando una BrowserView chiamata persone posso interrogare il database e ottenere una lista di oggetti persona da rappresentare all'interno della mia vista.
La classe che definisce tale vista avrà un metodo simile al seguente che restituisce tutte le persone (consultare la documentazione di SQLAlchemy per ulteriori dettagli)
class PersoneView(BrowserView):
...
def getPersone(self):
db = getUtility(IDatabase, name='persone')
results = db.session.query(Persona).all()
for persona in results:
yield dict(id = persona.id,
nome = persona.nome,
cognome = persona.cognome)
Adesso devo utilizzare il Traverse di Zope per creare il sistema di url che mi sono prefisso di ottenere.
Devo fare in modo che, a partire dalla vista persone, la parte successiva dell'indirizzo venga processata da Zope in modo tale da restituire un oggetto Persona (nel caso venga trovato) che diventerà il contesto delle viste (è più difficile scriverlo che farlo).
Imposto quindi un meccanismo di traverse che, a partire da un oggetto marcato con l'interfaccia IPersoneContainer, discrimini gli oggetti da restituire
from demo.sqlitems.interfaces import IPersoneContainer, IPersona
from zope.publisher.interfaces import IRequest, NotFound
from ZPublisher.BaseRequest import DefaultPublishTraverse
...
class personeTraverser(DefaultPublishTraverse):
adapts(IPersoneContainer, IRequest)
def fallback(self, request, name):
return super(personeTraverser, self).publishTraverse(request, name)
def publishTraverse(self, request, name):
id_persona = None
try:
id_persona = int(name)
except:
pass
if id_persona:
db = getUtility(IDatabase, name='persone')
results = db.query(Persona).filter_by(id=id_persona)
if results.count() != 1:
raise NotFound(self, name, request)
else:
persona = db.one()
persona.__name__ = str(id_persona)
persona.__parent__ = self.context
return persona.__of__(self.context)
return self.fallback(request, name)
registro quindi l'adapter in configure.zcml
<adapter factory = ".traverse.personeTraverser"/>
ora passo a modificare la vista precedente che dovrà essere marcato con l'interfaccia IPersoneContainer:
...
class PersoneView(BrowserView):
implements(IPersoneContainer)
...
a questo punto se vado con il browser all'indirizzo: http://sitoplone/persone/1 mi verrà restituito l'oggetto Persona con id 1. La parte difficile è stata fatta, devo solo registrare alcune viste che mi permettano di visualizzare correttamente i dati:
<browser:page
for="demo.sqlitems.interfaces.IPersona"
name="view"
permission="zope2.View"/>
<browser:page
for="demo.sqlitems.interfaces.IPersona"
name="edit"
permission="cmf.ModifyPortalContent"/>
<browser:defaultView
for="demo.sqlitems.interfaces.IPersona"
name="view"/>
La classe per la visualizzazione della persona sarà quindi una normalissima browser view:
class ViewPersona(BrowserView):
...
@property
def cognome(self):
return self.context.cognome
...
mentre la classe per la modifica dei dati della persona sarà una z3c.form:
...
from z3c.form import field, form
from plone.z3cform.layout import wrap_form
...
class EditPersonaForm(form.EditForm):
fields = field.Fields(IPersona)
...
EditPersona = wrap_form(EditPersonaForm)
Imposto correttamente le azioni della form seguendo la documentazione di SQLAlchemy ed il gioco è fatto.
L'aggiunta di una persona al database funziona esattamente come la form di modifica ovvero registrando l'apposita form di aggiunta dati. In ultimo, per la cancellazione posso creare un meccanismo analogo.
- Archive
- 2009 2010 2011 2012