Come fare una query su una table in Genropy

A volte capita di dover ricercare dei record da una tabella, utilizzando un criterio particolare che non era stato previsto dagli autori dell’applicativo.

In Genropy si può sempre offrire all’utente finale un modo semplice e rapidissimo per creare qualunque tipo di query si desideri.

Entriamo nel dettaglio

Normalmente le pagine che gestiscono tabelle di database, presentano strumenti di interrogazione piuttosto elementari e poco flessibili. Di solito è possibile porre semplici condizioni sui campi più significativi: ad esempio la ragione sociale per una tabella anagrafica, oppure il campo data per una tabella di operazione o un codice progressivo sulla tabella prodotto.

Questo approccio toglie all’utente gran parte dell’espressività che sarebbe invece offerta dalle Query SQL.
Vedremo in questi brevi filmati lo strumento messo a disposizione da Genropy per creare Query personalizzate in pochi click. In particolare mostreremo alcune caratteristiche:

  • Possibilità di porre condizioni su campi appartenenti a tabelle in relazione, senza doversi occupare delle JOIN
  • Possibilità di usare tutti gli operatori di raffronto offerti da SQL
  • Possibilità di sovrapporre più condizioni usando operatori logici ed eventualmente parentesi
  • Possibilità di salvare la Query per rieseguirla in futuro

Query Semplici e Query Complesse

Per attivare i sottotitoli premere il bottone CC

Per maggiori dettagli su come creare e salvare qualunque tipo di query in Genropy clicca qui.

OUT OF THE BOX

Quando nel 2006 abbiamo deciso di portare le nostre applicazioni in ambito web ci siamo resi conto che non esisteva nulla di idoneo a fornire nel browser la stessa esperienza d’uso e la stessa interattività cui erano abituati i nostri utenti nelle loro applicazioni desktop.

Non trovando nulla di pronto abbiamo dovuto pensare a come ottenere un framework web che fosse facile da usare e in grado di produrre in tempi rapidi applicazioni complesse, manutenibili, personalizzabili e che dessero agli utenti la stessa esperienza d’uso cui erano abituati.

Dovevamo insomma pensare fuori dagli schemi, ed inventarci l’uno dopo l’altro gli strumenti con cui costrire il nostro framework.

Per questa ragione abbiamo deciso di intitolare “Out of the box” questa serie di articoli in cui esamineremo proprio quelle idee che rendono Genropy così diverso dagli altri strumenti normalmente usati per fare applicativi web.

E partiamo dalle Bag, il contenitore di dati che costituisce l’ossatura portante di Genropy.

Bag

Gerarchico è meglio

Genropy usa un paradigma data driven per descrivere sotto forma di strutture di dati le varie parti dell’applicazione lasciando poi al framework il compito di interpretare questi dati per trasformarli in codice funzionante.

Queste strutture, chiamate Bag, possono essere immaginate come dei contenitori a cui si accede tramite un percorso gerarchico usando API disponibili sia in Python che in Javascript. In questo modo lo sviluppatore usa sia lato server che lato client lo stesso paradigma agevolando non solo lo sviluppo dell’applicativo ma anche il trasferimento dei dati.

Senza entrare in dettagli tecnici possiamo dire che le Bag rappresentano l’ossatura di tutto Genropy e quindi per capire come funziona il framework è necessario darne almeno una breve descrizione.

Vediamo quindi un esempio di Bag in Python:

 >>>from gnr.core.gnrbag import Bag
 >>>mybag=Bag()
 >>>mybag.setItem('alfa.beta.name','John')
 >>>mybag.setItem('alfa.beta.age', 34)
 >>>mybag.getItem('alfa.beta.name')
'John'
 >>> mybag.getItem('alfa.beta').keys()
 ['name', 'age']

Il modo migliore per vedere il contenuto di una bag è quello di chiederne la rappresentazione XML, tramite il metodo toXml:

