Punti salienti di Django: modelli, amministrazione e sfruttamento del database relazionale (parte 3)

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Il pannello di amministrazione è una delle funzionalità più potenti e flessibili fornite dal framework Web di Django, che combina funzionalità pronte all'uso istantanea con personalizzazione infinita. Utilizzando un progetto di esempio basato su un sistema di inventario delle biblioteche, utilizzeremo il pannello di amministrazione per apprendere come creare modelli e interagire con i database relazionali in Django.

Prima di iniziare, voglio notare che le capacità amministrative integrate di Django, anche dopo la personalizzazione, non sono pensate per gli utenti finali. Il pannello di amministrazione esiste come strumento per sviluppatori, operatori e amministratori per la creazione e la manutenzione del software. Non è inteso per essere utilizzato per fornire agli utenti finali capacità di moderazione o altre capacità di amministratore sulla piattaforma che sviluppi.

Questo articolo si basa su un'ipotesi in due parti:

  1. Il pannello di amministrazione di Django è così intuitivo che praticamente sai già come usarlo.
  2. Il pannello di amministrazione di Django è così potente che possiamo usarlo come strumento per imparare a rappresentare i dati in un database relazionale utilizzando un modello Django.

Offro queste idee con l'avvertenza che dovremo ancora scrivere del codice di configurazione per attivare le capacità più potenti del pannello di amministrazione e dovremo comunque utilizzare l'ORM basato su modelli di Django (mappatura relazionale degli oggetti) per specificare la rappresentazione dei dati nel nostro sistema.

Lettura consigliata

"Django Highlights" è una serie che introduce importanti concetti di sviluppo web in Django. Potresti voler leggere come fornire flussi di autenticazione utente sicuri e seguire insieme una dimostrazione sull'uso dei modelli Django per scrivere pagine complesse.

Altro dopo il salto! Continua a leggere sotto ↓

Impostare

In questo articolo lavoreremo con un progetto di esempio. Il progetto modella alcuni dati che una biblioteca memorizzerebbe sui suoi libri e utenti. L'esempio dovrebbe essere abbastanza applicabile a molti tipi di sistemi che gestiscono utenti e/o inventario. Ecco un'anteprima di come appaiono i dati:

Modello di dati. (Grande anteprima)

Completa i seguenti passaggi per eseguire il codice di esempio sul tuo computer locale.

1. Installazione dei pacchetti

Con Python 3.6 o versioni successive installato, crea una directory e un ambiente virtuale. Quindi, installa i seguenti pacchetti:

 pip install django django-grappelli

Django è il framework web con cui stiamo lavorando in questo articolo. ( django-grappelli è un tema del pannello di amministrazione che tratteremo brevemente.)

2. Ottenere il progetto

Con i pacchetti precedenti installati, scarica il codice di esempio da GitHub. Correre:

 git clone https://github.com/philipkiely/library_records.git cd library_records/library

3. Creazione di un superutente

Usando i seguenti comandi, imposta il tuo database e crea un superutente. L'interfaccia della riga di comando ti guiderà attraverso il processo di creazione di un superutente. Il tuo account superutente sarà il modo in cui accedi al pannello di amministrazione in un momento, quindi assicurati di ricordare la password che hai impostato. Utilizzo:

 python manage.py migrate python manage.py createsuperuser

4. Caricamento dei dati

Per la nostra esplorazione, ho creato un set di dati chiamato fixture che puoi caricare nel database (ulteriori informazioni su come creare una fixture alla fine dell'articolo). Usa l'apparecchiatura per popolare il tuo database prima di esplorarlo nel pannello di amministrazione. Correre:

 python manage.py loaddata ../fixture.json

5. Esecuzione del progetto di esempio

Infine, sei pronto per eseguire il codice di esempio. Per eseguire il server, utilizzare il comando seguente:

 python manage.py runserver

Apri il tuo browser su https://127.0.0.1:8000 per visualizzare il progetto. Tieni presente che verrai reindirizzato automaticamente al pannello di amministrazione in /admin/ . L'ho realizzato con la seguente configurazione in library/urls.py :

 from django.contrib import admin from django.urls import path from records import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.index), ]

combinato con il seguente semplice reindirizzamento in records/views.py :

 from django.http import HttpResponseRedirect def index(request): return HttpResponseRedirect('/admin/')

Utilizzo del pannello di amministrazione

Ce l'abbiamo fatta! Quando carichi la tua pagina, dovresti vedere qualcosa di simile al seguente:

Pagina principale del pannello di amministrazione di Django. (Grande anteprima)

