• Non ci sono risultati.

5.2 Il server web

5.2.2 Il database

Come abbiamo gi`a accennato utilizzeremo il DBMS PostgreSQL. Come ogni DBMS che si rispetti esso offre un’estensione del linguaggio SQL sia per la gestione di tipi di dato, sia per l’automazione dei controlli di integrit`a dei dati, sia per la possibilit`a di programmare trigger all’inserimento, modifica o rimozione di tuple nelle tabelle.

Il lavoro fondamentale che `e necessario effettuare in fase progettuale `e quello di stabilire la struttura logica del database, ovvero il modo in cui i dati verranno strutturati in memoria e quindi poi recuperati per mezzo delle interrogazioni. Per progettare tale struttura logica abbiamo utilizzato il modello di rappresentazione dei dati denominato Entity-Relationship (ER), in quanto `e sia abbastanza astratto da non contenere troppe caratteristiche implementative, ma `e anche facilmente traducibile in schema relazionale SQL. Mostreremo infine la sua implementazione all’interno del framework Django e la sua traduzione finale in relazioni SQL.

Modellazione degli utenti e loro relazioni

Il fulcro del sistema sono i suoi utenti e il database deve quindi permettere la memorizzazione delle loro informazioni personali e del loro ruolo all’interno del sistema stesso. Come stabilito nei requisiti il sistema `e pensato per essere usato da due tipi di utenti: i medici e i pazienti (cfr 4.6.4 Classi di utenti). I primi hanno il ruolo di creare i questionari e gestire le somministrazioni e la raccolta dati, i secondi hanno il ruolo di compilare i questionari somministrati entro le date stabilite. ´E inoltre necessario che si stabilisca un legame tra i pazienti e i loro medici: un paziente pu`o ricevere somministrazioni soltanto dai suoi medici. Tale legame deve essere memorizzato in maniera persistente nel database.

Il database contiene innanzitutto una tabella Users che memorizza le infor- mazioni di tutti gli utenti del sistema, indipendentemente dal tipo di utente:

Figura 5.1: Esempio di struttura gerarchica di un questionario.

tali informazioni sono tipicamente l’email e la password per l’accesso alle aree riservate. Due tabelle specializzano gli utenti a seconda che siano medici o pa- zienti: Doctors e Patients contenenti dati anagrafici e informazioni aggiuntive dei medici e dei pazienti. Un’ultima tabella, la tabella Follows contiene coppie <medico,paziente> a indicare quali medici seguono un certo paziente o, viceversa, quali pazienti sono seguiti da un certo medico.

Modellazione dei questionari

Un questionario `e implicitamente organizzato secondo una struttura gerarchi- ca: contiene una serie di domande, ognuna delle quali contiene una serie di possi- bili risposte tra cui `e concesso scegliere; alcune domande del questionario, inoltre, non prevedono opzioni, ma l’inserimento diretto di input numerici o stringhe di caratteri. La struttura di un questionario pu`o quindi essere facilmente rappresen- tata con un albero ordinato. La Figura 5.1 mostra un esempio di tale situazione. Nella Figura esiste un questionario Quest il quale contiene sei domande: d1 `e di tipo “opzione singola”, ovvero una e una sola scelta possibile tra le tre presenti o1, o1 e o3; d2 `e di tipo “opzione multipla”, ovvero un numero qualunque di scelte effettuabili tra le tre disponibili o4, o5 e o6; d3 di tipo condizionale, ovvero crea un branch logico in cui se il paziente risponde “s`ı” si sposta alla prossima doman- da altrimenti passa a d5; d4 di tipo numerico senza range; d5 di tipo numerico con range; d6 di tipo stringa. Supponiamo che un medico voglia assegnare tale questionario al paziente p1 con cadenza settimanale, ogni luned`ı per due luned`ı consecutivi e lo stesso questionario al paziente p2 un’unica volta senza ripetizio-

ni3. Per complicare un po’ lo scenario supponiamo che vogliamo memorizzare il

questionario Quest in due lingue: italiano e inglese. Il problema che ci poniamo `e come strutturare in tabelle relazionali una struttura cos`ı complessa e ricca di possibilit`a.