>>> print(mybag.toXml()
    <?xml version="1.0" encoding="utf-8"?>
    <GenRoBag>
          <alfa>
                 <beta>
                    <name>John</name>
                    <age _T="L">34</age>
                 </beta>
          </alfa>
    </GenRoBag>
>>>

Vediamo ora lo stesso esempio in Javascript:

var mybag=new gnr.GnrBag
mybag.setItem('alfa.beta.name','John')
mybag.setItem('alfa.beta.age', 34)
mybag.getItem('alfa.beta.name')
"John"
mybag.getItem('alfa.beta').keys()
["name", "age"]

mybag.toXml()
"<?xml version="1.0" encoding="utf-8"?>
 <GenRoBag>
   <alfa>
     <beta>
        <name>John</name>
        <age _T="L">34</age>
     </beta>
   </alfa>
 </GenRoBag>"

Nella versione Python i comandi setItem e getItem possono essere rimpiazzati da una sintassi con parentesi quadre:

>>>mybag['alfa.beta.age'] = 34
>>>print (mybag['alfa.beta.age'])
34
>>>

Una Bag è composta da “nodi di bag”: BagNode. Un nodo ha un’ etichetta che lo identifica, un valore e può anche avere degli attributi. Gli attributi possono essere inseriti usando la setItem, come parametri nominati aggiuntivi.

Ad esempio aggiungiamo il campo indirizzo di cui diamo anche latitudine e longitudine come attributi:

>>>mybag.setItem('alfa.beta.indirizzo','Via Roma 13',
                       latitudine=45.4456761,
                       longitudine=9.0906981)

Ma possiamo anche accedere ad essi in modo diretto attraverso i metodi setAttr e getAttr

>>>mybag.setAttr('alfa.beta.indirizzo', piano=4, interno=21)
>>>mybag.getAttr('alfa.beta.indirizzo', 'latitudine')
45.4456761
>>>

Possiamo leggere gli attributi aggiungendo al percorso gerarchico del nodo a cui si trovano, il nome attributo dopo il carattere ‘?’. Questo path può essere utilizzato dal metodo getItem oppure, limitatamente a Python nella sintassi con le parentesi quadre:

>>>mybag.getItem('alfa.beta.indirizzo?latitudine')
45.4456761
>>>mybag['alfa.beta.indirizzo?longitudine']
9.0906981

Quando creiamo una bag possiamo inizializzarne il contenuto, ad esempio da un XML:

>>>mybagxml=mybag.toXml()
>>>newbag=Bag(mybagxml)

In questo caso avremo una nuova bag che avrà lo stesso contenuto di mybag.

Possiamo anche inizializzare una Bag da un file del filesystem:

>>>mybag.toXml('mybag.xml')
>>>newbag=Bag(('mybag.xml')

In questo caso il primo comando avrà scritto nel file system il documento ‘mybag.xml’ e il secondo avrà popolato newbag a partire dallo stesso documento.

Quindi le bag possono essere serializzate e de-serializzate facilmente con i comandi toXml e fromXml ma il contenuto, a differenza di un xml standard, risulta tipizzato. Quindi se salveremo un intero (ad esempio age) troveremo nell’xml il tipo e la rilettura ripristinerà il tipo desiderato.

Una bag come XML può facilmente essere salvata nel file system, in un campo di database, oppure ricevuta o spedita tramite una chiamata di rete.

Una bag può essere anche popolata da un URL, ad esempio accedendo ad un servizio di rete (in questo caso si tratta di openweathermap):

   >>>tempo_a_milano=Bag("http://api.openweathermap.org/data/2.5/weatherappid=mysecretappid&q=milano&mode=xml&units=metric")

>>>print (tempo_a_milano)
 0 - (Bag) current:
 0 - (Bag) city: <id='6542283' name='Milan'>
     0 - (str) coord:   <lat='45.47' lon='9.19'>
     1 - (unicode) country: IT
     2 - (str) sun:   <rise='2019-04-25T04:21:24' set='2019-04-25T18:20:49'>
 1 - (str) temperature:   <max='18.89' unit='celsius' value='16.26' min='14.44'>
 2 - (str) humidity:   <unit='%' value='87'>
 3 - (str) pressure:   <unit='hPa' value='1019'>
 4 - (Bag) wind:
     0 - (str) speed:   <name='Gentle Breeze' value='3.6'>
     1 - (str) gusts:
     2 - (str) direction:   <code='ESE' name='East-southeast' value='120'>
 5 - (str) clouds:   <name='broken clouds' value='75'>
 6 - (str) visibility:   <value='7000'>
 7 - (str) precipitation:   <unit='1h' mode='rain' value='1.52'>
 8 - (str) weather:   <number='501' value='moderate rain' icon='10d'>
 9 - (str) lastupdate:   <value='2019-04-25T10:55:07'>

Da questo esempio notiamo che nella Bag oltre ai valori sono presenti degli attributi. Ad esempio:

temperature:   <max='18.89' unit='celsius' value='16.26' min='14.44'>

Quindi nel nodo di Bag temperature il valore è None ma sono definiti 4 attributi ovvero max, min, unit, value.

Prendiamo ad esempio la temperatura a Milano:

>>>print (tempo_a_milano['current.temperature?value'])
16.26

La funzione digest consente di estrarre secondo un’opportuna sintassi elementi dalla bag in modo da facilitare le elaborazioni successive.

Prendiamo, ad esempio, con la funzione digest tutte le grandezze disponibili con relativa unità di misura:

>>>tempo_a_milano['current'].digest('#k,#a.unit, #a.value')
[(u'city', None, None),
 (u'temperature', u'celsius', u'16.26'),
 (u'humidity', u'%', u'87'),
 (u'pressure', u'hPa', u'1019'),
 (u'wind', None, None),
 (u'clouds', None, u'75'),
 (u'visibility', None, u'7000'),
 (u'precipitation', u'1h', u'1.52'),
 (u'weather', None, u'moderate rain'),
 (u'lastupdate', None, u'2019-04-25T10:55:07')]

Possiamo accedere in lettura ad un elemento della Bag anche dalla posizione tramite il simbolo # seguito dall’indice in base 0.

Ad esempio:

>>>print(mybag['alfa.#0.#1'])
34

Una bag di norma può essere navigata solo in senso discendente ma tramite il comando setBackRef possiamo abilitare la navigazione ascendente con il segmento speciale ‘#^’.

Ad esempio:

>>>mybag.setBackRef()
>>>mybag['alfa.gamma.color']='red'

>>>beta=mybag['alfa.beta']

>>>print (beta['name'])
John

>>>print (beta['#^.gamma.color'])
red

Il segmento speciale ‘#^’ invece di seguire un ramo discendente della Bag risale al nodo superiore. Usando percorsi misti possiamo navigare la Bag in qualunque modo.

Senza ulteriormente dilungarci con esempi possiamo dire che le Bag sono quindi un ottimo contenitore gerarchico che consente di annidare informazioni in modo facile ed intuitivo fornendo della API coerenti per l’estrazione di dati e anche per il caricamento da fonti esterne.

Resolver

Dinamico è meglio

Uno dei vantaggi di una bag è quello ammettere come contenuto di un nodo non solo dei dati ma anche degli oggetti (chiamati resolver) in grado di recuperare o calcolare il valore nel momento in cui viene richiesto.

Supponiamo di voler creare una bag che ci possa dare le condizioni metereologiche sempre aggiornate. Per prima cosa creiamo una classe resolver

from gnr.core.gnrbag import BagResolver
class MeteoResolver(BagResolver):
    apikey='mysecretkey'
    url="http://api.openweathermap.org/data/2.5/weather?appid=%(apikey)s&q=%(city)s&mode=xml&units=metric"

    def load(self):
        return Bag(self.url%dict(apikey=self.apikey,city=self.city))['current']

E infine creiamo una bag dove prepariamo dei resolver per le città desiderate:

>>>meteo=Bag()
>>>for city in ('milano','torino','roma','firenze','como'):
...     meteo[city]=MeteoResolver(city=city)

>>>meteo.keys()
['milano', 'torino', 'roma', 'firenze', 'como']

In questo momento non abbiamo ancora fatto alcuna chiamata per leggere i dati e potremmo creare un gran numero di nodi senza avere rallentamenti dovuti alle richieste di rete.

Inoltre nel codice non è necessario fare alcuna chiamata specifica: basta infatti leggere il dato dalla bag per averlo in modo completamente trasparente. Ad esempio per conoscere la temperatura di Torino scriveremo:

>>>print (meteo['milano.temperature?value'])
15.37

Ogni volta che accederemo ad elementi interni scatteranno i resolver necessari e avremo sempre dei dati aggiornati.

Usando digest vediamo tutte le temperature:

>>>meteo.digest('#k,#v.temperature?value')
[('milano', u'15.41'), ('torino', u'13.61'), ('roma', u'20.34'), ('firenze', u'17.93'), ('como', u'14.82')]

Ogni volta che accediamo a dei valori interni ad un resolver genereremo una nuova chiamata di rete che reperirà nuovamente i dati. Nella maggioranza dei casi però non ci serve avere valori così aggiornati e quindi possiamo avvalerci di una funzionalità di cache offerta dai resolver.

Modifichiamo quindi la nostra bag meteo aggiungendo il parametro cacheTime:

>>>meteo=Bag()
>>>for city in ('milano','torino','roma','firenze','como'):
...     meteo[city]=MeteoResolver(city=city,cacheTime=3600)
>>>

In questo caso una volta letto un valore per una città, tutti i dati della “sotto-bag” relativa saranno considerati validi per 3600 secondi e quindi i dati meteo di ogni città saranno come massimo vecchi di un’ora. Avremo però evitato continue chiamate di rete per accedere al servizio esterno.

Trigger

Essere avvisati è meglio

Una bag può essere anche vista come un contenitore in grado di eseguire delle azioni ogni volta che viene cambiato il contenuto.

Questa funzionalità si attiva con il comando subscribe che può essere messo sia alla bag di radice che in qualunque sotto-bag.

Le sottoscrizioni possono essere relative a specifici eventi (insert, update, delete) oppure relative a tutti (any). Inoltre alla stessa bag o sotto-bag possiamo aggiungere varie sottoscrizioni con nomi diversi.

Vediamo subito un esempio. Ci proponiamo di farci mostrare tutti gli eventi che accadono in una bag.

Creiamo innanzitutto una funzione logEvents:

>>>def logEvents(**kwargs):
...     print (kwargs)

>>>mybag=Bag()
>>>mybag.subscribe('mylogger',any=logEvents)

>>>mybag['alfa']='foo'
{'node': BagNode : alfa at 4424525520, 'ind': 0, 'reason': None, 'evt': 'ins', 'pathlist': []}

>>>mybag['alfa']='bar']
{'node': BagNode : alfa at 4424525520, 'reason': None, 'evt': 'upd_value', 'pathlist': ['alfa'], 'oldvalue': 'foo'}

mybag.setAttr('alfa',color='red',size=67)
{'node': BagNode : alfa at 4424525520, 'reason': True, 'evt': 'upd_attrs', 'pathlist': ['alfa'], 'oldvalue': None}

Proviamo ora ad inserire in un path annidato:

>>>mybag['this.is.a.nested.bag']=456
{'node': BagNode : this at 4424493328, 'ind': 1, 'reason': 'autocreate', 'evt': 'ins', 'pathlist': []}
{'node': BagNode : is at 4424491472, 'ind': 0, 'reason': 'autocreate', 'evt': 'ins', 'pathlist': ['this']}
{'node': BagNode : a at 4424491856, 'ind': 0, 'reason': 'autocreate', 'evt': 'ins', 'pathlist': ['this', 'is']}
{'node': BagNode : nested at 4424491152, 'ind': 0, 'reason': 'autocreate', 'evt': 'ins', 'pathlist': ['this', 'is', 'a']}
{'node': BagNode : bag at 4424491408, 'ind': 0, 'reason': None, 'evt': 'ins', 'pathlist': ['this', 'is', 'a', 'nested']}

Vediamo quindi che la nostra funzione di logEvent è chiamata ad ogni inserimento e ci fornisce gli elementi necessari per svolgere i compiti desiderati.

Conclusione

Le Bag si sono dimostrate uno strumento estremamente versatile perchè ci hanno consentito usare un unico modello concettuale sia nella definizione del database che nella creazione della GUI, delle stampe e in moltissime altre aree del framework.

Il loro uso però non è limitato a Genropy e crediamo che anche in altri contesti potrebbero rivelarsi uno strumento molto utile ad affrontare anche problematiche diverse.

Come personalizzare le viste su una tabella in un attimo


Quante volte, nella vista di una tabella, manca proprio la colonna di cui avevamo bisogno?

Grazie a Genropy ogni griglia può essere personalizzata in pochi secondi direttamente da interfaccia utente. Semplice a dirsi, ma vediamo come.

Supponiamo di dover esportare dalla tabella Clienti un foglio di calcolo che riporti i seguiti campi:

Ragione sociale, Provincia, Email, Tipo Cliente, Tipo Pagamento, Numero di Fatture, Totale fatturato.

Genropy ti permette di modificare le colonne di una griglia in pochi attimi. Basterà aprire il cassetto laterale di configurazione per entrare in modalità modifica ed aggiungere colonne o rimuovere quelle che non ci servono. Infine si potrà salvare la nuova vista per utilizzi futuri.

Per attivare i sottotitoli premere il bottone CC

Per maggiori dettagli su come funzionano le griglie in Genropy clicca qui.

Tutti al PyCon!

Anche quest’anno saremo presenti al PyCon Italia, la conferenza nazionale che raccoglie professionisti, ricercatori e appassionati dell’universo Python.

Siamo orgogliosi di partecipare ed essere sponsor silver della 10° edizione di uno degli eventi più importanti del nostro settore.

Ci vediamo nella bellissima Firenze, dal 2 al 5 Maggio, per raccontarvi tutte le potenzialità e i vantaggi dell’utilizzo del nostro framework Genropy; vantaggi che offre sia a sviluppatori che desiderino realizzare applicativi web, programmando in Python, sia all’utente finale.

Se vuoi scoprire tutti i numerosi benefici del nostro framework open source non puoi mancare all’appuntamento. Ti aspettiamo e siamo pronti a rispondere a ogni quesito e dimostrare le vere capacità di Genropy.

In ultimo, ma non meno importante, saremo presenti con una recruiting session per chiunque fosse interessato a collaborare con noi.