.. _orm_genropy/orm_genropy/model/relation: Definire una relazione ====================== È possibile stabilire delle relazioni tra le tabelle, in modo da poter sempre reperire tramite :ref:`Operazioni di lettura` i valori presenti nelle tabelle collegate. Pensiamo per esempio all'indirizzo di un cliente, che dipende dall'indirizzo legato alla sua anagrafica. In questo modo aggiornando l'indirizzo nella tabella di origine *anagrafica*, verrà riproposto l'indirizzo aggiornato anche al momento di una nuova fatturazione dalla tabella *fattura*, questo automaticamente senza necessità di aggiornamento manuale. In ogni record di Genropy è disponibile in qualunque momento anche l'intero record in relazione, individuabile percorrendo il *relation_path* contrassegnato dal simbolo con la ``@``. Per esempio, aprendo `l'Inspector di Genropy `_ su un qualsiasi record di una tabella in relazione, troveremo la seguente situazione: .. image:: /_static/images/model/fatt-relazione.png :width: 100% :align: center Seguendo quindi la relazione troveremo per intero il record in relazione, in questo caso il ``cliente`` associato alla ``fattura`` corrente. Questo ci permetterà di reperire dati **in qualsiasi momento** da **tutte le colonne**, semplicemente seguendo la relazione in questione, tramite semplici :ref:`Operazioni di lettura` . La relazione può essere definita come segue:: tbl.column('cliente_id',size='22', group='_',name_long='!![it]Cliente' ).relation('cliente.id', relation_name='fatture', mode='foreignkey',onDelete='raise') A una normale :ref:`colonna` , quindi, di cui definiamo le caratteristiche come descritto in precedenza, possiamo appendere l'oggetto ``relation``, che definiamo come segue: - con ``related_column='cliente.id'`` (o implicitamente, solo ``cliente.id``), stabiliamo la relazione con la tabella ``cliente`` e con la colonna ``id``. In questo caso omettiamo il *package*, in quanto è lo stesso della tabella di origine, altrimenti andrà indicato prima della tabella. Stabilendo la relazione con la *pkey* ne garantiamo l'assoluta unicità. - con ``relation_name='fatture'`` chiediamo che alla relazione inversa sia dato il nome ``fatture``, in questo modo dalla tabella *cliente*, seguendo il path ``@fatture`` potremo individuare tutte le fatture del cliente. Si noti che il parametro è facoltativo, se non specificato di default verrebbe utilizzato *nomepackage_nometable_nomecolonna*, ma in questo modo rendiamo il nome della relazione più mnemonico e facilmente leggibile. - con ``mode='foreignkey'`` stabiliamo un vincolo di integrità referenziale a livello SQL e ci assicuriamo quindi che il database garantirà che i valori assegnati a questo campo siano validi ed effettivamente presenti nella tabella in relazione. - con ``onDelete='raise'``, infine, definiamo il comportamento da tenere in caso di eliminazione di un record, in questo caso verrà sollevata un'eccezione che impedisce l'eliminazione del record referenziato Relazioni "uno a uno" --------------------- Di default, le relazioni in Genropy sono di tipo "molti a uno", ovvero a un record ``cliente`` possono corrispondere molti record ``fattura`` con questo in relazione. È però possibile prevedere comportamenti differenti, per esempio in alcuni casi è possibile richiedere che la relazione sia *uno a uno*. Ipotizziamo per esempio di avere dei flussi ordine-DDT-fattura: a un ordine di un cliente seguirà un movimento di magazzino e infine verrà generata una fattura. In questo caso sarà *un solo* record di ordine a generare a sua volta *un solo* movimento ed infine *una e una sola* fattura. La definizione di questa relazione come *uno a uno* avrà come conseguenza che la relazione sarà **perfettamente percorribile in entrambi i versi**. La definizione avviene tramite l'aggiunta del parametro ``one_one`` (in questo caso vediamo quella nella tabella *fattura*, tra la fattura e il movimento):: tbl.column('movimento_id',size='22' , group='_', name_long='!![it]Movimento').relation( 'movimento.id', one_one='*', mode='foreignkey', onDelete='cascade') Si noti che il parametro *one_one* può essere settato o a `True`, e in quel caso la relazione sarà individuabile al path completo ``@package_nometabella_nomecolonna``, o alternativamente a ``one_one='*'``, in quel caso la relazione viene contraddistinta dal semplificato path ``@nome_tabella``. Relazioni "molti a molti" ------------------------- La descrizione di una relazione "molti a molti", invece, ovvero quella per la quale a molti clienti possono corrispondere molte tipologie di servizi erogate, o a dei candidati molti titoli di studio, ecc, necessita la creazione di una **tabella di relazione** ad hoc. Non sarà quindi presente un campo di relazione nella tabella stessa, bensì la relazione sarà identificata da una tabella di collegamento, che si occuperà di creare le due relazioni, una in una direzione e una in un'altra. La relazione quindi tra un record *candidato* e dei *titoli di studio* sarà espressa, nella tabella di relazione che possiamo chiamare ``candidato_titolo_studio``, in questo modo:: class Table(object): def config_db(self, pkg): tbl = pkg.table('candidato_titolo_studio', pkey='id', name_plural='!!Titoli di studio candidato', name_long=u'!!Titolo di studio candidato', caption_field='titolo_studio') self.sysFields(tbl) tbl.column('candidato_id',size='22',name_long = '!!Candidato',group='_').relation('candidato.id', onDelete='cascade', mode='foreignkey', relation_name='titoli_studio') tbl.column('titolo_studio_id',size='22',name_long = '!!Titolo studio',group='_').relation('titolo_studio.id', onDelete='cascade', mode='foreignkey', relation_name='candidati') Si noti che nella connessa tabella *candidato* potremo seguire la relazione dal suo *relation_name* ``@titoli_studio``, e da *titolo_studio* potremo trovare i candidati in ``@candidati``. .. raw:: html
**Parametri:** +------------------------+------+--------------------------------------------------+ | Nome parametro | Tipo | Descrizione | +========================+======+==================================================+ |related_column |T |Generalmente implicito, individua con quale | | | |colonna è stabilita la relazione (es: | | | |'glbl.comune.id') | +------------------------+------+--------------------------------------------------+ |relation_name |T |Permette di dare un identificativo alla relazione | | | |inversa, individuando il valore che verrà | | | |restituito (es: nella tabella clienti, il | | | |relation_name potrebbe essere proprio 'clienti'). | +------------------------+------+--------------------------------------------------+ |mode |T |Generalmente mode='foreignkey', a indicare che tra| | | |le due colonne vige un vincolo di integrità | | | |referenziale. Alternativamente può essere | | | |mode='relation', che definisce una relazione | | | |logica senza vincoli di integrati e "case- | | | |sensitive", oppure mode='insensitive' (come il | | | |precedente ma "case-insensitive"). Se omesso, in | | | |quel caso di default mode='relation' | +------------------------+------+--------------------------------------------------+ |onDelete |T |Permette di indicare il comportamento in caso di | | | |eliminazione. Valori possibili: 'raise' (se c'è | | | |una relazione il campo non può essere eliminato), | | | |'cascade' (entrambi vengono eliminati), 'ignore' | | | |(non succede nulla), 'setnull' (la relazione viene| | | |eliminata) | +------------------------+------+--------------------------------------------------+ |onDelete_sql |T |Come il precedente, ma a livello di Sql, senza | | | |informare quindi eventuali trigger presenti | +------------------------+------+--------------------------------------------------+ |one_one |B |Se impostato a True, definisce la relazione come | | | |"uno a uno", con la conseguenza che la relazione | | | |diventa percorribile anche al contrario seguendo | | | |il path "package_nometabella_nomecolonna". È | | | |possibile utilizzare al posto di one_one=True, | | | |one_one='*', così facendo il path predefinito | | | |diventa solo @nome_tabella | +------------------------+------+--------------------------------------------------+ |eager_one |B | | +------------------------+------+--------------------------------------------------+ |one\_ |T |L'etichetta data alla relazione inversa, al gruppo| | | |("one_group") o direttamente alla colonna | | | |("one_name"), se la relazione è uno a uno | +------------------------+------+--------------------------------------------------+ |many\_ |T |L'etichetta data alla relazione inversa, al gruppo| | | |("many_group") o direttamente alla colonna | | | |("many_name"), se la relazione è molti a molti | +------------------------+------+--------------------------------------------------+ |deferred |B |Se impostato a True, in caso di mode='foreignkey' | | | |il sistema non andrà in errore se viene indicata | | | |una id per la relazione per la quale non è stato | | | |ancora effettuato il commit | +------------------------+------+--------------------------------------------------+ |inheritLock |B |Se impostato a False, in caso di record primario | | | |bloccato è comunque possibile aggiungere o | | | |modificare i record secondari | +------------------------+------+--------------------------------------------------+ |inheritProtect |B |Se impostato a False, in caso di record primario | | | |bloccato è comunque possibile aggiungere o | | | |modificare i record secondari | +------------------------+------+--------------------------------------------------+ .. sectionauthor:: Davide Paci