Bookmark and Share
Document Actions

Usare TreeWalker
medio

Uno sguardo al TreeWalker dal punto di vista di uno sviluppatore che desidera capire che cosa abbiamo fatto, o persino scaricare ed usare il nostro codice.

Partiamo

Per usare il TreeWalker nel tuo codice non devi fare molto! Questo è il punto. Sì, ci sono importazioni da maneggiare, come di consueto, ma a parte questo dovrai soltanto definire un Operatore e sarai operativo!

Per prima cosa guardiamo la cosa più semplice che tu possa fare, e partiamo da là. Visiteremo semplicemente un albero e non faremo altro. Per il nostro test abbiamo definito un NullOperator che non fa altro che soddisfare l'interfaccia di IOperator. Possiamo usare questa classe per cominciare.

Per caso, i passaggi che sto per usare come esempio sono presi da un file doctest chiamato treeWalker.txt presente nella cartella docs del prodotto ATContentTypes. Ecco perché vedrai il >>> a sinistra di ogni linea (se non sai cosa sono i doctests non dovresti preoccuparti per questa sintassi - immagina solo che il >>> è un prompt per una sessione Python interattiva seguito da comandi che potresti usare nella tua shell Python preferita, oppure torna alla sezione precedente per la storia completa). Per eseguire questo doctest dovresti usare il photoimagemerge branch con Archetypes 1.4 branch ed il CMFDynamicFTI trunk. Sei inoltre invitato a dare un'occhiata al file treeWalker.txt stesso. Il sorgente di questo tutorial di Plone.org è disponibile sotto forma di singolo file di testo strutturato, denominato treeOperations.txt, che si trova nella cartella docs ed è inoltre testato nella nostra test suite.

OK, andiamo avanti con il tutorial…

In primo luogo importiamo TreeWalker e NullOperator:

      >>> from Products.ATContentTypes.adapters.treeWalker import TreeWalker
      >>> from Products.ATContentTypes.adapters.operator import NullOperator

Ora istanziamo NullOperator:

      >>> nulloperator = NullOperator()

Ora istanziamo un TreeWalker usando il nulloperator - dobbiamo sempre dire al TreeWalker cosa vogliamo che faccia ad ogni nodo che visita. In questo caso, niente:

  >>> walker = TreeWalker(nulloperator)

Lasciamo che ispezioni l'albero iniziando da una cartella specifica:

      >>> walker.walk(folder)

Ecco fatto. I nodi sono stati visitati. Il metodo operate della classe NullOperator è stato invocato e non ci aspettiamo che accada niente.

Facciamo qualcosa di reale

Immagina di voler costruire una sintesi testuale o un albero che mostra il percorso di ogni oggetto (come il comando tree di UNIX) in una gerarchia di cartelle. E supponiamo che non vuoi occuparti dei dettagli dell'iterazione.

Possiamo farlo abbastanza rapidamente. Tutto quello che ci serve è di definire un operatore conforme all'interfaccia di IOperator e passarlo ad un nuovo tree walker. Qui c'è il codice per il nostro doctest:

   	>>> from zope.interface import implements
        >>> from Products.ATContentTypes.interface.operator import IOperator
        >>> class TracingOperator (object):
        ...     implements(IOperator)
        ...     def __init__(self):
        ...        self.trace = []
        ...     def getTrace(self):
        ...        return self.trace
        ...     def operate(self, context, path='', **kwargs):
        ...        self.trace.append(path)

Lo abbiamo denominato TracingOperator, perché restituisce una traccia dell'attraversamento della gerarchia. Abbiamo dovuto aggiungere due nuove importazioni: implements che ci fornisce la nuova rigidità di interfaccia accennato nell'introduzione e Ioperator, che è l'interfaccia che dobbiamo implementare.

Il costruttore inizializza una lista che memorizzerà i nodi che visitiamo. GetTrace () restituisce tale lista, e Operate() è il metodo, definito nell'interfaccia di IOperator, che viene invocato su ogni nodo dell'albero. In questo caso, Operate() si limita ad aggiungere al trace il percorso del nodo partendo dal nodo su cui è iniziata la visita dell'albero.

Citando direttamente dal DocTest:

    Aggiungi qualche cartella::

        >>> folder = self.folder
        >>> folder.invokeFactory('Folder', 'f1')
        'f1'
        >>> folder.f1.invokeFactory('Folder', 'subf1_1')
        'subf1_1'
        >>> folder.f1.invokeFactory('Folder', 'subf1_2')
        'subf1_2'
        >>> folder.f1.subf1_2.invokeFactory('Folder', 'subsubf1_2_1')
        'subsubf1_2_1'

Aggiungiamo una selezione di documenti:

 	>>> folder.f1.invokeFactory('Document', 'd1')
        'd1'
        >>> folder.f1.d1.setText("A nice text")
        >>> folder.f1.subf1_1.invokeFactory('Document', 'd2')
        'd2'
        >>> folder.f1.subf1_1.d2.setText("A nice text")
        >>> folder.f1.subf1_1.invokeFactory('Document', 'd3')
        'd3'
        >>> folder.f1.subf1_1.d3.setText("A nice text")
        >>> folder.f1.subf1_2.subsubf1_2_1.invokeFactory('Document', 'd4')
        'd4'

      Ed esegui l'operazione::

        >>> tracingoperator = TracingOperator()
        >>> tracingwalker = TreeWalker(tracingoperator)
        >>> tracingwalker.walk(folder.f1) 
        >>> trace = tracingoperator.getTrace()