Questa visualizzazione viene eseguita con il seguente codice standard in records/admin.py :

 from django.contrib import admin from .models import Book, Patron, Copy admin.site.register(Book) admin.site.register(Copy) admin.site.register(Patron)

Questa visualizzazione dovrebbe fornire una comprensione iniziale dei dati archiviati dal sistema. Rimuoverò parte del mistero: i Groups e gli Users sono definiti da Django e memorizzano informazioni e autorizzazioni per gli account sul sistema. Puoi leggere di più sul modello User in un articolo precedente di questa serie. Books , Copys e Patrons sono tabelle nel database che abbiamo creato durante l'esecuzione delle migrazioni e popolate caricando l'apparecchiatura. Si noti che Django pluralizza ingenuamente i nomi dei modelli aggiungendo una "s", anche in casi come "copie" in cui l'ortografia è errata.

Modello di dati. (Grande anteprima)

Nel nostro progetto, un Book è un record con titolo, autore, data di pubblicazione e ISBN (International Standard Book Number). La biblioteca conserva una Copy di ogni Book , o eventualmente più. Ogni Copy può essere ritirata da un Patron o potrebbe essere attualmente archiviata. Un Patron è un'estensione User che ne registra l'indirizzo e la data di nascita.

Crea, leggi, aggiorna, distruggi

Una funzionalità standard del pannello di amministrazione è l'aggiunta di istanze di ciascun modello. Fai clic su "libri" per accedere alla pagina del modello e fai clic sul pulsante "Aggiungi libro" nell'angolo in alto a destra. In questo modo verrà visualizzato un modulo, che puoi compilare e salvare per creare un libro.

Crea un libro (Anteprima grande)

La creazione di un Patron rivela un'altra funzionalità integrata nel modulo di creazione dell'amministratore: puoi creare il modello connesso direttamente dallo stesso modulo. Lo screenshot seguente mostra il pop-up che viene attivato dal segno più verde a destra del menu a discesa User . Pertanto, puoi creare entrambi i modelli sulla stessa pagina di amministrazione.

Crea un mecenate. (Grande anteprima)

È possibile creare una COPY tramite lo stesso meccanismo.

Per ogni record, puoi fare clic sulla riga per modificarla utilizzando lo stesso modulo. Puoi anche eliminare i record utilizzando un'azione dell'amministratore.

Azioni dell'amministratore

Sebbene le funzionalità integrate del pannello di amministrazione siano ampiamente utili, puoi creare i tuoi strumenti utilizzando le azioni di amministrazione. Ne creeremo due: uno per creare copie di libri e uno per archiviare i libri che sono stati restituiti alla biblioteca.

Per creare una Copy di un Book , vai all'URL /admin/records/book/ e usa il menu a tendina "Azione" per selezionare "Aggiungi una copia del libro" e poi usa le caselle di controllo nella colonna di sinistra della tabella per selezionare il libro o i libri di cui aggiungere una copia all'inventario.

Crea azione di copia. (Grande anteprima)

La creazione di questo si basa su un metodo modello che tratteremo in seguito. Possiamo chiamarla come azione di amministrazione creando una classe ModelAdmin per il modello Profile come segue in records/admin.py :

 from django.contrib import admin from .models import Book, Patron, Copy class BookAdmin(admin.ModelAdmin): list_display = ("title", "author", "published") actions = ["make_copys"] def make_copys(self, request, queryset): for q in queryset: q.make_copy() self.message_user(request, "copy(s) created") make_copys.short_description = "Add a copy of book(s)" admin.site.register(Book, BookAdmin)