La soluzione al problema appena discusso passa attraverso la separazione dei concetti di domande di un questionario, di opzioni relative a una domanda, di somministrazione e di outcome di un questionario. Quando si definiscono le domande di un questionario bisogna ammettere che una stessa domanda abbia pi`u entry, una per ogni lingua usata. Inoltre bisogna permettere di selezionare il tipo della domanda: `e una domanda con opzioni? `e una domanda senza opzioni? se `e senza opzioni, ci si aspetta un numero o una stringa come input? se ci si aspetta un input, che range deve avere? La nostra soluzione a questo problema `e una tabella Questions nella quale si indica il tipo di domanda: ‘o’ per opzionale, ‘b’ per branch logico, ‘n’ per input numerico senza range, ‘r’ per input numerico con range, ‘s’ per input stringa. Altri due attributi numerici lef t val e right val servono per impostare caratteristiche aggiuntive della domanda: se la domanda `e di tipo ‘r’ i due valori indicano il range di validit`a dell’input numerico che l’utente pu`o fornire; se la domanda `e di tipo ‘s’ i due numeri indicano la lunghezza minima e massima della stringa che l’utente deve inserire; se la domanda `e di tipo ‘o’ i due valori indicano il numero minimo e il numero massimo di opzioni contemporaneamente selezionabili (per esempio lef t val = 1 e right val = 1 indica che `e possibile scegliere una e una sola opzione, lef t val = 0 e right val = ∞ indica che `e possibile scegliere un numero arbitrario di opzioni, anche nessuna). Infine, se la domanda `e di tipo ‘b’ i due valori indicano la prossima domanda a cui andare se l’utente ha selezionato rispettivamente la prima o la seconda delle due opzioni disponibili, nel seguente modo: lef t val indica l’offset, nella sequenza del questionario, tra la domanda attuale e la prossima domanda, qualora l’utente scegliesse la prima delle due opzioni disponibili; rightVal, invece, `e l’offset qualora

l’utente scegliesse la seconda opzione. Ad esempio, se lef t val = −2 e l’utente

3E esattamente lo stesso esempio discusso in precedenza, in sezione 5.1.1 XML per nuovo´

scegliesse la prima opzione della domanda, nel momento in cui si procedesse nel questionario, l’utente verrebbe riportato indietro di due domande4.

La tabella Options memorizza le opzioni possibili per quelle domande che sono di tipo ‘o’. La tabella Givings invece viene utilizzata per la somministrazione dei questionari ai pazienti. In tale tabella `e possibile indicare una finestra di tempo durante la quale il questionario `e compilabile e non vi `e nessuna restrizione a quante volte `e possibile somministrare lo stesso questionario a un certo paziente. L’ultima tabella, la tabella Outcomes, `e quella che memorizza i risultati delle esecuzioni dei questionari. Essa `e capace di memorizzare, per ogni domanda, uno dei seguenti tre dati: 1) la/e scelta/e effettuata/e se si tratta di domanda di tipo opzionale; 2) il numero inserito se si tratta di domanda numerica; 3) la stringa inserita se la domanda `e di tipo stringa. ´E inoltre previsto l’uso di speciali triggers che si attivino all’inserimento di dati all’interno della tabella Outcomes, al fine di controllare che vengano rispettati i vincoli stabiliti per il tipo di domanda e rifiutare input fuori norma.

Diagramma ER e schema relazionale

La Figura 5.2 mostra il diagramma ER che descrive quanto detto finora. Nel diagramma le entit`a sono rappresentate dai rettangoli, le relazioni dai rombi, le linee indicano partecipazione multipla, le frecce indicano partecipazione singola e linee e frecce in grassetto indicano partecipazione obbligatoria. Inoltre, gli at- tributi sottolineati indicano le chiavi primarie e quelle sottolineate con tratteggio indicano gli attributi unici. Da notare che sono stati omessi molti attributi sup- plementari per ragioni di semplicit`a del diagramma. Il primo passo `e quello di trasformare tale modello dei dati in classi Python nel framework di Django. Esso stesso si preoccuper`a di effettuare la trasformazione del modello a classi in mo- dello relazionale SQL. Ad ogni modo, per completezza, riportiamo qui di seguito sia la rappresentazione del modello in classi Python, sia quella in SQL (in una forma leggermente diversa rispetto a quella che Django utilizza in pratica).

4Questo `e ovviamente un esempio poco utile in pratica, ma adatto a chiarire le idee

