Blog

Come personalizzare le procedure di importazione

Come personalizzare le procedure di importazione

Il tema dell’importazione da file Excel è ampiamente diffuso in Genropy, si pensi alla possibilità di importare record di una tabella da file xls o addirittura alla possibilità di creare completamente da zero un intero applicativo proprio da una serie di fogli.

La procedura di importazione consente quindi di popolare le tabelle associando le colonne a quelle di un foglio di calcolo, anche se queste hanno nomi diversi. Supponiamo però di avere bisogno di personalizzare questa procedura di importazione. Potrebbe essere il caso di un’importazione in cui abbiamo dei campi in relazione, per esempio, ed è quindi necessario effettuare delle query preventive, oppure i valori devono essere scritti su più tabelle, devono essere effettuate delle operazioni di controllo o modifica, ecc.

In tutti questi casi è possibile sfruttare un metodo di hook offerto da Genropy per ridefinire la procedura di importazione. Questo potrà poi essere attivato dall’interfaccia di importazione standard, attivabile dall’apposita icona in un TableHandler, oppure da un bottone personalizzabile, tramite il component paletteImporter.


Definizione di un metodo di importazione custom: importer_

Questa prima metodologia, meno invasiva, permette di aggiungere una tendina Metodo che comparirà nell’interfaccia e permetterà di utilizzare un metodo di importazione “modificato” con le personalizzazioni desiderate.

Questo risultato può essere ottenuto definendo un metodo con prefisso importer_ nella tabella dove verrà eseguita l’importazione, ad esempio:

def importer_con_territori(self, reader): 
    pass

comporterà l’apparizione nella tendina di un’opzione “con_territori” (ovvero del suffisso del metodo da noi definito).

importer con metodo

Si noti che il metodo riceve reader, un oggetto che permette di accedere e iterare sui dati del file Excel, il quale è ormai stato uploadato e sul quale è già stato fatta una prima verifica.

Tale oggetto avrà molti attributi interessanti già valorizzati, per esempio, ipotizzando a titolo di esempio di effettuare l’upload, nella tabella cliente di Sandbox, di questo file Excel, potremmo trovare:

  • reader.headers, ovvero una lista delle intestazioni del file Excel, nel nostro caso ['rag_soc', 'indirizzo', 'comune', 'pr']
  • reader.ncols: il numero di colonne, pari a 4
  • reader.index: un dizionario che “mappa” i nomi delle colonne e la loro posizione, ad es. {'rag_soc': 0, 'indirizzo': 1, 'comune': 2, 'pr': 3}

Al di là di queste informazioni, reader() restituisce un iteratore che ci permette di scorrere le righe del foglio Excel. Ci basterà quindi nel nostro metodo percorrere queste righe:

def importer_con_territori(self, reader):      
    for row in reader():
        new_cliente = self.newrecord(ragione_sociale=row['rag_soc'], **row)
        self.insert(new_cliente)
        self.db.commit()  

In questo modo potremo assegnare i vari campi al nuovo record. Si noti che le assegnazioni prescinderanno a questo punto da quanto avverrà a livello di interfaccia (in termini di spunta o meno, e in caso di colonne assegnate differenti).

Fin qui non abbiamo aggiunto nulla che non potessimo fare già tramite l’importatore standard. È però poi possibile effettuare anche delle query o coinvolgere altre tabelle, aspetto ben più utile e interessante. Nel file che stiamo importando, infatti, “Comune” e “Provincia” sono campi testuali, mentre nel model di cliente si tratta di campi in relazione. Sarà quindi necessario effettuare delle query preventive per assegnare correttamente questi valori:

def importer_con_territori(self, reader): 
    comuni =    self.db.table('glbl.comune').query(columns='$denominazione,$id').fetchAsDict('denominazione') 
    for row in reader(): 
        if row['comune']: 
             comune_id = comuni[row['comune']]['id'] 
        else: 
             comune_id = None 
        new_cliente = self.newrecord(ragione_sociale=row['rag_soc'], comune_id=comune_id, provincia=row['pr'], **row) 
        self.insert(new_cliente) 
self.db.commit()

In questo modo accoppieremo correttamente i vari comuni della lista con quelli della tabella dei comuni di Genropy. Con la stessa metodologia è possibile anche inserire o modificare record in altre tabelle, innescare altre procedure o meccanismi di controllo, sulla base dei quali eventualmente interrompere l’operazione.

Importare un file Excel con il component paletteImporter

Se si richiede un maggior grado di personalizzazione non solo della procedura, ma anche dell’interfaccia stessa di importazione, è possibile utilizzare il component paletteImporter, un pulsante che possiamo posizionare a piacimento e che si occuperà di aprire una finestra di import per permettere l’importazione di un file tramite interfaccia.

Questo bottone potrà o richiamare un metodo di importazione definito secondo le modalità spiegate nella sezione precedente, o attivare una procedura di importazione tramite un metodo rpc.

Ipotizziamo di aggiungere il bottone nella barra superiore della vista in questo modo:

def th_view(self,view):
    bar = view.top.bar.replaceSlots('delrow','importa,2,delrow')
    bar.importa.paletteImporter(paletteCode='xls_importer',
                            dockButton_iconClass=False,
                            title='!!Importa clienti',
                            importButton_label='Importa',
                            previewLimit=50,
                            dropMessage='Trascina qui il tuo file o clicca per cercarlo', filetype='excel',
                            importButton_action="genro.publish('import_clienti',{filepath:imported_file_path,agente_id:agente_id})",
                            matchColumns='*',
                            importButton_ask=dict(title='Assegna record importati', fields=[dict(
                                   name='agente_id',lbl='Agente', tag='dbSelect',
                                   hasDownArrow=True, table='agt.agente', default='^current.agente_id')]),
                            _tags='superadmin,_DEV_')
    bar.dataRpc(self.importaClienti, subscribe_import_clienti=True,
                      _onResult="genro.publish('xls_importer_onResult',result);",
                      _onError="genro.publish('xls_importer_onResult',{error:error});")

@public_method
def importaClienti(self, filepath=None, agente_id=None, **kwargs):
    "Importa Excel clienti con comuni"
    comuni = self.db.table('glbl.comune').query(columns='$denominazione,$id').fetchAsDict('denominazione')        
    reader = self.utils.getReader(filepath)
    result = Bag()

    for row in reader():
        if row['comune']:
           comune_id = comuni.get(row['comune']).get('id')
        else:
           comune_id = None
        new_cliente = self.db.table('fatt.cliente').newrecord(ragione_sociale=row['rag_soc'], 
                                            comune_id=comune_id, provincia=row['pr'], **row)
        self.db.table('fatt.cliente').insert(new_cliente)
        result.addItem('inserted_row', row)

    self.db.commit()
    return result

Si rimanda alla sezione dedicata sulla Widgetpedia per tutti i parametri possibili del paletteImporter. In questa sede ci concentriamo solo su alcuni aspetti. Innanzitutto diamo uno sguardo al parametro matchColumns:

  • se viene specificato non verrà visualizzata la parte sinistra della griglia (dove vengono associate le colonne ai campi) perché il suo contenuto indica come è fatto il file. Viene quindi eseguito un controllo di validità.
  • se viene impostato ad ‘*’, come in questo caso, viene comunque eliminata la parte sinistra della finestra di import, ma non viene eseguito alcun controllo di validità.

Si noti poi che il paletteImporter innesca in questo caso una dataRpc con il meccanismo publish/subscribe, e che il metodo server di questa rpc (in questo caso importaClienti) riceverà un filepath e l’agente_id. La sostanziale differenza rispetto alla metodologia precedente in cui definivamo un metodo importer_ è proprio che il metodo che definisce l’importazione qui riceve un filepath anziché il reader. È nel metodo stesso che andremo poi a costruire l’oggetto iteratore reader:

reader = self.utils.getReader(filepath)

Si noti poi che un’ulteriore vantaggio di questa modalità è che alla rpc possiamo poi passare tutti i parametri che vogliamo tramite la ask collegata al bottone.


Ipotizziamo infine di creare un paletteImporter che però, invece di innescare una chiamata di tipo rpc, demanda la procedura di importazione a un metodo hook importer_ definito come nel primo caso che abbiamo visto.

In questo caso il paletteImporter sarebbe definito come segue:

def th_view(self,view): 
    bar = view.top.bar.replaceSlots('delrow','importa,2,delrow')
    bar.importa.paletteImporter(paletteCode='xls_importer',
                                dockButton_iconClass=False, 
                                title='!!Importa clienti',
                                importButton_label='Importa',
                                previewLimit=50, 
                                dropMessage='Trascina qui il tuo file o clicca per cercarlo', 
                                filetype='excel', 
                                matchColumns='*', 
                                table='fatt.cliente',
                                importerMethod='con_territori')

Si noti che non si specifica in questo caso una action bensì una table e un importerMethod (ovvero il metodo importer_ che abbiamo specificato nella table cliente). Il vantaggio di questa strada è che il metodo viene così reso disponibile anche per l’importazione tramite l’icona standard, e riceve sempre reader, che in questo modo non necessita di essere specificato all’interno della procedura. Inoltre, il codice è più corto e più semplice, e dal punto di vista di “ordine” si inserisce la procedura in un metodo di table prefissato in modo chiaro senza rischiare di “intasare” il codice di metodi rpc (dal momento che è molto probabile che ne avremo altri).

Di contro, si noti che perdiamo la possibilità di richiedere parametri esterni tramite la ask, che invece necessita di una ridefinizione completa tramite metodo rpc.


Abbiamo quindi visto come Genropy ci permette di personalizzare a vari livelli la procedura di importazione, non solo in termini di “metodo”, ovvero di gestione dei dati importati, ma anche in termini di interfaccia.

Consulta la sezione dedicata al paletteImporter sulla Widgetpedia per approfondimenti sui parametri che è possibile utilizzare:

Hai ancora curiosità/domande sulle procedure di importazione? Usa il box dei commenti qui sotto o scrivici su AskGenropy!