La proprietà list_display indica quali campi vengono utilizzati per rappresentare il modello nella pagina di panoramica del modello. La proprietà actions elenca le azioni dell'amministratore. La nostra azione di amministrazione è definita come una funzione all'interno di BookAdmin e accetta tre argomenti: l'oggetto di amministrazione stesso, la richiesta (l'effettiva richiesta HTTP inviata dal client) e il set di query (l'elenco di oggetti le cui caselle sono state selezionate). Eseguiamo la stessa azione su ogni elemento nel set di query, quindi informiamo l'utente che le azioni sono state completate. Ogni azione dell'amministratore richiede una breve descrizione in modo che possa essere identificata correttamente nel menu a discesa. Infine, ora aggiungiamo BookAdmin durante la registrazione del modello.

La scrittura di azioni di amministrazione per l'impostazione delle proprietà in blocco è piuttosto ripetitiva. Ecco il codice per il check-in di una Copy , nota la sua quasi equivalenza all'azione precedente.

 from django.contrib import admin from .models import Book, Patron, Copy class CopyAdmin(admin.ModelAdmin): actions = ["check_in_copys"] def check_in_copys(self, request, queryset): for q in queryset: q.check_in() self.message_user(request, "copy(s) checked in") check_in_copys.short_description = "Check in copy(s)" admin.site.register(Copy, CopyAdmin)

Tema amministrativo

Per impostazione predefinita, Django fornisce stili abbastanza semplici per il pannello di amministrazione. Puoi creare il tuo tema o utilizzare un tema di terze parti per dare un nuovo aspetto al pannello di amministrazione. Un popolare tema open source è grappelli, che abbiamo installato in precedenza nell'articolo. Puoi controllare la documentazione per tutte le sue capacità.

L'installazione del tema è piuttosto semplice, richiede solo due righe. Innanzitutto, aggiungi grappelli a INSTALLED_APPS come segue in library/settings.py :

 INSTALLED_APPS = [ 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'records', ]

Quindi, regola library/urls.py :

 from django.contrib import admin from django.urls import path, include from records import views urlpatterns = [ path('grappelli/', include('grappelli.urls')), path('admin/', admin.site.urls), path('', views.index), ]

Con queste modifiche in atto, il pannello di amministrazione dovrebbe apparire come segue:

Pannello di amministrazione con tema. (Grande anteprima)

Ci sono una serie di altri temi là fuori e di nuovo puoi svilupparne uno tuo. Rimarrò con l'aspetto predefinito per il resto di questo articolo.

Capire i modelli

Ora che hai dimestichezza con il pannello di amministrazione e lo utilizzi per navigare tra i dati, diamo un'occhiata ai modelli che definiscono la struttura del nostro database. Ogni modello rappresenta una tabella in un database relazionale.

Un database relazionale memorizza i dati in una o più tabelle. Ognuna di queste tabelle ha una struttura di colonne specifica, inclusa una chiave primaria (un identificatore univoco per ogni elemento) e una o più colonne di valori, che sono di vario tipo come stringhe, numeri interi e date. Ogni oggetto archiviato nel database è rappresentato come una singola riga. La parte “relazionale” del nome deriva da quella che è probabilmente la caratteristica più importante della tecnologia: creare relazioni tra i tavoli. Un oggetto (riga) può avere un mapping uno a uno, uno a molti (chiave esterna) o molti a molti a righe in altre tabelle. Ne discuteremo ulteriormente negli esempi.

Django, per impostazione predefinita, utilizza SQLite3 per lo sviluppo. SQLite3 è un semplice motore di database relazionale e il tuo database viene creato automaticamente come db.sqlite3 la prima volta che esegui python manage.py migrate . Continueremo con SQLite3 per questo articolo, ma non è adatto per l'uso in produzione, principalmente perché le sovrascritture sono possibili con utenti simultanei. In produzione, o quando scrivi un sistema che un giorno intendi implementare, usa PostgreSQL o MySQL.

Django utilizza modelli per interfacciarsi con il database. Utilizzando parte dell'ORM di Django, il file records/models.py include più modelli, che consentono di specificare campi, proprietà e metodi per ciascun oggetto. Quando creiamo modelli, ci sforziamo per un'architettura "Fat Model", entro limiti ragionevoli. Ciò significa che la maggior parte possibile della convalida dei dati, dell'analisi, dell'elaborazione, della logica aziendale, della gestione delle eccezioni, della risoluzione dei casi limite e di attività simili dovrebbe essere gestita nelle specifiche del modello stesso. Sotto il cofano, i modelli Django sono oggetti molto complessi e ricchi di funzionalità con un comportamento predefinito ampiamente utile. Ciò rende l'architettura "Fat Model" facile da realizzare anche senza scrivere una notevole quantità di codice.

Esaminiamo i tre modelli nella nostra applicazione di esempio. Non possiamo coprire tutto, poiché questo dovrebbe essere un articolo introduttivo, non la documentazione completa del framework Django, ma evidenzierò le scelte più importanti che ho fatto nella costruzione di questi semplici modelli.

La classe Book è il più diretto dei modelli. Eccolo da record/models.py :

 from django.db import models class Book(models.Model): title = models.CharField(max_length=300) author = models.CharField(max_length=150) published = models.DateField() isbn = models.IntegerField(unique=True) def __str__(self): return self.title + " by " + self.author def make_copy(self): Copy.objects.create(book=self)

Tutti i campi CharField richiedono un attributo max_length specificato. La lunghezza convenzionale è di 150 caratteri, che ho raddoppiato per il title in caso di titoli molto lunghi. Naturalmente, c'è ancora un limite arbitrario, che potrebbe essere superato. Per una lunghezza del testo illimitata, utilizzare un TextField . Il campo published è un DateField . L'ora in cui è stato pubblicato il libro non ha importanza, ma se lo facesse userei un DateTimeField . Infine, l'ISBN è un numero intero (gli ISBN sono 10 o 13 cifre e quindi rientrano tutti nel valore massimo dell'intero) e utilizziamo unique=True poiché nessun libro può avere lo stesso ISBN, che viene quindi applicato a livello di database.