L’entit`a Users `e gi`a implementata da Django nel suo modulo di autenticazione e quindi possiamo ometterla. Le classi per Patients e Doctors importeranno le chiavi primarie dall’entit`a Users e specializzeranno gli utenti a seconda che siano pazienti o dottori, come mostrato qui di seguito.

class Patient(models.Model):

pid = models.ForeignKey(User, primary_key=True) name = models.CharField(max_length=200)

class Doctor(models.Model):

did = models.ForeignKey(User, primary_key=True) pid = models.ManyToManyField(Patient)

name = models.CharField(max_length=200)

Tale rappresentazione pu`o essere espressa in SQL nel seguente modo, dove la tabella Follows implementa la relazione many-to-many tra pazienti e dottori:

CREATE TABLE Patients (

pid integer PRIMARY KEY, name varchar(200),

FOREIGN KEY (pid) REFERENCES Users(uid) );

CREATE TABLE Doctors (

did integer PRIMARY KEY, name varchar(200),

FOREIGN KEY (did) REFERENCES Users(uid) );

CREATE TABLE Follows ( did integer,

pid integer,

FOREIGN KEY (did) REFERENCES Doctors(did), FOREIGN KEY (pid) REFERENCES Patients(pid), PRIMARY KEY (did, pid)

Le tabelle Questionnaires, Questions e Options servono a memorizzare le strutture gerarchiche dei questionari. Nella prima tabella viene anche memo- rizzato l’ID del dottore che ha creato il questionario. Lo schema permette di memorizzare questionari in pi`u lingue mediante l’uso di apposite tabelle e dell’at- tributo lang, una stringa che indichi la lingua secondo uno degli standard usati da HTTP, come ad esempio l’RFC 5646 [Phillips and Davis, 2009]. La tabella delle domande permette inoltre di gestire il tipo di domanda mendiate l’attributo type e il range dei valori ammessi in input o del numero di opzioni contemporaneamen- te selezionabili o ancora la prossima domanda nel caso di branching condizionale, mediante left val e right val, come abbiamo precedentemente illustrato.

class Questionnaire(models.Model):

quid = models.AutoField(primary_key=True) created_by = models.ForeignKey(Doctor)

description = models.CharField(max_length=1000, null=True) class Questionnaire_Lang(models.Model): quid = models.ForeignKey(Questionnaire) lang = models.CharField(max_length=5) title = models.CharField(max_length=1000) info = models.TextField() class Question(models.Model): qid = models.AutoField(primary_key=True) quid = models.ForeignKey(Questionnaire) type = models.CharField(max_length=3)

left_val = models.DecimalField(max_digits=32, decimal_places=16) right_val = models.DecimalField(max_digits=32, decimal_places=16) description = models.CharField(max_length=500, null=True)

class Question_Lang(models.Model): qid = models.ForeignKey(Question) lang = models.CharField(max_length=5) string = models.CharField(max_length=500) class Option(models.Model): oid = models.AutoField(primary_key=True) qid = models.ForeignKey(Question)

lang = models.CharField(max_length=5)

description = models.CharField(max_length=500)

