Cominciamo quindi impostando l'ambiente di sviluppo come spiegato nell'articolo precedente, ed eseguiamo il seguente comando per creare la nostra applicazione di ecommerce:
django-admin.py startproject web_application .
cd web_application/
python manage.py startapp ecommerce
Configurare l'applicazione
I file di configurazione principali si trovano all'interno della root della cartella del progetto. Nel caso in esame, dobbiamo spostarci nella cartella web_application
. Analizziamo adesso i moduli python settings.py
e urls.py
. Il file wsgi.py
sarà esaminato nell'ultima parte della mini-serie di articoli.
File settings.py
Il file settings.py
contiene tutte le configurazioni dell'applicazione. E' gestito come un modulo python, in cui ogni variabile definisce una configurazione. Alcune variabili sono automaticamente generate tramite il comando startproject
, mentre altre possono essere inserite facendo riferimento alla documentazione ufficiale. Vediamo adesso alcune delle variabili preimpostate da Django:
SECRET_KEY
: stringa random generata automaticamente dal comandostartproject
; è la base a partire dalla quale sono crittografate le comunicazioni all'interno dell'applicazione; è altamente sconsigliato condividere il valore dellaSECRET_KEY
di produzione al di fuori dell'applicazione; si consiglia perciò di salvare il contenuto della chiave segreta in un file esterno all'applicazione, differente dasettings.py
.DEBUG
: valore booleano per abilitare le funzionalità di debug; molto utile in fase di sviluppo, è tuttavia sconsigliato lasciare il valore aTrue
in produzione perché potrebbe rivelare informazioni sensibili sull'applicazione.ALLOWED_HOSTS
: lista di stringhe che identificano gli host e i domini permessi per l'applicazione.INSTALLED_APPS
: lista di stringhe delle applicazioni installate; ogni volta che eseguite il comandostartapp
dovete ricordarvi di aggiungere alla lista il nome della app che avete creato affinché sia raggiungibile dal sito.MIDDLEWARE
: lista di stringhe con i moduli dello strato Middleware utilizzati dall'applicazione; lo strato Middleware entra in gioco quando è inviata una richiesta all'applicazione, prima che questa sia indirizzata verso una view.ROOT_URLCONF
: stringa di testo che definisce il path per il fileurls.py
dell'applicazione.TEMPLATES
: lista di dizionari delle configurazioni dei template dell'applicazione.DATABASES
: lista di dizionari per la configurazione dei database ai quali si deve connettere l'applicazione; negli esempi presentati utilizzeremo SQLite; in caso di DBMS più complessi, dove è necessario specificare USERNAME e PASSWORD per la connessione, sono consigliati gli stessi accorgimenti spiegati per la variabileSECRET_KEY
.STATIC_URL
: stringa che definisce il path per la cartella dei file di tipo statico: css, immagini, funzioni javascript, ecc...MEDIA_URL
: stringa che definisce il path per la cartella dei media caricati.
File urls.py
Ogni volta che un utente fa una richiesta per accedere ad una pagina web della vostra applicazione Django, il framework controlla nel file urls.py
se esiste una corrispondenza tra l'indirizzo richiesto e le viste definite nella applicazione.
Django è stato pensato in modo che gli indirizzi delle web application siano il più puliti e concisi possibile. Per ottenere tale chiarezza, sono utilizzati dei file di configurazione chiamati urls.py
.
Un file urls.py
contiene una lista di tuple chiamata urlpatterns
. Il nome è piuttosto esplicativo visto che ogni tupla è formata da:
- una espressione regolare utilizzata per trovare la corrispondenza con l'indirizzo web richiesto dall'utente;
- il riferimento alla vista che gestisce la chiamata; tale riferimento può essere passato o tramite il metodo
as_view()
o con la funzioneinclude
- altri possibili parametri, tra cui il nome da attribuire all'indirizzo.
Il codice all'interno di /web_applications/urls.py
si presenta così:
1 2 3 4 5 6 7 |
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^ecommerce/', include('ecommerce.urls')), ] |
Dal modulo django.conf.urls
sono importati i riferimenti a url
, un oggeto che prende in ingresso la tupla di cui sopra, e include
, un metodo che prende in ingresso il path verso un altro file di configurazione del tipo urls.py
. Nell'esempio mostrato, ogni volta che l'indirizzo inizia con ecommerce/
, l'applicazione cerca il matching all'interno del file urls.py
dell'applicazione ecommerce
. In questo modo è sfruttata la modularità del framework Django.
Del modulo admin
parleremo in un prossimo tutorial
Model
Adesso che abbiamo un'idea dei principali file di configurazione di Django, veniamo alla creazione del modello per la nostra applicazione di e-commerce.
Per il momento andremo a creare una semplice ontologia con due entità: prodotto e casa produttrice. Ogni prodotto ha obbligatoriamente una casa produttrice, ed ovviamente una cosa produttrice ha 0 o più prodotti.
Utilizzeremo come chiavi primarie degli id numerici progressivi, ma aggiungeremo anche i vincoli di obbligatorietà per i campi più importanti, e di unicità per il campo nome del prodotto della casa produttrice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from django.db import models class Casa(models.Model): nome = models.CharField(max_length=100, blank=False) about = models.TextField(max_length=400) email = models.EmailField(blank=False) class Meta: ordering = ['nome'] def __str__(self): return '{} {}'.format(self.nome, self.email) class Prodotto(models.Model): DIMENSIONI = (('P', 'Piccolo'), ('M', 'Medio'), ('G', 'Grande'),) nome = models.CharField(max_length=100, blank=False, unique=True) dettagli = models.TextField(max_length=255) prezzo = models.DecimalField(max_digits=10, decimal_places=2, blank=False) immagine = models.FileField() dimensione = models.CharField(max_length=1, choices = DIMENSIONI) data_rilascio = models.DateField(auto_now=True) spedizione_gratuita = models.BooleanField(default=False) casa = models.ForeignKey(Casa, on_delete=models.CASCADE) class Meta: ordering = ['nome'] def __str__(self): return '{} {}'.format(self.nome, self.prezzo) |
Definiamo le classi Casa
e Prodotto
all'interno del file ecommerce/models.py
. Entrambe le classi derivano dalla classe Model
di models
. In questo modo, Casa
e Prodotto
ereditano tutti i metodi e gli attributi per gestire il salvataggio ed il recupero delle istanze dalle tabelle del database.
Attributi
Gli attributi delle classi che devono rappresentare una colonna nelle tabelle del database sono definiti utilizzando le classi Field
di models
. Nell'esempio mostrato abbiamo:
CharField
: corrisponde ad un attributo SQL di tipovarchar
; è obbligatorio definirne la lunghezza massima.TextField
: corrisponde ad un attributo SQL di tipotext
; a differenza diCharField
, per questa tipologia di campo non deve essere specificata una lunghezza massima; è utile quindi per salvare lunghe descrizioni degli oggetti.EmailField
: campo di testo con dei vincoli di integrità definiti per fare in modo che il contenuto sia della forma di un indirizzo di posta elettronica.DecimalField
: campo numerico, corrispondente ad un attributo SQL di tipodecimal
; ha due campi obbligatori,max_digits
edecimal_places
.BooleanField
: attributo boolean.DateField
: corrisponde ad un attributo SQL di tipodate
; salva un oggetto Python del tipodatetime.date
.FileField
: classeField
per il caricamento e salvataggio dei file; è necessario valorizzare i campiMEDIA_ROOT
eMEDIA_URL
nel filesettings.py
per permettere il salvataggio dei file caricati.ForeignKey
: oggetto usato per definire una relazione uno-a-molti tra due entità; nell'esempio mostrato, ogni prodotto è fabbricato da una casa produttrice; per questo motivo è stato aggiunto il riferimentocasa
all'interno del modelloProdotto
.
Oltre alla relazione uno-a-molti, Django consente l'utilizzo di relazioni uno-a-uno e molti-a-molti grazie alle classi OneToOneField
e ManyToManyField
.
Django mette a disposizione molte altre classi di tipo Field
e permette estendere tali classi per creare oggetti Field
ad hoc, come questo progetto per la gestione dei campi criptati.
Parametri
Passiamo in rassegna i differenti parametri utilizzati nella definizione dei campi:
null
: se impostato aFalse
corrisponde al vincoloNOT NULL
di SQL; se impostato aTrue
rende possibile valorizzare l'attributo con valorenull
.blank
: se impostato aFalse
fa in modo che il campo sia considerato obbligatorio e che debba essere obbligatoriamente valorizzato.unique
: se impostato aTrue
impone che tutti i valori del campo siano unici, senza ripetizioni.max_length
: imposta la lunghezza massima del campo.max_digits
: parametro dei campiDecimal
, definisce la lunghezza massima del numero salvato, considerando sia parte intera che decimale.decimal_places
: parametro dei campiDecimal
, definisce il numero di cifre decimale da salvare.on_delete
: parametro dei campi per le relazioni tra entità, definisce il comportamento da tenere in caso di cancellazione di una istanza; nell'esempio utilizzato, con l'opzionemodels.CASCADE
, si fa in modo che se una istanza di una casa produttrice è eliminata dalla tabella, tutti i prodotti collegati saranno eliminati di conseguenza.
Meta
In entrambe le classi del modello è definita una sottoclasse Meta
. Tale sottoclasse contiene tutte le informazioni che non vogliamo siano salvate come attributi della tabella. Nell'esempio presentato, è stato scelto di indicare il tipo di ordinamento delle tabelle.
Ereditarietà
Django consente di creare gerarchie di entità. Potrebbe essere utile per esempio definire soltanto una volta degli attributi generici che devono essere utilizzati da più entità. Ho aggiunto al codice precedentemente mostrato una classe GenericObject
all'interno della quale sono definiti due attributi data, created_at
e updated_at
. I parametri auto_now_add
e auto_now
fanno sì che in created_at
sia salvata la data in cui l'oggetto è stato creato, mentre in updated_at
sia salvata la data dell'ultima modifica all'istanza.
Siccome non vogliamo che sia creata una tabella per GenericObject
aggiungiamo alla sottoclasse Meta
l'attributo abstract
, inizializzato a true
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
from django.db import models class GenericObject(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Casa(GenericObject): nome = models.CharField(null=False, blank=False, max_length=100) about = models.TextField(null=True, blank=True, max_length=400) email = models.EmailField(null=False, blank=False,) class Meta: ordering = ['nome'] def __str__(self): return '{} {}'.format(self.nome, self.email) class Prodotto(GenericObject): DIMENSIONI = (('P', 'Piccolo'), ('M', 'Medio'), ('G', 'Grande'),) nome = models.CharField(null=False, blank=False, unique=True, max_length=100) dettagli = models.TextField(null=True, blank=True, max_length=255) prezzo = models.DecimalField(null=False, blank=False, max_digits=10, decimal_places=2) immagine = models.FileField(null=True, blank=True) dimensione = models.CharField(null=False, blank=False, max_length=1, choices = DIMENSIONI) spedizione_gratuita = models.BooleanField(default=False) casa = models.ForeignKey(Casa, on_delete=models.CASCADE) class Meta: ordering = ['nome'] def __str__(self): return '{} {}'.format(self.nome, self.prezzo) |
Popolamento con shell
In un prossimo tutorial vedremo come utilizzare le funzioni di amministrazione per le vostre applicazioni Django. In attesa di ciò, armiamoci di santa pazienza, e popoliamo il nostro database utilizzando la shell di Django.
Sebbene, come vedremo in seguito, l'uso delle funzioni admin sia molto più comodo per inserire nuove istanze, utilizzare la shell vi permetterà di applicare metodi Python definiti nell'applicazione direttamente sulle istanze ottenute. Ciò potrebbe salvarvi la vita se in futuro doveste debuggare il codice online.
Prima di aprire il terminale, ricordiamoci di eseguire i comandi makemigrations
e migrate
per configurare il database secondo le impostazioni definite dal modello. Spostiamoci col terminale alla base del nostro progetto e digitiamo:
python ./manage.py makemigrations
python ./manage.py migrate
Adesso digitiamo il seguente comando per aprire la shell i Django.
python ./manage.py shell
Dentro la shell, aggiungiamo al nostro database alcune istanze con le istruzioni python mostrate:
1 2 3 4 5 6 7 8 9 |
from ecommerce.models import Prodotto, Casa Casa.objects.create(nome='Sony', email='info@sony.com') Casa.objects.create(nome='Nintendo', email='info@nitendo.com') sony = Casa.objects.filter(nome='Sony') nintendo = Casa.objects.filter(nome='Nintendo') Prodotto.objects.create(nome='GameBoy', prezzo=150.00, dimensione='P', casa=nintendo[0]) Prodotto.objects.create(nome='SNES', prezzo=249.99, dimensione='M', casa=nintendo[0]) Prodotto.objects.create(nome='PSP', prezzo=170.00, dimensione='P', casa=sony[0]) Prodotto.objects.create(nome='PlayStation', prezzo=349.99, dimensione='M', casa=sony[0]) |
Il primo comando importa semplicemente le classi Prodotto
e Casa
. A questo punto è possibile utilizzare tutti i metodi che si interfacciano col database definiti in objects
della classe Model
. Con il metodo create
inseriamo le nuove istanze nelle tabelle. Si noti che ogni volta che inseriamo un nuovo oggetto Prodotto
, dobbiamo attribuirgli un riferimento alla sua casa produttrice. Per questo motivo, utilizziamo il metodo filter
per ricercare nella tabella le case produttrici con quello specifico nome. Il metodo ci restituisce un elenco di risultati all'interno di una collezione QuerySet
. In questo caso, poiché siamo sicuri che esista una sola casa produttrice col nome cercato, prendiamo l'oggetto di indice 0.
Ipotizziamo infine di voler abbassare il prezzo di un prodotto.
1 2 3 |
p = Prodotto.objects.get(nome='PSP') p.prezzo = 134.99 p.save() |
Il metodo save
esegue una update dell'oggetto p
. Se controllate il valore dell'attributo updated_at
vedrete che sarà cambiato rispetto al valore dell'attributo created_at
.
Nell'esempio precedente, il metodo save
è utilizzato per fare un aggiornamento di un oggetto già esistente, ma può essere utilizzato anche per inserire un nuova instanza:
1 2 |
n64 = Prodotto(nome='Nitendo64', prezzo=230.00, dimensione='G', casa=nintendo[0]) n64.save() |
Infine, è possibile eliminare un oggetto utilizzando il metodo delete
:
1 |
n64.delete() |
Nel prossimo articolo della serie su Django vedremo come utilizzare Viste e Template per comunicare col Modello creato e mostrare nelle pagine web le informazioni salvate nel database.
Potete trovare il codice completo dell'applicazione di esempio che andremo a costruire sul nostro repository github.
Vi invitiamo come sempre a commentare l'articolo per domande, approfondimento e, soprattutto, correzioni.
Fonti