Tutti gli oggetti hanno un metodo __str__(self) che definisce la loro rappresentazione di stringa. Sovrascriviamo l'implementazione predefinita fornita dalla classe models.Model e rappresentiamo invece i libri come "titolo per autore" in tutti i punti in cui il modello sarebbe rappresentato come una stringa. Ricordiamo che in precedenza abbiamo usato list_display nell'oggetto admin di Book per determinare quali campi sarebbero stati mostrati nell'elenco del pannello di amministrazione. Se tale list_display non è presente, l'elenco admin mostra invece la rappresentazione di stringa del modello, come fa sia per Patron che per Copy .

Infine, abbiamo un metodo su Book che abbiamo chiamato nella sua azione di amministrazione che abbiamo scritto in precedenza. Questa funzione crea una Copy correlata a una determinata istanza di un Book nel database.

Passando a Patron , questo modello introduce il concetto di relazione uno-a-uno, in questo caso con il modello User integrato. Dai un'occhiata da record/models.py :

 from django.db import models from django.contrib.auth.models import User class Patron(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) address = models.CharField(max_length=150) dob = models.DateField() def __str__(self): return self.user.username

Il campo user non è esattamente una funzione biunivoca. Può esserci un'istanza User senza un'istanza Patron associata. Tuttavia, un User NON PUÒ essere associato a più di un'istanza Patron e un Patron non può esistere senza esattamente una relazione con un utente. Ciò viene imposto a livello di database ed è garantito dalla specifica on_delete=models.CASCADE : se un'istanza User viene eliminata, un Profile associato verrà eliminato.

Gli altri campi e __str__(self) li abbiamo visti prima. Vale la pena notare che è possibile raggiungere tramite una relazione uno-a-uno per ottenere attributi, in questo caso user.username , nelle funzioni di un modello.

Per espandere l'utilità delle relazioni di database, rivolgiamo la nostra attenzione a Copy from records/models.py :

 from django.db import models class Copy(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL) def __str__(self): has_copy = "checked in" if self.out_to: has_copy = self.out_to.user.username return self.book.title + " -> " + has_copy def check_out(self, p): self.out_to = p self.save() def check_in(self): self.out_to = None self.save()

Ancora una volta, abbiamo visto la maggior parte di questo prima, quindi concentriamoci sulle novità: models.ForeignKey . Una Copy deve essere di un solo Book , ma la biblioteca può avere più Copy di ogni Book . Un Book può esistere nel database senza che la biblioteca abbia una Copy nel proprio catalogo, ma una Copy non può esistere senza un Book sottostante.

Questa complessa relazione si esprime con la seguente riga:

 book = models.ForeignKey(Book, on_delete=models.CASCADE)

Il comportamento di cancellazione è lo stesso del Patron in riferimento User .

La relazione tra una Copy e un Patron è leggermente diversa. Una Copy può essere ritirata fino a un Patron , ma ogni Patron può ritirare tutte le Copy che la libreria gli consente. Tuttavia, questa non è una relazione permanente, la Copy a volte non viene ritirata. Patron se Copy s esistono indipendentemente l'uno dall'altro nel database; l'eliminazione di un'istanza di uno non dovrebbe eliminare alcuna istanza dell'altro.

Questa relazione è ancora un caso d'uso per la chiave esterna, ma con argomenti diversi:

 out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL)

Qui, avere blank=True consente ai moduli di accettare None come valore per la relazione e null=True consente alla colonna per la relazione Patron nella tabella di Copy nel database di accettare null come valore. Il comportamento di eliminazione, che verrebbe attivato su una Copy se un'istanza Patron fosse stata eliminata mentre era stata estratta la Copy , consiste nell'interrompere la relazione lasciando intatta la Copy impostando il campo Patron su null.

Lo stesso tipo di campo, models.ForeignKey , può esprimere relazioni molto diverse tra gli oggetti. L'unica relazione che non sono riuscito a inserire nell'esempio è un campo molti-a-molti, che è come un campo uno-a-uno, tranne per il fatto che, come suggerito dal suo nome, ogni istanza può essere correlata a molte altre istanze e ogni altro e ciascuno di questi può essere ricondotto a molti altri, come come un libro potrebbe avere più autori, ognuno dei quali ha scritto più libri.