La struttura relazionale pu`o essere espressa nel seguente modo, dove abbiamo omesso le tabelle delle lingue in quanto immediate:

CREATE TABLE Questionnaires (

quid serial PRIMARY KEY,

description varchar(1000) NOT NULL, created_by_did integer NOT NULL,

FOREIGN KEY (created_by_did) REFERENCES Doctors(did), );

CREATE TABLE Questions (

qid serial PRIMARY KEY,

quid integer,

type char(1) NOT NULL

CHECK(type=’n’ OR type=’s’ OR type=’o’ OR type=’b’), left_val numeric(32,16),

right_val numeric(32,16),

description varchar(500) NOT NULL,

FOREIGN KEY (quid) REFERENCES Questionnaires(quid), );

CREATE TABLE Options (

oid serial PRIMARY KEY,

qid integer NOT NULL,

lang varchar(5),

description varchar(500),

FOREIGN KEY (qid) REFERENCES Questions(qid), );

Le ultime due tabelle memorizzano rispettivamente le somministrazioni dei questionari ai pazienti e i loro risultati, somministrazione per somministrazione. La tabella Givings memorizza l’ID del questionario somministrato, l’ID del me- dico che effettua la somministrazione, l’ID del paziente a cui si somministra il questionario, la data di inizio e di fine della somministrazione e un booleano che

indica se quella somministrazione `e gi`a stata completata5. La tabella Outcomes

`e atta a contenere il riferimento alla somministrazione cui si riferisce, l’ID della domanda cui si riferisce e un dato che indica la scelta effettuata o il valore di input inserito. Per risalire, ad esempio, al paziente che ha inviato il risultato baster`a unire questa tabella con la tabella Givings selezionando le tuple con attributo gid uguale. class Giving(models.Model): gid = models.AutoField(primary_key=True) did = models.ForeignKey(Doctor) pid = models.ForeignKey(Patient) quid = models.ForeignKey(Questionnaire) from_date = models.DateTimeField() to_date = models.DateTimeField() given_date = models.DateTimeField(auto_now=True) completed = models.BooleanField(default=False) class Outcome(models.Model): outid = models.AutoField(primary_key=True) gid = models.ForeignKey(Giving) qid = models.ForeignKey(Question)

oid = models.ForeignKey(Option, null=True)

num = models.DecimalField(max_digits=32, decimal_places=16, null=True) string = models.CharField(max_length=5, null=True)

date = models.DateTimeField(auto_now=True)

Ecco, infine, la loro rappresentazione SQL:

CREATE TABLE Givings (

gid serial PRIMARY KEY,

did integer NOT NULL, -- dottore che somministra

pid integer NOT NULL, -- paziente a cui si somministra

quid integer NOT NULL, -- questionario somministrato

5Tale ridondanza `e stata inserita per motivi prestazionali, in quanto consente di evitare

from_date timestamp NOT NULL, -- data inizio compilazione to_date timestamp NOT NULL, -- data scadenza compilazione given_date timestamp DEFAULT NOW(),-- data di somministrazione completed boolean DEFAULT FALSE,

FOREIGN KEY (did) REFERENCES Doctors(did), FOREIGN KEY (pid) REFERENCES Patients(pid),

FOREIGN KEY (quid) REFERENCES Questionnaires(quid) );

CREATE TABLE Outcomes (

outid serial PRIMARY KEY, gid integer NOT NULL, qid integer NOT NULL, oid integer,

num numeric(32,16), string varchar(500),

date timestamp DEFAULT NOW(),

FOREIGN KEY (gid) REFERENCES Givings(gid), FOREIGN KEY (qid) REFERENCES Questions(qid) FOREIGN KEY (oid) REFERENCES Options(oid) );

Inoltre, Django si occupa automaticamente di creare degli indici sulle tabelle, utilizzando le strutture dati a disposizione di PostgreSQL (es. B-tree, Hash, ecc.).

5.2.3

Funzioni MVC