Dopo aver istanziato il TracingOperator lo passiamo all'istanza del TreeWalker. Quindi effettuiamo la visita, a partire dalla cartella di esempio che abbiamo generato sopra. A proposito, “folder” è la cartella radice che ci fornisce il test framework.

Finalmente possiamo vedere la lista di articoli sul percorso che abbiamo visto:

        >>> print trace
        ['f1', 'f1/subf1_1', 'f1/subf1_1/d2', 'f1/subf1_1/d3', 'f1/subf1_2', 
        'f1/subf1_2/subsubf1_2_1', 'f1/subf1_2/subsubf1_2_1/d4', 'f1/d1']

Questo è il tracciato della struttura originale della cartella. Ci sono un paio di cose da notare:

  • L' origine del percorso, “folder.f1” in questo caso viene visualizzato nei risultati. Ciò significa che “operate” è stato invocato anche su tale nodo.

  • Anche le cartelle intermedie sono stati visitate. Per esempio, “f1/subf1_2” è una cartella che contiene un'altra cartella.

  • Le cartelle sono visitate prima dei loro contenuti. Questa è un tipo di ricorsione chiamata “pre-order”, in quanto  le cartelle sono visitate prima dei loro contenuti. Sembra che questo sia il caso più comune.

Approfondimento sul filtraggio

Il filtraggio è una parte importante nella visita di un albero. Migliora le prestazioni e può semplificare il codice nel tuo operatore. Volevamo potenza e flessibilità senza complessità. Inizialmente, filtrare era semplice e omogeneo - il filtro fornito di default, o il filtro passato al walker alla creazione, veniva applicato a tutti i nodi dell'albero. Abbiamo migliorato questo offrendo una terza opzione che potremmo denominare Filtraggio Eterogeneo, o forse Filtraggio Adattativo  che applica automagicamente filtri differenti a nodi differenti di un albero.

Le opzioni di filtraggio sono:

  1. Se una filterClass viene passata come parametro a TreeWalker (), quella classe viene applicata per filtrare il nodo di inizio e tutti i nodi secondari ed è responsabile della sicurezza di accesso al contenuto dell'albero (vedi successivamente).

  2. Altrimenti, se una cartella implementa IFilterFolder, sarà adattata durante la ricorsione mediante quell'interfaccia ed il filtro configurato per quel tipo sarà applicato. Un livello di sicurezza è fornito dalla superclasse Filter.

  3. Altrimenti, il filtro di default - FolderFilter - sarà applicato. FolderFilter restituisce tutti gli elementi in una cartella per elaborarli, in conformità con le osservazioni di sicurezza qui sotto.

Questo ci permette, configurando degli adattatori speciali per uno specifico tipo, di controllare il numero ed il tipo di elementi restituiti dal filtraggio. Un semplice esempio sarebbe un PhotoAlbum, dove se venisse definito un adattatore specifico, sarebbero restituiti solo oggetti foto e album secondari. Tutti gli altri documenti in una gerarchia di PhotoAlbum sarebbero ignorati durante l'attraversamento dell'albero. Ciò è sviluppato più dettagliatamente nella sezione Implementazione.

Usando “filterClass=FolderFilter” come parametro per TreeWalker (), abbiamo anche la possibilità di forzare facilmente l'elaborazione su tutto il contenuto accessibile (vedi Sicurezza, sotto), forzando l'applicazione del filtro meno stringente a tutti i nodi contenitore accessibili, senza eccezione.

Sicurezza

Esiste un problema di sicurezza che è importante  considerare nella visita di un albero. In Zope possiamo invocare un metodo su un nodo se l'utente ha i permessi necessari su quel nodo. Ma quel permesso non ci impedisce durante la visita di accedere o manipolare contenuti a cui l'utente normalmente non avrebbe diritto d'accesso. Nel caso del nostro metodo Archive, non vogliamo che il nostro archivio compresso contenga elementi che l'utente normalmente non potrebbe vedere.

Dal punto di vista del programmatore, non vogliamo che il creatore di un filtro debba lottare con questioni di sicurezza di cui possiamo occuparci noi per suo conto.

Per occuparci di entrambe le questioni, abbiamo creato una gerarchia Filter che separa i due aspetti di selezione e di sicurezza del filtraggio in due metodi. L'utente normalmente implementerà la funzione di selezione in una nuova sottoclasse di Filter, lasciando che il nostro metodo listPermittedObjects (), fornito nella superclasse, si occupi dell'aspetto sicurezza. Senza entrare nel dettaglio, basta dire che implementare un adattatore per un filtro è facile quanto questo:

        class PhotoAlbumFilter(Filter):
             """
             """
             implements(IFilterFolder)

             def filter(self):
                 return self.context.ObjectValues(['Image','PhotoAlbum','Folder'])

E se vuoi escludere la nostra sicurezza completamente, hai sempre la possibilità di fornire la tua filterClass che ti permette di avere tutto il controllo e tutta la responsabilità che ti servono.

 

 
by Maurizio Delmonte last modified 2008-12-12 14:57
Contributors: Jean François Roche, Russ Ferriday, Maurizio Delmonte (traduzione)