Migrazioni

Ci si potrebbe chiedere come fa il database a sapere cosa è espresso nel modello. Nella mia esperienza, le migrazioni sono una di quelle cose che sono piuttosto semplici finché non lo sono, e poi ti mangiano la faccia. Ecco come mantenere intatta la tua tazza, per i principianti: scopri le migrazioni e come interagire con esse, ma cerca di evitare di apportare modifiche manuali ai file di migrazione. Se sai già cosa stai facendo, salta questa sezione e continua con ciò che funziona per te.

In ogni caso, controlla la documentazione ufficiale per una trattazione completa dell'argomento.

Le migrazioni traducono le modifiche in un modello in modifiche nello schema del database. Non devi scriverli da solo, Django li crea con il comando python manage.py makemigrations . È necessario eseguire questo comando quando si crea un nuovo modello o si modificano i campi di un modello esistente, ma non è necessario farlo durante la creazione o la modifica dei metodi del modello. È importante notare che le migrazioni esistono come una catena, ognuna fa riferimento alla precedente in modo che possa apportare modifiche prive di errori allo schema del database. Pertanto, se stai collaborando a un progetto, è importante mantenere un'unica cronologia di migrazione coerente nel controllo della versione. Quando sono presenti migrazioni non applicate, esegui python manage.py migrate per applicarle prima di eseguire il server.

Il progetto di esempio viene distribuito con una singola migrazione, records/migrations/0001_initial.py . Ancora una volta, questo è un codice generato automaticamente che non dovresti modificare, quindi non lo copierò qui, ma se vuoi avere un'idea di cosa sta succedendo dietro le quinte vai avanti e dai un'occhiata.

Infissi

A differenza delle migrazioni, le fixture non sono un aspetto comune dello sviluppo di Django. Li uso per distribuire dati campione con articoli e non li ho mai usati in altro modo. Tuttavia, poiché ne abbiamo usato uno in precedenza, mi sento in dovere di introdurre l'argomento.

Per una volta, la documentazione ufficiale è un po' scarsa sull'argomento. Nel complesso, quello che dovresti sapere è che i dispositivi sono un modo per importare ed esportare dati dal tuo database in una varietà di formati, incluso JSON, che è quello che uso. Questa funzione esiste principalmente per aiutare con cose come i test automatizzati e non è un sistema di backup o un modo per modificare i dati in un database live. Inoltre, i dispositivi non vengono aggiornati con le migrazioni e se si tenta di applicare un dispositivo a un database con uno schema incompatibile non riuscirà.

Per generare un dispositivo per l'intero database, eseguire:

 python manage.py dumpdata --format json > fixture.json

Per caricare un dispositivo, eseguire:

 python manage.py loaddata fixture.json

Conclusione

Scrivere modelli in Django è un argomento enorme e l'utilizzo del pannello di amministrazione è un altro. In 3.000 parole, sono riuscito a presentarle ciascuna. Si spera che l'utilizzo del pannello di amministrazione ti abbia fornito un'interfaccia migliore per esplorare il modo in cui i modelli funzionano e si relazionano tra loro, lasciandoti la sicurezza di sperimentare e sviluppare le tue rappresentazioni relazionali dei dati.

Se stai cercando un punto di partenza facile, prova ad aggiungere un modello Librarian che erediti da User come fa Profile . Per più di una sfida, prova a implementare una cronologia di pagamento per ciascuna Copy e/o Patron (esistono diversi modi per farlo).

Django Highlights è una serie che introduce importanti concetti di sviluppo web in Django. Ogni articolo è scritto come una guida autonoma a un aspetto dello sviluppo di Django inteso ad aiutare gli sviluppatori e i designer front-end a raggiungere una comprensione più profonda dell '"altra metà" della base di codice. Questi articoli sono costruiti principalmente per aiutarti a comprendere la teoria e le convenzioni, ma contengono alcuni esempi di codice scritti in Django 3.0.

Ulteriori letture

Potresti essere interessato ai seguenti articoli e documentazione.

  • Punti salienti di Django: modelli utente e autenticazione (parte 1)
  • Punti salienti di Django: la creazione di modelli salva le linee (parte 2)
  • Punti salienti di Django: gestione di risorse statiche e file multimediali (parte 4)
  • Documentazione dell'amministratore di Django
  • Modelli Django
  • Riferimento campo modello Django
  • Migrazioni Django