Come abbiamo gi`a accennato, in un MVC il Controller gestisce la logica di accesso alle risorse del server e fa da tramite tra l’utente e i dati presenti nel sistema. a una richiesta dell’utente (tipicamente una URL) il Controller esegue le azioni corrispondenti, se necessario interroga e/o modifica li database e infine genera una View che rappresenta il nuovo stato del sistema dopo l’operazione effettuata. Il Controller `e quindi il centro nevralgico di un sistema basato sul pattern MVC. Programmarlo significa fornire all’utente tutte le funzionalit`a da lui richieste.

Nella filosofia di Django non vi `e una netta distinzione tra le View e il Con- troller, in quanto si guarda a una View come a un modo unico di chiamare una

funzionalit`a del server, alla quale sia attribuito uno specifico modello di rappre- sentazione grafica6. Ci`o che conta `e che a ogni View corrisponde una specifica

funzionalit`a del server e questo lo mette in stratta relazione con il Controller, tanto da sfocare i confini tra di essi. In questa sezione ci occuperemo di proget- tare le funzioni principali della componente View/Controller di P.ROse, al livello di astrazione pi`u basso che forniremo in questa trattazione. Mostreremo qui di seguito le funzioni lato server secondo un preciso schema: in alto indicheremo la URL relativa alla risorsa che staremo per definire unitamente al metodo HTTP con il quale `e possibile interrogarla (GET oppure POST); sotto indicheremo la categoria di utenti (cfr. 4.6.4 Classi di utenti) ai quali la risorsa verr`a resa di- sponibile (alcune risorse sono disponibili anche a utenti non registrati, altri sono a tutti gli utenti registrati, altre ancora solo a dottori o solo a pazienti); infine descriveremo in linguaggio naturale la risorsa stessa e/o il suo comportamento at- teso, evidenziando, qualora necessario, comportamenti speciali che il codice lato server deve avere.

• POST /register

Disponibile a: Utenti non registrati

Funzionalit`a che permette la registrazione a P.ROse per mezzo di un indirizzo email valido. Una volta registrati al servizio sar`a necessario validare l’indirizzo email per mezzo di una speciale URL.

• GET /register/confirm/<info>

Disponibile a: Utenti registrati ma non confermati

Funzionalit`a che permette la finalizzazione della registrazione. Il campo info contiene le informazioni circa l’utente che si sta confermando e, possibilmente, stringhe per controlli aggiuntivi. Notare che il metodo utilizzato qui `e il GET, poich´e il link a tale risorsa viene comunemente fornito all’utente tramite email e invocato senza l’ausilio di un form.

6Come si pu`o leggere nel sito stesso di Django: “A view is a ‘type’ of Web page in your

• GET /home

Disponibile a: Utenti non registrati

Pagina principale di P.ROse. E’ una pagina ad accesso pubblico. • GET /accounts/login

Disponibile a: Tutti gli utenti

Mostra il form per effettuare il login. • POST /accounts/login

Disponibile a: Tutti gli utenti

Funzione di autenticazione. Restituisce, in caso di successo, un cookie con sessione opportunamente impostata.

• GET /accounts/profile

Disponibile a: Dottori e pazienti

Permette di accedere alla pagina del proprio profilo personale, nella quale si possono visualizzare e modificare le proprie informazioni. L’utente deve essere gi`a autenticato oppure si verr`a reindirizzati alla pagina di login (ci`o vale per tutte le funzioni da qui in poi).

• POST /accounts/profile

Disponibile a: Dottori e pazienti

Permette di modificare i dati presenti nel proprio profilo personale. • GET /accounts/profile/<pid>

Disponibile a: Dottori

Permette di accedere alla pagina del profilo personale del paziente pid, il quale deve essere gi`a connesso al dottore che la richiede.

• GET /accounts/profile/<did>

Disponibile a: Dottori

Permette di accedere alla pagina del profilo personale del dottore did, il quale deve essere gi`a connesso all’utente che la richiede.

• GET /doctors/mine

Disponibile a: Pazienti

Visualizza la lista dei dottori del paziente correntemente loggato. • GET /patients/mine

Disponibile a: Dottori

Visualizza la lista dei pazienti sotto l’ala del dottore correntemente loggato. • POST /patients/new

Disponibile a: Dottori

Permette di aggiungere un paziente tra quelli del dottore. L’aggiunta avviene per mezzo di un indirizzo email al quale il sistema provveder`a a inviare richiesta di accettazione. Solo quando il paziente avr`a accettato la richiesta il paziente ricever`a le somministrazioni del dottore e il dottore potr`a ricevere i risultati dei questionari (cfr. 4.7.1 Aggiunta di pazienti gi`a registrati).

