Questo tutorial estenderà il nostro sito LocalLibrary, aggiungendo elenchi 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 vista. Per queste pagine, mostreremo un tipo di visualizzazione completamente diverso: elenco di classi e viste di dettaglio generico. Questi possono ridurre in modo significativo la quantità di codice di visualizzazione necessaria, semplificandone la scrittura e la manutenzione.

La parte finale del tutorial mostrerà come impaginare i dati quando si utilizzano visualizzazioni di elenchi generici basati su classi.

 

Pagina lista di libri

Nella pagina dell'elenco dei libri verrà visualizzato un elenco di tutti i record di libri disponibili nella pagina, 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 modello 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, all'URL deve già essere stato abbinato /catalog, quindi la view sarà richiamata per l'URL: /catalog/books/.

La funzione di visualizzazione 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.

Per le viste basate su classi di Django, si accede a una funzione di visualizzazione appropriata chiamando il metodo della classe as_view(). Questo fa tutto il lavoro necessario per creare un'istanza della classe e assicurarsi che i metodi del giusto handler siano chiamati per le richieste HTTP in arrivo..

Viste (class-based)

Potremmo facilmente scrivere la vista 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 modello specificato. Invece, utilizzeremo una vista 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 modello di libro (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_listbook_list (esempio: genericamente "the_model_name_list").

Note: Questo percorso scomodo per la posizione del modello non è un errore di stampa: le viste 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 modello se è necessario disporre di più viste che utilizzano questo stesso modello oppure si potrebbe voler utilizzare un nome di variabile diverso per il modello se book_list non è intuitivo per il proprio caso d'uso specifico del modello. 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 methodi 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'attrivbuto 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).

Note: 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 templates per le viste generiche sono come qualsiasi altro template (anche se ovviamente il contesto / le informazioni passate al template possono differire). Come con il nostro template di index, estendiamo il nostro template di base nella prima riga e quindi 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 vista passa il contesto (elenco di libri) per impostazione predefinita con alias object_list e book_list; 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 testa 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/ifnotequalifchanged 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 book nel template con le informazioni per l'item 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.titlebook.author), dove il testo che segue book è il nome del campo (come definito nel modello).

Possiamo anche chiamare delle functions nel modello 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!)

Note: Dobbiamo stare attenti agli "effetti collaterali" quando chiamiamo le funzioni nei modelli. 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 modello Book (autore, sommario, ISBN, lingua e genere), elencheremo anche i dettagli delle copie disponibili (BookInstances) compreso lo stato, la data di ritorno prevista, la stampa 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 modello, 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 percorso 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 conversionen 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 libro.

Note: 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.

Avanzamento del percorso avanzato/primer di espressione regolare

Note: 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 percorso 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 devono essere dichiarate usando la sintassi "raw string" letterale (ad esempio, sono incluse come mostrato: r'<testo della regex>').

Le parti principali della sintassi che devi conoscere per dichiarare le corrispondenze del pattern sono:

Symbol Meaning
^ Abbina l'inizio del testo
$ Abbina 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 (_)
+ Abbina uno o più caratteri precedenti. Ad esempio, per abbinare 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 book/ all'inizio della linea (^book/), dopo ha una o più cifre (\d+), e quindi termina (senza caratteri non numerici prima dell'indicatore di fine riga).

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, book/1234 invierà una variabile pk = '1234' alla vista.

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 book/ all'inizio della linea (^book/), quindi ha uno o più caratteri che sono o un carattere '-' o una parola ([-\w]+), e si conclude. Cattura anche questo set di caratteri e li passa alla vista in un parametro chiamato 'stub'.

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 /catalog/book/the-secret-garden anzichè /catalog/book/33.

È possibile acquisire più pattern nella stessa corrispondenza e quindi codificare molte informazioni diverse in un URL.

Note: 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 ulteriori opzioni nelle tue mappe URL

Una caratteristica che non abbiamo usato qui, ma che potresti trovare di valore, è che puoi dichiarare e passare alla vista opzioni aggiuntive. Le opzioni sono dichiarate come dizionario che si passa come terzo argomento non assegnato alla funzione path (). Questo approccio può essere utile se si desidera utilizzare la stessa vista per più risorse e passare i dati per configurarne il comportamento in ciascun caso (di seguito forniamo un modello 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'),

Note: Entrambe le opzioni extra e i pattern nominati catturati vengono passati alla vista 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à eliminato).

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 modello denominata object O book (cioè genericamente "the_model_name").

Se necessario, è possibile modificare il modello utilizzato e il nome dell'oggetto contesto utilizzato per fare riferimento al libro nel modello. È inoltre possibile sovrascrivere i metodi per, ad esempio, aggiungere ulteriori informazioni al contesto.

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 modello sopra ha un URL vuoto perché non abbiamo ancora creato una pagina dei dettagli dell'autore. Una volta che esiste, 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 modello è stato descritto in precedenza:

  • Estendiamo il nostro modello di base e sostituiamo il blocco "contenuto".
  • 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 contesto usando la notazione dot (perché abbiamo usato la vista generica di dettaglio, il contesto è denominato book, potremmo anche usare "object")

Prima non abbiamo visto la funzione interessante book.bookinstance_set.all(). Questo metodo viene 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 altri campi da prendere dal 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 (es. la funzione creata in Book è bookinstance_set()).

Note: Qui usiamo all() per prendere 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 o modello basato sulla classe), 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 restituiti siano 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:

  1. Aggiungere un ordering dentro una dichiarazione class Meta nel tuo modello.
  2. Aggiungere un attibuto queryset  nella tua view custom class-based, specificando un order_by().
  3. Aggiungere un metodo get_queryset alla tua view  custom class-based e specificando un order_by().

Se decidi di usare una classe Meta per il modello 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. 

Book List Page

Quindi fai clic su un link a uno dei tuoi libri. Se tutto è impostato correttamente, dovresti vedere qualcosa come il seguente screenshot.

Book Detail Page

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 generiche visualizzazioni elenco basate sulla 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 modello per scorrere il set di risultati. Poiché potremmo volerlo fare in tutte le visualizzazioni elenco, lo faremo in un modo che può essere aggiunto al modello base.

Apri /locallibrary/catalog/templates/base_generic.html e copia nel seguente blocco di pagine sotto il nostro blocco di contenuti (evidenziato in grassetto in basso). Il codice controlla innanzitutto se l'impaginazione è abilitata nella pagina corrente. In tal caso, aggiunge i collegamenti successivi e precedenti in base alle esigenze (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 %} 

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 successivi / precedenti visualizzati a seconda della pagina in cui ti trovi.

Book List Page - paginated

Prova tu

La sfida in questo articolo è di creare il dettaglio dell'autore e le visualizzazioni di elenco 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.

Author List Page

Author Detail Page

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

 

In questo modulo

 

Tag del documento e collaboratori

Hanno collaborato alla realizzazione di questa pagina: mdnwebdocs-bot, mattiatoselli
Ultima modifica di: mdnwebdocs-bot,