Questo tutorial estenderà il nostro sito LocalLibrary, aggiungendo pagine di elenco e pagine di dettaglio per libri e autori. Qui apprenderemo le viste generiche basate su classi e mostreremo come possono ridurre la quantità di codice che devi scrivere per casi di uso comune. Passeremo inoltre alla gestione degli URL in maggiore dettaglio, mostrando come eseguire la corrispondenza di base dei pattern.
Prerequisiti: | Completare tutti i tutorial precedenti, incluso Django Tutorial Part 5: Creating our home page. |
---|---|
Obiettivi: | Comprendere dove e come utilizzare viste generiche basate su classi e come estrarre modelli dagli URL e passare le informazioni alle viste. |
Panoramica
In questo tutorial completeremo la prima versione del sito Web LocalLibrary aggiungendo pagine di elenco e dettagli per libri e autori (o per essere più precisi, ti mostreremo come implementare le pagine del libro e ti guideremo nella creazione dellle pagine dell'autore in modo che possa farle tu stesso!)
Il processo è simile alla creazione della pagina indice, che abbiamo mostrato nel precedente tutorial. Dovremo comunque creare mappe URL, viste e template. La differenza principale è che per le pagine di dettaglio avremo la sfida aggiuntiva di estrarre le informazioni dai pattern nell'URL e passarle alla view. Per queste pagine, mostreremo un tipo di view completamente diverso: view di elenco e view di dettaglio generiche e basate su classi. Queste possono ridurre in modo significativo la quantità di codice necessario per la view, semplificandone la scrittura e la manutenzione.
La parte finale del tutorial mostrerà come impaginare i dati quando si utilizzano view generiche di elenco basate su classi.
Pagina lista di libri
Nella pagina dell'elenco dei libri verrà visualizzato un elenco di tutti i record di libri disponibili nella biblioteca, a cui è possibile accedere utilizzando l'URL: catalog/books/
. La pagina mostrerà un titolo e un autore per ogni record, con il titolo che è un collegamento ipertestuale alla relativa pagina dei dettagli del libro. La pagina avrà la stessa struttura e la stessa navigazione di tutte le altre pagine del sito e, pertanto, possiamo estendere il template di base (base_generic.html).
URL mapping
Apri /catalog/urls.py e copia la riga in grassetto sotto. Come per la pagina index, la funzione path()
definisce un pattern da matchare con l' URL ('books/'), una funzione di view che verrà richiamata se l' URL matcha (views.BookListView.as_view()
), e un nome per questa particolare mappatura.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
]
Come discusso nel precedente tutorial, l'URL deve avere già matchato /catalog
, quindi la view sarà richiamata per l'URL: /catalog/books/
.
La funzione view ha un formato diverso rispetto a prima - questo perché questa vista verrà effettivamente implementata come una classe. Noi erediteremo da una funzione di visualizzazione generica esistente che già fa la maggior parte di ciò che vogliamo, invece di scriverla noi stessi daccapo.
Per le view class-based di Django, si accede a una funzione view appropriata chiamando il metodo di classe as_view()
. Questo fa tutto il lavoro necessario per creare un'istanza della classe e assicurarsi che i giusti metodi handler vengano chiamati per le richieste HTTP in arrivo.
Viste (class-based)
Potremmo facilmente scrivere la view dell'elenco dei libri come una funzione regolare (proprio come la nostra precedente vista indice), che interrogherebbe il database cercando tutti i libri e quindi chiamerebbe render()
per passare l'elenco a un template specificato. Invece, utilizzeremo una view elenco generica basata su classi (ListView
) — una classe che eredita da una vista esistente. Poiché la vista generica implementa già la maggior parte delle funzionalità di cui abbiamo bisogno e segue la best practice di Django, saremo in grado di creare una vista elenco più robusta con meno codice, meno ripetizioni e, in definitiva, meno manutenzione.
Apri catalog/views.py, e copia il seguente codice nel file:
from django.views import generic
class BookListView(generic.ListView):
model = Book
Ecco fatto! la list view generica effettuerà una query al database per prendere tutti i record per lo specifico model (Book
) poi effettuerà un render tramite il template in /locallibrary/catalog/templates/catalog/book_list.html che creeremo sotto. Dentro al template puoi accedere alla lista dei libri con la variabile object_list
OR book_list
(cioè, genericamente "the_model_name_list
").
Nota: Questo percorso scomodo per la posizione del template non è un errore di stampa: le view generiche cercano i template in /application_name/the_model_name_list.html
(catalog/book_list.html
in questo caso) dentro l'applicazione /application_name/templates/
nella directory (/catalog/templates/)
.
È possibile aggiungere attributi per modificare il comportamento predefinito sopra. Ad esempio, è possibile specificare un altro file template se è necessario disporre di più viste che utilizzano questo stesso model oppure si potrebbe voler utilizzare un dievrso nome di variabile di template se book_list
non è intuitivo per il proprio specifico caso d'uso del template. Probabilmente la variante più utile è quella di modificare/filtrare il sottoinsieme di risultati che vengono restituiti, quindi, invece di elencare tutti i libri, potresti elencare i primi 5 libri letti da altri utenti.
class BookListView(generic.ListView):
model = Book
context_object_name = 'my_book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location
Override dei metodi nelle viste class-based
Anche se non è necessario farlo qui, puoi anche sovrascrivere alcuni dei metodi di classe.
Possiamo, per esempio, sovrascrivere get_queryset()
per modificare la lista di record restituiti. Questa metodologia è molto più flessibile rispetto all'attributo queryset
come abbiamo fatto nel precedente frammento di codice (sebbene in questo caso non ci sia alcun beneficio reale):
class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
Possiamo anche sovrascrivere get_context_data()
per passare altre variabili addizionali di context al template (es. la lista di libri è passata per default). Il frammento sotto mostra come aggiungere una variabile "some_data
" al context (sarà quindi disponibile come variabile del template).
class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(BookListView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['some_data'] = 'This is just some data'
return context
Quando si esegue questa operazione è importante seguire lo schema usato sopra:
- Per prima cosa prendi il contesto esistente dalla nostra superclasse.
- Quindi aggiungi le tue nuove informazioni di contesto.
- Quindi restituisci il nuovo contesto (aggiornato).
Nota: Leggi Built-in class-based generic views (Django docs) per vedere molti altri esempi di cosa puoi fare.
Creare List View template
Crea il file HTML /locallibrary/catalog/templates/catalog/book_list.html e copia il testo sotto. Come discusso sopra, questo è il file predefinito per un template previsto dalla vista elenco generica basata su classi (per un modello denominato Book
in un'applicazione denominata catalog
)
I template per le view generiche sono come qualsiasi altro template (anche se ovviamente il contesto/informazioni passate al template possono differire). Come con il nostro template di index, estendiamo il nostro template di base nella prima riga e poi sostituiamo il blocco denominato content
.
{% extends "base_generic.html" %}
{% block content %}
<h1>Book List</h1>
{% if book_list %}
<ul>
{% for book in book_list %}
<li>
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books in the library.</p>
{% endif %}
{% endblock %}
La view passa il contesto (elenco di libri) di default con object_list
e book_list
come alias; in ogni caso funzionerà.
Esecuzione condizionale
Usiamo i tag template if
, else
, ed endif
per controllare se la book_list
è stata definita e non è vuota. Se book_list
è vuota, allora la clausola else
mostra un testo alternativo in cui spiega che non sono presenti record da elencare. Se book_list
non è vuota, allora iteriamo sulla lista di libri.
{% if book_list %}
<!-- code here to list the books -->
{% else %}
<p>There are no books in the library.</p>
{% endif %}
La condizione sopra fa il test su un'unica condizione, ma si possono effettuare ulteriori test e gestire ulteriori casi, per testare condizioni addizionali si può usare ad esempio il tag elif
(es. {% elif var2 %}
). Per maggiori informazioni sugli operatori condizionali consultare: if, ifequal/ifnotequal, ifchanged in Built-in template tags and filters (Django Docs).
Cicli for
Il template utilizza i tag for endfor
per ciclare la lista di libri, come sotto. Ogni iterazione popola la variabile di template book
con le informazioni per l'elemento corrente della lista.
{% for book in book_list %}
<li> <!-- code here get information from each book item --> </li>
{% endfor %}
Anche se non usato qui, all'interno del loop Django creerà anche altre variabili che puoi usare per tracciare l'iterazione. Ad esempio, è possibile testare la variabile forloop.last
per eseguire l'elaborazione condizionale l'ultima volta che viene eseguito il ciclo.
Accedere alle variabili
Il codice all'interno del ciclo crea un item di elenco per ogni libro che mostra sia il titolo (come collegamento alla vista dei dettagli ancora da creare) sia l'autore.
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
Accediamo ai campi del record del libro associato utilizzando la "notazione dot" (es. book.title
e book.author
), dove il testo che segue book
è il nome del campo (come definito nel model).
Possiamo anche chiamare delle functions nel model da dentro il nostro template — in questo caso Book.get_absolute_url()
per ottenere un URL che è possibile utilizzare per visualizzare il record di dettaglio associato. Questo funziona a condizione che la funzione non abbia argomenti (non c'è modo di passare argomenti!)
Nota: Dobbiamo stare attenti agli "effetti collaterali" quando chiamiamo le funzioni nei model. Qui visualizziamo solo un URL, ma una funzione può fare praticamente qualsiasi cosa: non vogliamo rischiare di cancellare il nostro database (per esempio) semplicemente mostrando il nostro template!
Update del template di base
Apri il template di base (/locallibrary/catalog/templates/base_generic.html) ed inserisci {% url 'books' %} dentro il link Url per All books, come sotto. Questo abiliterà il link in tutte le pagine (possiamo metterlo in pratica con successo ora che abbiamo creato il mapper URL "libri").
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="">All authors</a></li>
Come viene mostrato?
Non sarà ancora possibile creare l'elenco dei libri, perché manca ancora una dipendenza: la mappa degli URL per le pagine dei dettagli del libro, necessaria per creare collegamenti ipertestuali a singoli libri. Mostreremo entrambe le viste elenco e dettaglio dopo la prossima sezione.
Pagina di dettaglio dei libri
La pagina dei dettagli del libro mostrerà le informazioni su un libro specifico, accessibile tramite l'URL catalog/book/<id>
(dove <id>
è la chiave primaria per il libro). Oltre ai campi nel model Book (autore, sommario, ISBN, lingua e genere), elencheremo anche i dettagli delle copie disponibili (BookInstances
) compreso lo stato, la data di ritorno prevista, l'edizione e l'id. Ciò consentirà ai nostri lettori non solo di conoscere il libro, ma anche di confermare se/quando è disponibile.
URL mapping
Apri /catalog/urls.py e aggiungi l'URL mapper 'book-detail' mostrato in grassetto qui sotto. Questa funzione path()
definisce un pattern, una vista di dettaglio generica basata sulla classe e un nome.
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
Per il path dei dettagli del libro, il pattern URL utilizza una sintassi speciale per catturare l'ID specifico del libro che vogliamo vedere. La sintassi è molto semplice: le parentesi angolari definiscono la parte dell'URL da catturare, racchiudendo il nome della variabile che la vista può utilizzare per accedere ai dati acquisiti. Per esempio, <something> , catturerà il pattern marcato, e passerà il valore alla view come variabile con nome "something". Puoi anche far precedere al nome della variabile una specifica di conversione che definisce il tipo di dato (int, str, slug, uuid, path).
In questo caso usiamo '<int:pk>'
per acquisire l'id del libro, che deve essere una stringa appositamente formattata e passarla alla vista come parametro chiamato pk
(abbreviazione di primary key). Questo è l'ID che viene utilizzato per archiviare il libro in modo univoco nel database, come definito nel Modello Book.
Nota: Come discusso precedentemente, il nostro URL matchato in realtà è catalog/book/<digits>
(ma perchè siamo nell'applicazione catalog /catalog/
è sottinteso).
Importante: La vista di dettaglio generica basata sulla classe prevede di passare un parametro denominato pk. Se stai scrivendo la tua vista funzione puoi usare qualsiasi nome di parametro che ti piace, o addirittura passare le informazioni in un argomento senza nome.
Manuale di nozioni avanzate su path matching ed espressioni regolari
Nota: Non avrai bisogno di questa sezione per completare il tutorial! Lo forniamo perché conoscere questa opzione rischia di essere utile nel tuo futuro incentrato su Django.
Il pattern matching fornito da path()
è semplice ed utile per il caso (molto diffuso) in cui vuoi solo catturare ogni stringa od intero. Se è necessario un filtro più raffinato (ad esempio, per filtrare solo le stringhe con un determinato numero di caratteri), è possibile utilizzare il metodo re_path().
Questo metodo funziona esattamente come path()
eccetto per il fatto che permette di specificare un pattern utilizzando una Regex. Vedi: Regular expression. Per esempio, avremmo potuto specificare il path precedente con:
re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
Le espressioni regolari sono uno strumento di mappatura dei pattern incredibilmente potente. Sono, francamente, abbastanza non intuitive e spaventose per i principianti. Di seguito è riportato un primer molto breve!
La prima cosa da sapere è che di solito le espressioni regolari dovrebbero essere dichiarate usando la sintassi "raw string" letterale (cioè, sono incluse come mostrato: r'<testo della regex>').
Le parti principali della sintassi che devi conoscere per dichiarare i match del pattern sono:
Symbol | Meaning |
---|---|
^ | Matcha l'inizio del testo |
$ | Matcha la fine del testo |
\d | Matcha un numero (0, 1, 2, ... 9) |
\w | Matcha una parola word character, es. ogni maiuscola- o minuscola- dell'alfabeto, numero o underscore (_) |
+ | Matcha uno o più caratteri precedenti. Ad esempio, per matchare una o più cifre useresti \d+ . Per abbinare uno o più caratteri "a", potresti usare a+ |
* | Abbina zero o più del carattere precedente. Ad esempio, per abbinare niente o una parola che potresti usare \w* |
( ) | Cattura la parte del pattern all'interno delle parentesi. Tutti i valori acquisiti verranno passati alla vista come parametri senza nome (se vengono catturati più pattern, i parametri associati verranno forniti nell'ordine in cui sono state dichiarate le acquisizioni) |
(?P<name>...) | Cattura il pattern (indicato da ...) come una variabile con nome (in questo caso "name"). I valori catturati sono passati alla view con il nome specificato. La tua view deve dichiarare un argomento con lo stesso nome! |
[ ] | Abbina uno dei caratteri del set. Per esempio, [abc] matcherà con 'a' o 'b' o 'c'. [-\w] restituirà un match con il carattere '-' o con ogni parola. |
La maggioranza degli altri caratteri può essere presa letteralmente!
Consideriamo alcuni esempi di pattern realistici:
Pattern | Description |
---|---|
r'^book/(?P<pk>\d+)$' |
Matcha una stringa che ha Cattura anche tutte le cifre (?P<pk>\d+) e le passa alla vista in un parametro chiamato 'pk'. I valori catturati vengono sempre passati come una stringa! Ad esempio, |
r'^book/(\d+)$' | Questo corrisponde agli stessi URL del caso precedente. Le informazioni acquisite verranno inviate come argomento senza nome alla vista. |
r'^book/(?P<stub>[-\w]+)$' |
Matcha una stringa che ha Questo è uno schema abbastanza tipico per uno "stub". Gli stub sono chiavi primarie basate sull'uso di URL per i dati. È possibile utilizzare uno stub se si desidera che l'URL del libro sia più informativo. Per esempio |
È possibile acquisire più pattern nello stesso match e quindi codificare molte informazioni diverse in un URL.
Nota: Come sfida, considera come potresti codificare un URL per elencare tutti i libri pubblicati in un particolare anno, mese, giorno e RE che potrebbero essere utilizzati per abbinarlo.
Passare opzioni addizionali nelle tue mappe URL
Una caratteristica che non abbiamo usato qui, ma che potresti trovare di valore, è che puoi dichiarare e passare alla view opzioni aggiuntive. Le opzioni sono dichiarate come dizionario che si passa come terzo argomento non assegnato (senza nome) alla funzione path()
. Questo approccio può essere utile se si desidera utilizzare la stessa view per più risorse e passare i dati per configurarne il comportamento in ciascun caso (di seguito forniamo un template diverso in ciascun caso).
path('url/', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'),
path('anotherurl/', views.my_reused_view, {'my_template_name': 'another_path'}, name='anotherurl'),
Nota: Entrambe le opzioni extra e i pattern nominati catturati vengono passati alla view come argomenti con nome. Se si utilizza lo stesso nome sia per un pattern catturato che per un'opzione extra, solo il valore del pattern catturato verrà inviato alla vista (il valore specificato nell'opzione aggiuntiva verrà scartato).
View (class-based)
Apri catalog/views.py, e copia il seguente codice alla fine del file:
class BookDetailView(generic.DetailView):
model = Book
Fatto! Tutto ciò che ti serve fare ora è creare un template chiamato /locallibrary/catalog/templates/catalog/book_detail.html, e la view passerà al database l'informazione per lo specifico record di tipo Book
estratto dall'URL mapper. All'interno del modello è possibile accedere all'elenco di libri con la variabile template denominata object
OR book
(cioè genericamente "the_model_name").
Se necessario, è possibile modificare il template utilizzato e il nome dell'oggetto contesto utilizzato per fare riferimento al libro nel template. È inoltre possibile sovrascrivere i metodi per aggiungere ulteriori informazioni al contesto, ad esempio.
Cosa succede se il record non esiste?
Se un record richiesto non esiste, la vista generica basata sulla classe genererà un'eccezione Http404 automaticamente: in produzione, verrà automaticamente visualizzata una pagina appropriata "risorsa non trovata", che è possibile personalizzare se lo si desidera. Solo per darti un'idea di come funziona, il frammento di codice seguente mostra come implementare la vista basata su classi come una funzione se non si stesse utilizzando la vista di dettaglio generica basata sulla classe.
def book_detail_view(request, primary_key):
try:
book = Book.objects.get(pk=primary_key)
except Book.DoesNotExist:
raise Http404('Book does not exist')
return render(request, 'catalog/book_detail.html', context={'book': book})
La vista prima cerca di ottenere il record del libro specifico dal modello. Se questo fallisce, la vista dovrebbe sollevare un'eccezione Http404 per indicare che il libro è "non trovato". Il passo finale è quindi, come al solito, chiamare render () con il nome del modello e i dati del libro nel parametro di contesto (come dizionario).
In alternativa, possiamo usare la funzione get_object_or_404()
come scorciatoia per sollevare un'eccezione Http404
se il record non viene trovato.
from django.shortcuts import get_object_or_404
def book_detail_view(request, primary_key):
book = get_object_or_404(Book, pk=primary_key)
return render(request, 'catalog/book_detail.html', context={'book': book})
Creare il template per la vista dettaglio
Crea il file HTML /locallibrary/catalog/templates/catalog/book_detail.html ed inserisci il seguente contenuto. come discusso precedentmente, questo nome file di default per il template è quello atteso dalla generica class-based detail view (per un modello di nome Book
in una applicazione di nome catalog
).
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ book.title }}</h1>
<p><strong>Author:</strong> <a href="">{{ book.author }}</a></p> <!-- author detail link not yet defined -->
<p><strong>Summary:</strong> {{ book.summary }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>Genre:</strong> {% for genre in book.genre.all %} {{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>
{% for copy in book.bookinstance_set.all %}
<hr>
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">{{ copy.get_status_display }}</p>
{% if copy.status != 'a' %}
<p><strong>Due to be returned:</strong> {{copy.due_back}}</p>
{% endif %}
<p><strong>Imprint:</strong> {{copy.imprint}}</p>
<p class="text-muted"><strong>Id:</strong> {{copy.id}}</p>
{% endfor %}
</div>
{% endblock %}
Il link dell'autore nel template sopra ha un URL vuoto perché non abbiamo ancora creato una pagina dei dettagli dell'autore. Una volta che esisterà, dovresti aggiornare l'URL in questo modo:
<a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
Anche se un po 'più grande, quasi tutto in questo template è stato descritto in precedenza:
- Estendiamo il nostro template di base e facciamo l'override del blocco "content".
- Utilizziamo l'elaborazione condizionale per determinare se visualizzare o meno il contenuto specifico.
- Usiamo i loop per scorrere gli elenchi di oggetti.
- Accediamo ai campi di context usando la notazione dot (poiché abbiamo usato la vista generica di dettaglio, il context è denominato
book
, potremmo anche usare "object
")
Prima non abbiamo visto la funzione interessante book.bookinstance_set.all()
. Questo metodo viene auto-magicamente creato da Django per restituire un set di record BookInstance
associati con un particolare Book
.
{% for copy in book.bookinstance_set.all %}
<!-- code to iterate across each copy/instance of a book -->
{% endfor %}
Questo metodo è necessario perchè hai dichiarato una ForeignKey
(uno-a-molti) solamente da una parte della relazione. Poichè non hai fatto nulla per dichiarare la relazione negli altri ("molti") modelli, non ci sono alcun campo da cui prendere il set di record associati. Per superare questo problema, Django costruisce un appropriata funzione di nome "reverse lookup" (ricerca inversa) che puoi usare. Il nome della funzione viene costruito con le lettere minuscole del modello in cui la ForeignKey
è stata dichiarata, seguita da _set
(ovvero la funzione creata in Book
è bookinstance_set()
).
Nota: Qui usiamo all()
per ottenere tutti i record (di default). Anche se puoi usare il metodo filter()
per ricevere un sottoinsieme di record nel tuo codice, non puoi farlo direttamente nei template perchè non puoi specificare argomenti nelle funzioni.
Fai attenzione anche a non definire un ordine (sulla tua vista class-based o model), altrimenti vedrai anche degli errori dal server di sviluppo come questo:
[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 /foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> allow_empty_first_page=allow_empty_first_page, **kwargs)
Ciò si verifica perché paginator object si aspetta di vedere alcuni ORDER BY eseguiti sul database sottostante. Senza di esso, non può essere sicuro che i record siano restituiti effettivamente nell'ordine corretto!
In questo tutorial non abbiamo ancora visto Pagination (ancora, ma presto), ma poichè non puoi utilizzare sort_by()
e passare un parametro, (stessa cosa con filter()
) dovrai scegliere tra tre strade:
- Aggiungere un
ordering
dentro una dichiarazioneclass Meta
nel tuo modello. - Aggiungere un attibuto
queryset
nella tua view custom class-based, specificando unorder_by()
. - Aggiungere un metodo
get_queryset
alla tua view custom class-based e specificando unorder_by()
.
Se decidi di usare una classe Meta per il model Author (probabilmente non così flessibile come personalizzare la vista basata sulla classe, ma abbastanza facile), ti ritroverai con qualcosa di simile a questo
class Author(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) def get_absolute_url(self): return reverse('author-detail', args=[str(self.id)]) def __str__(self): return f'{self.last_name}, {self.first_name}' class Meta: ordering = ['last_name']
Ovviamente, il campo non necessita di essere un last_name
: può essere qualunque altro.
E per ultimo, ma non meno importante, dovresti ordinare un attributo/colonna che abbia effettivamente un indice (unico o meno) sul tuo database per evitare problemi di prestazioni. Ovviamente, questo non sarà necessario qui, con così pochi libri (e utenti!), ma è qualcosa da tenere a mente per i progetti futuri.
Come viene visualizzato?
A questo punto, avremmo dovuto creare tutto il necessario per visualizzare sia l'elenco dei libri sia le pagine di dettaglio dei libri. Lancia il comando (python3 manage.py runserver
) ed apri sul tuo browser http://127.0.0.1:8000/.
Warning: Non fare ancora clic su nessun link di autore o di dettaglio dell'autore: creerai quelli nella sfida!
Click su All books per vedere la lista di tutti i libri.
Quindi fai clic su un link a uno dei tuoi libri. Se tutto è impostato correttamente, dovresti vedere qualcosa come il seguente screenshot.
Impaginazione
Se hai appena qualche record, la nostra pagina di elenco dei libri sembrerà a posto. Tuttavia, inserendo decine o centinaia di record la pagina impiegherà più tempo a caricarsi (e avrà troppi contenuti per navigare in modo ragionevole). La soluzione a questo problema è di aggiungere l'impaginazione alle visualizzazioni della lista, riducendo il numero di elementi visualizzati su ciascuna pagina.
Django ha un eccellente supporto per l'impaginazione built-in. Ancora meglio, questo è incorporato nelle view lista generiche basate su classe, quindi non devi fare molto per abilitarlo!
Views
Apri catalog/views.py, ed aggiungi la riga di codice paginate_by
mostrata sotto.
class BookListView(generic.ListView):
model = Book
paginate_by = 10
Con questa aggiunta, non appena si hanno più di 10 record, la vista inizierà a impaginare i dati che invia al modello. Si accede alle diverse pagine usando i parametri GET - per accedere alla pagina 2 si utilizzerà l'URL: /catalog/books/?page=2
.
Templates
Ora che i dati sono impaginati, è necessario aggiungere il supporto al template per scorrere il set di risultati. Poiché potremmo volerlo fare in tutte le view elenco, lo faremo in un modo che possa essere aggiunto al template base.
Apri /locallibrary/catalog/templates/base_generic.html e copiaci dentro il seguente blocco di paginazione (evidenziato in grassetto qui in basso) sotto il nostro block content. Il codice controlla innanzitutto se l'impaginazione è abilitata nella pagina corrente. In tal caso, aggiunge i collegamenti successivo e precedente se appropriato (e il numero di pagina corrente).
{% block content %}{% endblock %}
{% block pagination %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
<p>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</p>
</span>
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
{% endblock %}
Il page_obj
è un oggetto Paginator che esisterà se la paginazione viene utilizzata nella pagina corrente. Ti permette di ottenere tutte le informazioni sulla pagina corrente, le pagine precedenti, quante pagine ci sono, ecc.
Usiamo {{ request.path }}
per ottenere l'URL della pagina corrente per la creazione dei collegamenti di paginazione. Questo è utile perché è indipendente dall'oggetto che stiamo impaginando.
Ecco fatto!
Come viene visualizzato?
Lo screenshot qui sotto mostra l'aspetto della paginazione: se non hai inserito più di 10 titoli nel tuo database, puoi testarlo più facilmente abbassando il numero specificato in paginate_by.
I link di impaginazione sono visualizzati in basso, con i link successivo / precedente visualizzati a seconda della pagina in cui ti trovi.
Sfida te stesso
La sfida in questo articolo è di creare le view di dettaglio e le view di elenco dell'autore richieste per completare il progetto. Questi dovrebbero essere resi disponibili ai seguenti URL:
catalog/authors/
— lista di tutti gli authors.catalog/author/<id>
— Vista di dettaglio dell'autore con chiave primaria<id>
Il codice richiesto per i mappatori di URL e le viste dovrebbe essere praticamente identico all'elenco di libri e alle viste di dettaglio che abbiamo creato sopra. I modelli saranno diversi ma condivideranno un comportamento simile.
Note:
- Una volta creato il mapper URL per la pagina di elenco dell'autore, sarà necessario aggiornare il collegamento Tutti gli autori nel modello di base. Segui lo stesso processo che abbiamo fatto quando abbiamo aggiornato il link Tutti i libri.
- Una volta creato il mapper URL per la pagina dei dettagli dell'autore, è necessario aggiornare anche il template della vista dettagliata dei libri (/locallibrary/catalog/templates/catalog/book_detail.html) in modo che il link dell'autore punti alla nuova pagina dei dettagli dell'autore (anziché essere un URL vuoto). La linea cambierà per aggiungere il tag template mostrato in grassetto sotto.
<p><strong>Author:</strong> <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a></p>
Quando hai finito, le tue pagine dovrebbero apparire come gli screenshot qui sotto.
Sommario
Congratulazioni, la nostra funzionalità di libreria di base è ora completa!
In questo articolo, abbiamo imparato come utilizzare la lista generica basata sulla classe e le viste di dettaglio e li abbiamo usati per creare pagine per visualizzare i nostri libri e autori. Lungo la strada abbiamo imparato a conoscere la corrispondenza dei modelli con le espressioni regolari e come puoi passare i dati dagli URL alle tue visualizzazioni. Abbiamo anche imparato qualche altro trucco per l'utilizzo dei modelli. Infine, abbiamo mostrato come impaginare le visualizzazioni degli elenchi in modo che le nostre liste siano gestibili anche quando abbiamo molti record.
Nei nostri prossimi articoli, estenderemo questa libreria per supportare gli account utente, dimostrando in tal modo l'autenticazione dell'utente, permissons, sessioni e moduli.
Vedi anche
- Built-in class-based generic views (Django docs)
- Generic display views (Django docs)
- Introduction to class-based views (Django docs)
- Built-in template tags and filters (Django docs).
- Pagination (Django docs)
In questo modulo
- Django introduction
- Setting up a Django development environment
- Django Tutorial: The Local Library website
- Django Tutorial Part 2: Creating a skeleton website
- Django Tutorial Part 3: Using models
- Django Tutorial Part 4: Django admin site
- Django Tutorial Part 5: Creating our home page
- Django Tutorial Part 6: Generic list and detail views
- Django Tutorial Part 7: Sessions framework
- Django Tutorial Part 8: User authentication and permissions
- Django Tutorial Part 9: Working with forms
- Django Tutorial Part 10: Testing a Django web application
- Django Tutorial Part 11: Deploying Django to production
- Django web application security
- DIY Django mini blog