• GET /givings

Disponibile a: Pazienti

Visualizza la lista dei questionari ePRO ancora da effettuare. Mostra soltanto i questionari il cui periodo di validit`a comprende la data e l’ora in cui la risorsa viene richiesta.

• GET /givings

Disponibile a: Dottori

Visualizza la lista dei questionari che un dottore ha somministrato ai propri pazienti, permettendo la navigazione sia di quelli gi`a completati, sia di quelli ancora da completare. Deve consentire, come minimo, il raggruppamento dei questionari per paziente, per data di somministrazione e per data di validit`a. • GET /givings/outcomes/<gid>

Disponibile a: Dottori

Prende in input un gid, ovvero l’ID di una singola somministrazione, e ne mostra tutti i risultati.

• GET /givings/outcomes/<gid1,. . . ,gidn>

Disponibile a: Dottori

Prende in input una lista di gid separati da virgola o altro delimitatore e mostra una visualizzazione aggregata e comparata dei risultati di tutte le sommini- strazioni specificate, includendo informazioni come: la percentuale di pazienti che hanno completato i questionari, medie, massimi e minimi di ogni serie di questionari, grafici di andamento, ecc.

• POST /givings/new/<quid>/<pid>

Disponibile a: Dottori

Permette la somministrazione del questionario quid al proprio paziente pid.

• GET /givings/compile/<gid>

Disponibile a: Dottori e pazienti

Permette la ricezione lato client del questionario corrispondente alla sommini- strazione gid ai fini della sua compilazione. Questa richiesta lancia l’interfaccia di esecuzione del questionario. La funzionalit`a `e disponibile sia ai pazienti che ai dottori. Nel caso di un paziente bisogna controllare che la somministrazione sia stata attribuita proprio a lui o lei. Se il questionario `e stato richiesto da un dottore, `e necessario accertarsi che la somministrazione sia stata effettuata dal dottore in questione e, per motivi di sicurezza, `e necessario che il paziente inserisca comunque le proprie credenziali di accesso (cfr. 4.7.1 Esecuzione dei questionari). In entrambi i casi bisogna controllare che la somministrazione sia attiva nel momento della chiamata (ovvero che non sia troppo presto o troppo tardi).

• GET /givings/getxml/<gid>

Disponibile a: Dottori e pazienti

Questa funzione permette di ricevere una rappresentazione XML del questio- nario corrispondente alla somministrazione gid. Il formato `e quello descritto in sezione 5.1 Interfacce XML.

• POST /givings/done/<gid>

Disponibile a: Dottori e pazienti

Funzione del server da chiamare quando si vogliono inviare i risultati di un questionario appena completato. La funzione effettua gli stessi controlli indicati nel punto precedente e infine notifica l’utente dell’avvenuto salvataggio dei dati o del fallimento dell’operazione.

• GET /epros

Disponibile a: Dottori

Permette di accedere alla lista dei questionari presenti nel sistema e di poter- ne selezionare uno da somministrare a un proprio paziente o a un gruppo di pazienti.

• GET /epros/create

Disponibile a: Dottori

Permette di accedere all’interfaccia per la creazione di nuovi questionari.

• POST /epros/new

Disponibile a: Dottori

Funzione del server da chiamare quando si vuole aggiungere al sistema un nuovo questionario appena creato. La definizione del questionario avviene lato client e mediante questa funzione `e possibile persistere il questionario nel database centrale.

• POST /epros/edit/<quid>

Documenti correlati