diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
commit | 218934fa2ed1c702a6d3923d2aa2cc6b43c48684 (patch) | |
tree | a9ef8ac1e1b8fe4207b6d64d3841bfb8990b6fd0 /files/zh-tw/learn/server-side/django/generic_views | |
parent | 074785cea106179cb3305637055ab0a009ca74f2 (diff) | |
download | translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.gz translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.bz2 translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.zip |
initial commit
Diffstat (limited to 'files/zh-tw/learn/server-side/django/generic_views')
-rw-r--r-- | files/zh-tw/learn/server-side/django/generic_views/index.html | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/files/zh-tw/learn/server-side/django/generic_views/index.html b/files/zh-tw/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..240354cd6b --- /dev/null +++ b/files/zh-tw/learn/server-side/django/generic_views/index.html @@ -0,0 +1,612 @@ +--- +title: 'Django Tutorial Part 6: Generic list and detail views' +slug: Learn/Server-side/Django/Generic_views +translation_of: Learn/Server-side/Django/Generic_views +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">本教程擴充了 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,為書本與作者增加列表與細節頁面。此處我們將學到通用類別視圖,並演示如何降低你必須為一般使用案例撰寫的程式碼數量。我們也會更加深入 URL 處理細節,演示如何實施基本模式匹配。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>To understand where and how to use generic class-based views, and how to extract patterns from URLs and pass the information to views.</td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>本教程中,通過為書本和作者添加列表和詳細信息頁面,我們將完成第一個版本的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站(或者更準確地說,我們將向您展示如何實現書頁,並讓您自己創建作者頁面!) )</p> + +<p>該過程在創建索引頁面,我們在上一個教程中展示了該頁面。我們仍然需要創建URL地圖,視圖和模板。主要區別在於,對於詳細信息頁面,我們還有一個額外的挑戰,即從URL對於這些頁面,我們將演示一種完全不同的視圖類型:基於類別的通用列表和詳細視圖。這些可以顯著減少所需的視圖代碼量,有助於更容易編寫和維護。</p> + +<p>本教程的最後一部分,將演示在使用基於類別的通用列表視圖時,如何對數據進行分頁。</p> + +<h2 id="Book_list_page">Book list page</h2> + +<p>該書將顯示每條記錄的標題和作者,標題是指向相關圖書詳細信息頁面的超鏈接。該頁面將具有與站點中,所有其他頁面相同的結構和導航,因此,我們可以擴展在上一個教程中創建的基本模板 (<strong>base_generic.html</strong>)。</p> + +<h3 id="URL_mapping">URL mapping</h3> + +<p>開啟/catalog/urls.py,並複製加入下面粗體顯示的代碼。就像索引頁面的方式,這個path()函數,定義了一個與URL匹配的模式('books /'),如果URL匹配,將調用視圖函數(views.BookListView.as_view())和一個對應的特定映射的名稱。</p> + +<pre class="brush: python notranslate">urlpatterns = [ + path('', views.index, name='index'), +<strong> </strong>path<strong>('books/', views.BookListView.as_view(), name='books'),</strong> +]</pre> + +<p>正如前一個教程中所討論的,URL必須已經先匹配了/ catalog,因此實際上將為URL調用的視圖是:/ catalog / books /。</p> + +<p>我們將繼承現有的泛型視圖函數,該函數已經完成了我們希望此視圖函數執行的大部分工作,而不是從頭開始編寫自己的函數。對於基於Django類的視圖,我們通過調用類方法as_view(),來訪問適當的視圖函數。由此可以創建類的實例,並確保為HTTP請求正確的處理程序方法。</p> + +<h3 id="View_class-based">View (class-based)</h3> + +<p>我們可以很容易地,將書本列表列表編寫為常規函數(就像我們之前的索引視圖一樣),進入查詢數據庫中的所有書本,然後調用render(),將列表傳遞給指定的模板。然而,我們用另一種方法取代,我們將使用基於類的通用列表視圖(ListView)-一個繼承自現有視圖的類。因為通用視圖,已經實現了我們需要的大部分功能,並且遵循Django最佳實踐,我們將能夠創建更強大的列表視圖,代碼更多,重複次數最多,最終維護所需。</p> + +<p>開啟catalog / views.py,將以下代碼複製到文件的底部:</p> + +<pre class="brush: python notranslate">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>就是這樣!通用view將查詢數據庫,以獲取指定模型(Book)的所有記錄,然後呈現/locallibrary/catalog/templates/catalog/book_list.html的模板(我們將在下面創建)。在模板中,您可以使用所謂的object_list或book_list的模板變量(即通常為“ the_model_name_list”),以訪問書本列表。</p> + +<div class="note"> +<p><strong>Note</strong>: This awkward path for the template location isn't a misprint — the generic views look for templates in <code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code> (<code>catalog/book_list.html</code> in this case) inside the application's <code>/<em>application_name</em>/templates/</code> directory (<code>/catalog/templates/)</code>.</p> +</div> + +<p>您可以添加屬性,以更改上面的某種行為。例如,如果需要使用同一模型的多個視圖,則可以指定另一個模板文件,或者如果book_list對於特定模板用例不直觀,則可能需要使用不同的模板變量名稱。可能最有用的變更,是更改/過濾返回的結果子集-因此,您可能會列出其他用戶閱讀的前5本書,而不是列出所有書本。</p> + +<pre class="brush: python notranslate">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</pre> + +<h4 id="Overriding_methods_in_class-based_views">Overriding methods in class-based views</h4> + +<p>雖然我們不需要在這裡執行此操作,但您也可以覆寫某些類別方法。</p> + +<p>例如,我們可以覆寫get_queryset()方法,來更改返回的記錄列表。這比單獨設置queryset屬性更靈活,就像我們在前面的代碼片段中進行的那樣(儘管在這案例中沒有太大用處):</p> + +<pre class="brush: python notranslate">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 +</pre> + +<p>我們還可以重寫<code>get_context_data()</code> 以便將其他上下文變數傳遞給模組 (例如,默認情況下傳遞書籍列表). 下面的片段顯示瞭如何向上下文添加名為"<code>some_data</code>" 的變數(然後它將用作模組變數)</p> + +<pre class="brush: python notranslate">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</pre> + +<p>執行此操作時,務必遵循上面使用的模式:</p> + +<ul> + <li>首先從我們的superclass中獲取現有內文。</li> + <li>然後添加新的內文信息。</li> + <li>然後返回新的(更新後)內文。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Check out <a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs) for many more examples of what you can do.</p> +</div> + +<h3 id="Creating_the_List_View_template">Creating the List View template</h3> + +<p>建立HTML及複製以下文字串到<strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> , 這是基於通用類的列表視圖所期望的默認模板文件 (默認在<code>catalog中名稱為Book</code> 的模組).</p> + +<p>通用的views模板跟其他的模板沒有不同 (儘管傳遞給模板的內文/訊息當然可以不同). 與index模板一樣,我們在第一行中擴展了基本模板,然後更替名為 <code>content</code>的區塊。</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + <strong>{% if book_list %}</strong> + <ul> + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>該視圖默認將上下文(書籍列表)作為<code>object_list</code> 和 <code>book_list</code> 別名傳遞;兩者都會起作用.</p> + +<h4 id="Conditional_execution">Conditional execution</h4> + +<p>我們使用 <code><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a></code>, <code>else</code> 和 <code>endif</code> 模組標籤,以檢查<code>book_list</code> 是否已定義並且不為空。 如果 <code>book_list</code> 為空值, 則 <code>else</code> 子句回傳text 說明沒有書可以列出. 如果<code>book_list</code>不是空值, 然後我們遍曆書籍清單。</p> + +<pre class="brush: html notranslate"><strong>{% if book_list %}</strong> + <!-- code here to list the books --> +<strong>{% else %}</strong> + <p>There are no books in the library.</p> +<strong>{% endif %}</strong> +</pre> + +<p>The condition above only checks for one case, but you can test on additional conditions using the <code>elif</code> template tag (e.g. <code>{% elif var2 %}</code> ). For more information about conditional operators see: <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a>, and <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifchanged">ifchanged</a> in <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django Docs).</p> + +<h4 id="For_loops">For loops</h4> + +<p>The template uses the <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for">for</a> and <code>endfor</code> template tags to loop through the book list, as shown below. Each iteration populates the <code>book</code> template variable with information for the current list item.</p> + +<pre class="brush: html notranslate">{% for <strong>book</strong> in book_list %} + <li> <!-- code here get information from each <strong>book</strong> item --> </li> +{% endfor %} +</pre> + +<p>While not used here, within the loop Django will also create other variables that you can use to track the iteration. For example, you can test the <code>forloop.last</code> variable to perform conditional processing the last time that the loop is run.</p> + +<h4 id="Accessing_variables">Accessing variables</h4> + +<p>The code inside the loop creates a list item for each book that shows both the title (as a link to the yet-to-be-created detail view) and the author.</p> + +<pre class="brush: html notranslate"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>We access the <em>fields</em> of the associated book record using the "dot notation" (e.g. <code>book.title</code> and <code>book.author</code>), where the text following the <code>book</code> item is the field name (as defined in the model).</p> + +<p>We can also call <em>functions</em> in the model from within our template — in this case we call <code>Book.get_absolute_url()</code> to get an URL you could use to display the associated detail record. This works provided the function does not have any arguments (there is no way to pass arguments!)</p> + +<div class="note"> +<p><strong>Note</strong>: We have to be a little careful of "side effects" when calling functions in templates. Here we just get a URL to display, but a function can do pretty much anything — we wouldn't want to delete our database (for example) just by rendering our template!</p> +</div> + +<h4 id="Update_the_base_template">Update the base template</h4> + +<p>Open the base template (<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>) and insert <strong>{% url 'books' %} </strong>into the URL link for <strong>All books</strong>, as shown below. This will enable the link in all pages (we can successfully put this in place now that we've created the "books" url mapper).</p> + +<pre class="brush: python notranslate"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>You won't be able to build book list yet, because we're still missing a dependency — the URL map for the book detail pages, which is needed to create hyperlinks to individual books. We'll show both list and detail views after the next section.</p> + +<h2 id="Book_detail_page">Book detail page</h2> + +<p>The book detail page will display information about a specific book, accessed using the URL <code>catalog/book/<em><id></em></code> (where <code><em><id></em></code> is the primary key for the book). In addition to fields in the <code>Book</code> model (author, summary, ISBN, language, and genre), we'll also list the details of the available copies (<code>BookInstances</code>) including the status, expected return date, imprint, and id. This will allow our readers not just to learn about the book, but also to confirm whether/when it is available.</p> + +<h3 id="URL_mapping_2">URL mapping</h3> + +<p>Open <strong>/catalog/urls.py</strong> and add the '<strong>book-detail</strong>' URL mapper shown in bold below. This <code>path()</code> function defines a pattern, associated generic class-based detail view, and a name.</p> + +<pre class="brush: python notranslate">urlpatterns = [ + path('', views.index, name='index'), + path('books/', views.BookListView.as_view(), name='books'), +<strong> path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p>For the <em>book-detail</em> path the URL pattern uses a special syntax to capture the specific id of the book that we want to see. The syntax is very simple: angle brackets define the part of the URL to be captured, enclosing the name of the variable that the view can use to access the captured data. For example, <strong><something></strong> , will capture the marked pattern and pass the value to the view as a variable "something". You can optionally precede the variable name with a <a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters">converter specification</a> that defines the type of data (int, str, slug, uuid, path).</p> + +<p>In this case we use <code>'<int:pk>'</code><strong> </strong> to capture the book id, which must be an integer, and pass it to the view as a parameter named <code>pk</code> (short for primary key).</p> + +<div class="note"> +<p><strong>Note</strong>: As discussed previously, our matched URL is actually <code>catalog/book/<digits></code> (because we are in the <strong>catalog</strong> application, <code>/catalog/</code> is assumed).</p> +</div> + +<div class="warning"> +<p><strong>Important</strong>: The generic class-based detail view <em>expects</em> to be passed a parameter named <strong>pk</strong>. If you're writing your own function view you can use whatever parameter name you like, or indeed pass the information in an unnamed argument.</p> +</div> + +<h4 id="Advanced_path_matchingregular_expression_primer">Advanced path matching/regular expression primer</h4> + +<div class="note"> +<p><strong>Note</strong>: You won't need this section to complete the tutorial! We provide it because knowing this option is likely to be useful in your Django-centric future.</p> +</div> + +<p>The pattern matching provided by <code>path()</code> is simple and useful for the (very common) cases where you just want to capture <em>any</em> string or integer. If you need more refined filtering (for example, to filter only strings that have a certain number of characters) then you can use the <a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path">re_path()</a> method.</p> + +<p>This method is used just like <code>path()</code> except that it allows you to specify a pattern using a <a href="https://docs.python.org/3/library/re.html">Regular expression</a>. For example, the previous path could have been written as shown below:</p> + +<pre class="brush: python notranslate"><strong>re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +</pre> + +<p><em>Regular expressions</em> are an incredibly powerful pattern mapping tool. They are, frankly, quite unintuitive and scary for beginners. Below is a very short primer!</p> + +<p>The first thing to know is that regular expressions should usually be declared using the raw string literal syntax (i.e. they are enclosed as shown: <strong>r'<your regular expression text goes here>'</strong>).</p> + +<p>The main parts of the syntax you will need to know for declaring the pattern matches are:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Symbol</th> + <th scope="col">Meaning</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>Match the beginning of the text</td> + </tr> + <tr> + <td>$</td> + <td>Match the end of the text</td> + </tr> + <tr> + <td>\d</td> + <td>Match a digit (0, 1, 2, ... 9)</td> + </tr> + <tr> + <td>\w</td> + <td>Match a word character, e.g. any upper- or lower-case character in the alphabet, digit or the underscore character (_)</td> + </tr> + <tr> + <td>+</td> + <td>Match one or more of the preceding character. For example, to match one or more digits you would use <code>\d+</code>. To match one or more "a" characters, you could use <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>Match zero or more of the preceding character. For example, to match nothing or a word you could use <code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>Capture the part of the pattern inside the brackets. Any captured values will be passed to the view as unnamed parameters (if multiple patterns are captured, the associated parameters will be supplied in the order that the captures were declared).</td> + </tr> + <tr> + <td>(?P<<em>name</em>>...)</td> + <td>Capture the pattern (indicated by ...) as a named variable (in this case "name"). The captured values are passed to the view with the name specified. Your view must therefore declare an argument with the same name!</td> + </tr> + <tr> + <td>[ ]</td> + <td>Match against one character in the set. For example, [abc] will match on 'a' or 'b' or 'c'. [-\w] will match on the '-' character or any word character.</td> + </tr> + </tbody> +</table> + +<p>Most other characters can be taken literally!</p> + +<p>Lets consider a few real examples of patterns:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Pattern</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>This is the RE used in our url mapper. It matches a string that has <code>book/</code> at the start of the line (<strong>^book/</strong>), then has one or more digits (<code>\d+</code>), and then ends (with no non-digit characters before the end of line marker).</p> + + <p>It also captures all the digits <strong>(?P<pk>\d+)</strong> and passes them to the view in a parameter named 'pk'. <strong>The captured values are always passed as a string!</strong></p> + + <p>For example, this would match <code>book/1234</code> , and send a variable <code>pk='1234'</code> to the view.</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>This matches the same URLs as the preceding case. The captured information would be sent as an unnamed argument to the view.</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>This matches a string that has <code>book/</code> at the start of the line (<strong>^book/</strong>), then has one or more characters that are <em>either</em> a '-' or a word character (<strong>[-\w]+</strong>), and then ends. It also captures this set of characters and passes them to the view in a parameter named 'stub'.</p> + + <p>This is a fairly typical pattern for a "stub". Stubs are URL-friendly word-based primary keys for data. You might use a stub if you wanted your book URL to be more informative. For example <code>/catalog/book/the-secret-garden</code> rather than <code>/catalog/book/33</code>.</p> + </td> + </tr> + </tbody> +</table> + +<p>You can capture multiple patterns in the one match, and hence encode lots of different information in a URL.</p> + +<div class="note"> +<p><strong>Note</strong>: As a challenge, consider how you might encode an url to list all books released in a particular year, month, day, and the RE that could be used to match it.</p> +</div> + +<h4 id="Passing_additional_options_in_your_URL_maps">Passing additional options in your URL maps</h4> + +<p>One feature that we haven't used here, but which you may find valuable, is that you can declare and pass <a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#views-extra-options">additional options</a> to the view. The options are declared as a dictionary that you pass as the third un-named argument to the <code>path()</code> function. This approach can be useful if you want to use the same view for multiple resources, and pass data to configure its behaviour in each case (below we supply a different template in each case).</p> + +<pre class="brush: python notranslate">path('url/', views.my_reused_view, <strong>{'my_template_name': 'some_path'}</strong>, name='aurl'), +path('anotherurl/', views.my_reused_view, <strong>{'my_template_name': 'another_path'}</strong>, name='anotherurl'), +</pre> + +<div class="note"> +<p><strong>Note:</strong> Both extra options and named captured patterns are passed to the view as <em>named</em> arguments. If you use the <strong>same name</strong> for both a captured pattern and an extra option then only the captured pattern value will be sent to the view (the value specified in the additional option will be dropped). </p> +</div> + +<h3 id="View_class-based_2">View (class-based)</h3> + +<p>Open <strong>catalog/views.py</strong>, and copy the following code into the bottom of the file:</p> + +<pre class="brush: python notranslate">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>That's it! All you need to do now is create a template called <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>, and the view will pass it the database information for the specific <code>Book</code> record extracted by the URL mapper. Within the template you can access the list of books with the template variable named <code>object</code> OR <code>book</code> (i.e. generically "<code><em>the_model_name</em></code>").</p> + +<p>If you need to, you can change the template used and the name of the context object used to reference the book in the template. You can also override methods to, for example, add additional information to the context.</p> + +<h4 id="What_happens_if_the_record_doesnt_exist">What happens if the record doesn't exist?</h4> + +<p>If a requested record does not exist then the generic class-based detail view will raise an <code>Http404</code> exception for you automatically — in production this will automatically display an appropriate "resource not found" page, which you can customise if desired.</p> + +<p>Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-based view as a function, if you were <strong>not</strong> using the generic class-based detail view.</p> + +<pre class="brush: python notranslate">def book_detail_view(request, primary_key): + try: + book = Book.objects.get(pk=primary_key) + except Book.DoesNotExist: + raise Http404('Book does not exist') + + # from django.shortcuts import get_object_or_404 + # book = get_object_or_404(Book, pk=primary_key) + + return render(request, 'catalog/book_detail.html', context={'book': book}) +</pre> + +<p>The view first tries to get the specific book record from the model. If this fails the view should raise an <code>Http404</code> exception to indicate that the book is "not found". The final step is then, as usual, to call <code>render()</code> with the template name and the book data in the <code>context</code> parameter (as a dictionary).</p> + +<div class="note"> +<p><strong>Note</strong>: The <code>get_object_or_404()</code> (shown commented out above) is a convenient shortcut to raise an <code>Http404</code> exception if the record is not found.</p> +</div> + +<h3 id="Creating_the_Detail_View_template">Creating the Detail View template</h3> + +<p>Create the HTML file <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong> and give it the below content. As discussed above, this is the default template file name expected by the generic class-based <em>detail</em> view (for a model named <code>Book</code> in an application named <code>catalog</code>).</p> + +<pre class="brush: html notranslate">{% 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 %}</pre> + +<ul> +</ul> + +<div class="note"> +<p>The author link in the template above has an empty URL because we've not yet created an author detail page. Once that exists, you should update the URL like this:</p> + +<pre class="notranslate"><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>Though a little larger, almost everything in this template has been described previously:</p> + +<ul> + <li>We extend our base template and override the "content" block.</li> + <li>We use conditional processing to determine whether or not to display specific content.</li> + <li>We use <code>for</code> loops to loop through lists of objects.</li> + <li>We access the context fields using the dot notation (because we've used the detail generic view, the context is named <code>book</code>; we could also use "<code>object</code>")</li> +</ul> + +<p>The one interesting thing we haven't seen before is the function <code>book.bookinstance_set.all()</code>. This method is "automagically" constructed by Django in order to return the set of <code>BookInstance</code> records associated with a particular <code>Book</code>.</p> + +<pre class="brush: python notranslate">{% for copy in book.bookinstance_set.all %} +<!-- code to iterate across each copy/instance of a book --> +{% endfor %}</pre> + +<p>需要這方法是因為我們僅在“一”那側model(Book)定義一個<code>ForeignKey</code> (一對多)字段的關聯,也因為沒有任何的關聯被定義在“多”那側model(BookInstance),故無法透過字段來取得相關的紀錄。為了克服這個問題,Django建立一個function取名為“reverse lookup”供使用。function的名字以一對多關係中該 <code>ForeignKey</code> 被定義在的那個模型名稱小寫,再在字尾加上<code>_set</code>(因此在 <code>Book</code> 創建的function名是 <code>bookinstance_set()</code>)。</p> + +<div class="note"> +<p><strong>Note</strong>: 在這我們使用 <code>all()</code> 取得所有紀錄 (預設),你無法直接在template做是因為你無法指定引數到function,但你可用 <code>filter()</code> 方法取得一個紀錄的子集 。</p> + +<p>順帶一提,若你不再基於類的view或model定義順序(order),開發伺服器會將會報錯類似的訊息:</p> + +<pre class="notranslate">[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) +</pre> + +<p>That happens because the <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">paginator object</a> expects to see some ORDER BY being executed on your underlying database. Without it, it can't be sure the records being returned are actually in the right order!<strong> </strong></p> + +<p>This tutorial didn't reach <strong>Pagination</strong> (yet, but soon enough), but since you can't use <code>sort_by()</code> and pass a parameter (the same with <code>filter()</code> described above) you will have to choose between three choices:</p> + +<ol> + <li>Add a <code>ordering</code> inside a <code>class Meta</code> declaration on your model.</li> + <li>Add a <code>queryset</code> attribute in your custom class-based view, specifying a <code>order_by()</code>.</li> + <li>Adding a <code>get_queryset</code> method to your custom class-based view and also specify the <code>order_by()</code>.</li> +</ol> + +<p>If you decide to go with a <code>class Meta</code> for the <code>Author</code> model (probably not as flexible as customizing the class-based view, but easy enough), you will end up with something like this:</p> + +<pre class="notranslate">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}' + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>Of course, the field doesn't need to be <code>last_name</code>: it could be any other.</p> + +<p>And last, but not least, you should sort by an attribute/column that actually has a index (unique or not) on your database to avoid performance issues. Of course, this will not be necessary here (and we are probably getting ourselves too much ahead) if such small amount of books (and users!), but it is something to keep in mind for future projects.</p> +</div> + +<h2 id="What_does_it_look_like_2">What does it look like?</h2> + +<p>At this point we should have created everything needed to display both the book list and book detail pages. Run the server (<code>python3 manage.py runserver</code>) and open your browser to <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</p> + +<div class="warning"> +<p><strong>Warning:</strong> Don't click any author or author detail links yet — you'll create those in the challenge!</p> +</div> + +<p>Click the <strong>All books</strong> link to display the list of books. </p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>Then click a link to one of your books. If everything is set up correctly, you should see something like the following screenshot.</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="Pagination">Pagination</h2> + +<p>If you've just got a few records, our book list page will look fine. However, as you get into the tens or hundreds of records the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this problem is to add pagination to your list views, reducing the number of items displayed on each page. </p> + +<p>Django has excellent in-built support for pagination. Even better, this is built into the generic class-based list views so you don't have to do very much to enable it!</p> + +<h3 id="Views">Views</h3> + +<p>Open <strong>catalog/views.py</strong>, and add the <code>paginate_by</code> line shown in bold below.</p> + +<pre class="brush: python notranslate">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the template. The different pages are accessed using GET parameters — to access page 2 you would use the URL: <code>/catalog/books/<strong>?page=2</strong></code>.</p> + +<h3 id="Templates">Templates</h3> + +<p>Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might want to do this in all list views, we'll do this in a way that can be added to the base template. </p> + +<p>Open <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> and copy in the following pagination block below our content block (highlighted below in bold). The code first checks if pagination is enabled on the current page. If so then it adds next and previous links as appropriate (and the current page number). </p> + +<pre class="brush: python notranslate">{% block content %}{% endblock %} + +<strong>{% 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 %} </strong></pre> + +<p>The <code>page_obj</code> is a <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">Paginator</a> object that will exist if pagination is being used on the current page. It allows you to get all the information about the current page, previous pages, how many pages there are, etc. </p> + +<p>We use <code>\{{ request.path }}</code> to get the current page URL for creating the pagination links. This is useful, because it is independent of the object that we're paginating.</p> + +<p>Thats it!</p> + +<h3 id="What_does_it_look_like_3">What does it look like?</h3> + +<p>The screenshot below shows what the pagination looks like — if you haven't entered more than 10 titles into your database, then you can test it more easily by lowering the number specified in the <code>paginate_by</code> line in your <strong>catalog/views.py</strong> file. To get the below result we changed it to <code>paginate_by = 2</code>.</p> + +<p>The pagination links are displayed on the bottom, with next/previous links being displayed depending on which page you're on.</p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>The challenge in this article is to create the author detail and list views required to complete the project. These should be made available at the following URLs:</p> + +<ul> + <li><code>catalog/authors/</code> — The list of all authors.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— The detail view for the specific author with a primary key field named <em><code><id></code></em></li> +</ul> + +<p>The code required for the URL mappers and the views should be virtually identical to the <code>Book</code> list and detail views we created above. The templates will be different, but will share similar behaviour.</p> + +<div class="note"> +<p><strong>Note</strong>:</p> + +<ul> + <li>Once you've created the URL mapper for the author list page you will also need to update the <strong>All authors</strong> link in the base template. Follow the <a href="#Update_the_base_template">same process</a> as we did when we updated the <strong>All books</strong> link.</li> + <li>Once you've created the URL mapper for the author detail page, you should also update the <a href="#Creating_the_Detail_View_template">book detail view template</a> (<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>) so that the author link points to your new author detail page (rather than being an empty URL). The line will change to add the template tag shown in bold below. + <pre class="brush: html notranslate"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>When you are finished, your pages should look something like the screenshots below.</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Congratulations, our basic library functionality is now complete! </p> + +<p>In this article we've learned how to use the generic class-based list and detail views and used them to create pages to view our books and authors. Along the way we've learned about pattern matching with regular expressions, and how you can pass data from URLs to your views. We've also learned a few more tricks for using templates. Last of all we've shown how to paginate list views, so that our lists are managable even when we have many records.</p> + +<p>In our next articles we'll extend this library to support user accounts, and thereby demonstrate user authentication, permissons, sessions, and forms.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/">Generic display views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/">Introduction to class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/pagination/">Pagination</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> |