.. _tutorial_fatturazione/customer_1: Lezione 2: La tabella cliente ============================= In questa lezione vedremo come creare la prima table dei clienti e la pagina per gestirla. .. raw:: html
La struttura di un package -------------------------- Diamo un rapido sguardo a come è strutturato un package. Dopo la sua creazione troveremo all’interno della directory del package stesso (ovvero ``fatt`` nel nostro Tutorial), il modulo ``main.py``. In tale modulo viene data la definizione dell’oggetto Python corrispondente al *package*. Vediamo che inoltre sono state generate alcune directory: lib ~~~~ Viene utilizzata molto raramente e serve a contenere eventuali librerie Python accessorie scritte per essere usate specificamente dal package. model ~~~~~ Contiene i moduli relativi alla definizione delle tables del database e contiene le customizzazioni delle tables appartenenti ad altri packages. resources ~~~~~~~~~ Contiene le risorse del package. Per risorse si intende un insieme di moduli (javascript, css, Python) che riguarderanno la parte front-end dell'applicazione. Gran parte delle risorse sono suddivise in directory chiamate come le tables del package. In modo tale che per ogni table ci sarà la corrispettiva directory contenente le sue resource di competenza. In particolare troveremo le **th_resource**, ovvero un tipo particolare di risorsa nella quale si definiscono le viste e le form di una table. webpages ~~~~~~~~~ Contiene i moduli che definiscono alcune pagine realizzate appositamente per il package. Nella pratica comune, per presentare una pagina che consenta l'operatività su una table si usa lavorare sulla **th_resource** della table, vedremo in seguito come. Creazione tabella cliente ------------------------- Procediamo dunque a creare un file ``cliente.py`` nella cartella *model* e a definire in esso una classe ``Table`` che eredita da ``object``. La classe ``Table`` deve essere vista solo come raccolta di metodi che in seguito saranno mixati alla vera classe della tabella. :: class Table(object): Il metodo ``config_db`` riceve come parametro ``pkg`` che rappresenta il nostro package. Definizione della table ~~~~~~~~~~~~~~~~~~~~~~~ :: def config_db(self, pkg): tbl = pkg.table('cliente', pkey='id', name_long='Cliente', name_plural='Cliente', caption_field='ragione_sociale') Definiamo i parametri principali della tabella: - ``pkey`` ovvero il campo *primary key* - ``name_long`` cioé il nome esteso della tabella al singolare - ``name_plural`` è al plurale - ``caption_field`` indica il campo che verrà usato come *etichetta* del record. Può essere un campo singolo o composto da più campi. In questo caso per rappresentare il cliente usiamo la sua ``ragione_sociale``. Definizione delle colonne di sistema ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dopo aver definito la table utilizziamo la funzione ``self.sysFields()`` che ci permette di aggiungere velocemente alcune colonne di sistema, tra cui la colonna ``id``, che rappresenta la *primary key* di default per le table dei progetti Genropy. In questo caso, non specificando ulteriori parametri per la funzione ``self.sysFields()`` vengono aggiunte: - la primary key ``id`` - il timestamp di inserimento ``__ins_ts`` - il timestamp di ultima modifica ``__mod_ts`` .. note:: La colonna ``id`` di sistema è di tipo *CHAR* lunga 22 caratteri. Le chiavi sono quindi stringhe di 22 caratteri generate casualmente all'inserimento dei record. E' possibile ovviamente specificare altri tipe colonne *primary key* e ridefinire su ogni table il metodo di genrazione della chiave. Definizione colonne di cliente ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vediamo ora come si definiscono altre colonne specifiche della nostra table. Cominciando con la ``ragione_sociale``. :: tbl.column('ragione_sociale', size=':40', name_long='Ragione sociale', name_short='Rag. Soc.') Il primo parametro è il nome della colonna. Come secondo parametro abbiamo ``size``, ovvero la lunghezza in caratteri della colonna che in questo caso può variare da 0 a 40. .. note:: Se si desidera specificare esplicitamente il tipo di dato della colonna, bisogna usare il parametro nominato ``dtype`` che ammette come valori possibili le sigle di tutti i tipi supportati dai database SQL (I: Ingeger, L:Longinteger, T:Text, etc..). Il tipo di default quando si definisce una colonna è *TEXT*, a meno che non si utilizzi il parametro ``size`` per indicare la lunghezza in caratteri. In tal caso il tipo viene considerato *CHAR* o *VARCHAR*. Notiamo che in questo caso è presente un ``:``, ciò indica che la lunghezza può variare da 0 a 40 caratteri e quindi alla colonna viene assegnato il tipo *VARCHAR*. Se invece il valore fosse stato solamente numerico, la colonna avrebbe avuto tipo CHAR con dimensione pari al valore indicato. I parametri ``name_long`` e ``name_short`` si riferiscono a come la colonna verrà etichettata nelle griglie e nelle form. Quindi vengono poi definite le colonne con il metodo ``column`` - ``ragione_sociale`` - ``indirizzo`` - ``provincia`` - ``comune_id`` Queste ultime colonne hanno una relazione con le tabelle ``provincia`` e ``comune`` del package ``glbl``. Relazioni ~~~~~~~~~ Per stabilire una relazione si usa il metodo ``relation()`` :: provincia.relation('glbl.provincia.sigla', relation_name='clienti', mode='foreignkey', onDelete='raise') che consente di definire per la relazione il campo collegato, il nome della relazione ed altri parametri. Il primo parametro contiene la colonna in relazione, ovvero ``sigla`` della table ``provincia``, del package ``glbl``. ``relation_name`` è come viene nominata la relazione dal punto di vista della table in relazione, in questo caso la provincia. Vale a dire che per la table provincia, seguendo questa relazione ritrovo i ``clienti`` appartenenti ad essa. ``mode='foreignkey'`` indica che la relazione viene riportata anche nel database come una **foreignkey** a tutti gli effetti, con i vincoli che ne conseguono. Dunque alla fine la definizione della table ``cliente`` sarà la seguente:: class Table(object): def config_db(self, pkg): tbl = pkg.table('cliente', pkey='id', name_long='Cliente', name_plural='Cliente', caption_field='ragione_sociale') self.sysFields(tbl) tbl.column('ragione_sociale', size=':40', name_long='Ragione sociale', name_short='Rag. Soc.') tbl.column('indirizzo', name_long='Indirizzo') provincia = tbl.column('provincia', size='2', name_long='Provincia', name_short='Pr.') provincia.relation('glbl.provincia.sigla', relation_name='clienti', mode='foreignkey', onDelete='raise') tbl.column('comune_id', size='22', group='_', name_long='Comune').relation('glbl.comune.id', relation_name='clienti', mode='foreignkey', onDelete='raise') Creazione risorsa th_cliente ----------------------------- Ora lanciamo da console il comando :: gnrmkthresource fatturazione:fatt -m .. hint:: Questo comando non può essere lanciato dall'interno della directory ``fatt``. Da qualsiasi altro punto funziona, ma non dall'interno della directory del package. Questo script provvede alla creazione automatica delle risorse **th_resource** corrispondenti alle tabelle già definite nel ``model`` pacchetto ``fatt``. In questo caso solamente ``cliente``. Di norma per ogni tabella si definisce un modulo che ha lo stesso nome della tabella prefissato da **th_**. Il prefisso th sta per `TableHandler `_ che, come vedremo in seguito, è il component che gestisce le pagine per la gestione delle tabelle. In questo caso notiamo che il comando ha generato all' interno della cartella ``resources/tables/cliente`` il modulo ``th_cliente.py``. Con l'opzione ``-m`` abbiamo fatto generare anche il modulo ``menu.py`` dentro la directory del package. Di questo file parleremo in seguito. .. hint:: I menu sono stati profondamente rivisti a inizio 2022, come presentato nell'articolo dedicato ai `Nuovi menu `_ sul nostro Blog. Potrebbero quindi esserci delle incongruenze tra le registrazioni video effettuate precedentemente e la documentazione testuale che è stata aggiornata. Per un approfondimento sul funzionamento delle pagine ottenute definendo le risorse **tableHandler** vi rimandiamo alla spiegazione fornita dal `Manuale utente di Genropy `_ Classe View ------------ In ogni modulo di tipo **th_resource** possono essere presenti diverse classi di tipo *view*. La classe denominata ``View`` è quella di default. Le classi di tipo *view* sono quelle che implementano il metodo ``th_struct`` il quale definisce la vista, ovvero le colonne da visualizzare in una griglia. Vi sono inoltre altri metodi quali ``th_order`` che definisce l'ordinamento di default, e ``th_query`` che imposteranno i parametri di default per la query iniziale sulla table. L'implementazione di ``th_struct`` incomincia sempre con la definizione di un elemento ``rows`` sul quale si aggiungono gli elementi ``fieldcell`` che rappresentano le colonne della table che vogliamo presentare a video, nella nostra griglia. :: class View(BaseComponent): def th_struct(self, struct): r = struct.view().rows() r.fieldcell('ragione_sociale') r.fieldcell('indirizzo') r.fieldcell('provincia') r.fieldcell('comune_id') def th_order(self): return 'ragione_sociale' def th_query(self): return dict(column='ragione_sociale', op='contains', val='') Classe Form ------------ In ogni modulo di tipo **th_resource** possono anche essere presenti diverse classi di tipo **form** che essenzialmente sono le classi che implementano il metodo ``th_form``. La classe denominata ``Form`` che troviamo già nel file è quella di default. Una classe di form definisce la pagina che ci troviamo di fronte quando l'utente entra in modalità inserimento o modifica di un singolo record. Nella sua forma più essenziale conterrà la form con i campi di input della tabella a cui si riferisce la risorsa. Ma vedremo che è possibile sviluppare anche layout molto più complessi ed articolati. :: class Form(BaseComponent): def th_form(self, form): pane = form.record fb = pane.formbuilder(cols=2, border_spacing='4px') fb.field('ragione_sociale') fb.field('indirizzo') fb.field('provincia') fb.field('comune_id') Il modulo menu.py ------------------- Verifichiamo ora come è stato creato il file ``menu.py`` che definisce la struttura gerarchica del menu di navigazione dell'applicazione. Apriamolo e modifichiamolo come segue. :: class Menu(object): def config(self,root,**kwargs): fatt = root.branch(u"Fatturazione") fatt.thpage(u"Cliente", table="fatt.cliente") L'elemento ``branch`` crea una sorta di directory, mentre l'elemento ``thpage`` crea il collegamento con la pagina standard di gestione di tabella, la quale utilizzerà le classi *view* e *form* definite dalla **th_resource** ``th_cliente.py`` Per ulteriori dettagli si rimanda alla documentazione dedicata ai `Menu `_ Aggiorniamo il database ----------------------- Dal terminale eseguiamo il comando :: gnrdbsetup myfatturazione Per far sì che automaticamente il database si aggiorni secondo le modifiche stabilite nei file che si trovano nella directory ``model``. Verifichiamo il risultato ------------------------- Riavviamo nuovamente il server :: gnrwsgiserve myfatturazione E andiamo a vedere con il browser cosa troviamo nella nostra applicazione. Notiamo subito che nel menu è comparsa la voce **cliente** nella directory di menu del package **fatturazione**. Selezionandola viene aperta la pagina cliente e con il bottone + procediamo a creare un record cliente. La form creata in automatico non è particolarmente evoluta ma consente comunque di caricare i dati del nostro primo cliente. Notiamo che per i campi **provincia** e **comune** viene abilitata la ricerca sull'archivio in relazione corrispondente. Il fatto che un campo sia ricercabile è evidenziato dallo sfondo giallo. Inoltre, per la relazione su provincia, è anche abilitata un'icona a freccia per mostrare gli elementi. Per relazioni ad archivi ad alta numerosità, di norma l'icona a freccia viene omessa. Utilizzando la ricerca sul campo comune notiamo però che tale ricerca non sia limitata ai comuni della provincia selezionata. Se vogliamo restringere la scelta dei comuni alla provincia selezionata, provvediamo ad aggiungere al field del campo **comune_id** un parametro ``condition`` all'interno della classe *form* definita dalla **th_resource** ``th_cliente.py``, come mostrato :: fb.field('comune_id', condition='$sigla_provincia=:provincia', condition_provincia='^.provincia') Questo farà sì che nella query degli elementi selezionabili dal widget sia aggiunta la condizione che la provincia del comune sia uguale alla provincia già inserita nel campo provincia. .. raw:: html
.. raw:: html
**Allegati:** - `main `_ - `cliente `_ - `th_cliente `_ - `cliente_tipo `_ - `pagamento_tipo `_ - `menu `_