《Django By Example》第二章 中文 翻譯 (我的學習,渣翻)

書籍出處:https://www.packtpub.com/web-development/django-example
原做者:Antonio Melécss

2016年12月13日發佈(3天完成第二章的翻譯,但沒有進行校對,有不少錯別字以及模糊不清的語句,請你們見諒)html

2017年2月17日校對完成(不是精校,但願你們多指出須要修改的地方)python

2017年3月6日精校完成(感謝大牛 @kukoo 的精校!)git

2017年3月21日再度精校(感謝大牛 @媽媽不在家 的精校!第一版我已經不敢再看!)github

(譯者注:翻譯完第一章後,發現翻譯第二章的速度上升了很多,難道這就是傳說中的經驗值提高了?)web

第二章

用高級特性來加強你的blog

在上一章中,你建立了一個基礎的博客應用。如今你將利用一些高級的特性例如經過email來分享帖子,添加評論,給帖子打上tag,檢索出類似的帖子等將它改形成爲一個功能更加齊全的博客。在本章中,你將會學習如下幾點:shell

  • 經過Django發送email
  • 在視圖(views)中建立並操做表單
  • 經過模型(models)建立表單
  • 集成第三方應用
  • 構建複雜的查詢集(QuerySets)

經過email分享帖子

首先,咱們會容許用戶經過發送郵件來分享他們的帖子。讓咱們花費一小會時間來想下,根據在上一章中學到的知識,你該如何使用views,urls和templates來建立這個功能。如今,覈對一下你須要哪些才能容許你的用戶經過郵件來發送帖子。你須要作到如下幾點:數據庫

  • 給用戶建立一個表單來填寫他們的姓名,email,收件人以及評論,評論不是必選項。
  • views.py文件中建立一個視圖(view)來操做發佈的數據和發送email
  • 在blog應用的urls.py中爲新的視圖(view)添加一個URL模式
  • 建立一個模板(template)來展現這個表單

使用Django建立表單

讓咱們開始建立一個表單來分享帖子。Django有一個內置的表單框架容許你經過簡單的方式來建立表單。這個表單框架容許你定義你的表單字段,指定這些字段必須展現的方式,以及指定這些字段如何驗證輸入的數據。Django表單框架還提供了一種靈活的方式來渲染表單以及操做數據。django

Django提供了兩個能夠建立表單的基本類:api

  • Form: 容許你建立一個標準表單
  • ModelForm: 容許你建立一個可用於建立或者更新model實例的表單

首先,在你blog應用的目錄下建立一個forms.py文件,輸入如下代碼:

from django import forms

class EmailPostForm(forms.Form):
    name = forms.CharField(max_length=25)
    email = forms.EmailField()
    to = forms.EmailField()
    comments = forms.CharField(required=False,
                                         widget=forms.Textarea)

這是你的第一個Django表單。看下代碼:咱們已經建立了一個繼承了基礎Form類的表單。咱們使用不一樣的字段類型以使Django有依據的來驗證字段。

表單能夠存在你的Django項目的任何地方,但按照慣例將它們放在每個應用下面的forms.py文件中

name字段是一個CharField。這種類型的字段被渲染成<input type=「text」>HTML元素。每種字段類型都有默認的控件來肯定它在HTML中的展現形式。經過改變控件的屬性能夠重寫默認的控件。在comment字段中,咱們使用<textarea></textarea>HTML元素而不是使用默認的<input>元素來顯示它。

字段驗證取決於字段類型。例如,emailto字段是EmailField,這兩個字段都須要一個有效的email地址,不然字段驗證將會拋出一個forms.ValidationError異常致使表單驗證不經過。在表單驗證的時候其餘的參數也會被考慮進來:咱們將name字段定義爲一個最大長度爲25的字符串;經過設置required=False讓comments的字段可選。全部這些也會被考慮到字段驗證中去。目前咱們在表單中使用的這些字段類型只是Django支持的表單字段的一部分。要查看更多可利用的表單字段,你能夠訪問:https://docs.djangoproject.com/en/1.8/ref/forms/fields/

在視圖(views)中操做表單

當表單成功提交後你必須建立一個新的視圖(views)來操做表單和發送email。編輯blog應用下的views.py文件,添加如下代碼:

from .forms import EmailPostForm

def post_share(request, post_id):
    # retrieve post by id
    post = get_object_or_404(Post, id=post_id, status='published')
    
    if request.method == 'POST':
        # Form was submitted
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # Form fields passed validation
            cd = form.cleaned_data
            # ... send email
    else:
        form = EmailPostform()
    return render(request, 'blog/post/share.html', {'post': post,
                                                               'form: form})

該視圖(view)完成了如下工做:

  • 咱們定義了post_share視圖,參數爲request對象和post_id
  • 咱們使用get_object_or_404快捷方法經過ID獲取對應的帖子,而且確保獲取的帖子有一個published狀態。
  • 咱們使用同一個視圖(view)來展現初始表單和處理提交後的數據。咱們會區別被提交的表單和不基於此次請求方法的表單。咱們將使用POST來提交表單。若是咱們獲得一個GET請求,一個空的表單必須顯示,而若是咱們獲得一個POST請求,則表單須要提交和處理。所以,咱們使用request.method == 'POST'來區分這兩種場景。

下面是展現和操做表單的過程:

  • 1.經過GET請求視圖(view)被初始加載後,咱們建立一個新的表單實例,用來在模板(template)中顯示一個空的表單:

    form = EmailPostForm()
  • 2.當用戶填寫好了表單並經過POST提交表單。以後,咱們會用保存在request.POST中提交的數據建立一個表單實例。

    if request.method == 'POST':
         # Form was submitted
         form = EmailPostForm(request.POST)
  • 3.在以上步驟以後,咱們使用表單的is_valid()方法來驗證提交的數據。這個方法會驗證表單引進的數據,若是全部的字段都是有效數據,將會返回True。一旦有任何一個字段是無效的數據,is_valid()就會返回False。你能夠經過訪問 form.errors來查看全部驗證錯誤的列表。
  • 4若是表單數據驗證沒有經過,咱們會再次使用提交的數據在模板(template)中渲染表單。咱們會在模板(template)中顯示驗證錯誤的提示。
  • 5.若是表單數據驗證經過,咱們經過訪問form.cleaned_data獲取驗證過的數據。這個屬性是一個表單字段和值的字典。

若是你的表單數據沒有經過驗證,cleaned_data只會包含驗證經過的字段

如今,你須要學習如何使用Django來發送email,把全部的事情結合起來。

使用Django發送email

使用Django發送email很是簡單。首先,你須要有一個本地的SMTP服務或者經過在你項目的settings.py文件中添加如下設置去定義一個外部SMTP服務器的配置:

  • EMAIL_HOST: SMTP服務地址。默認本地。
  • EMAIL_POSR: SMATP服務端口,默認25。
  • EMAIL_HOST_USER: SMTP服務的用戶名。
  • EMAIL_HOST_PASSWORD: SMTP服務的密碼。
  • EMAIL_USE_TLS: 是否使用TLS加密鏈接。
  • EMAIL_USE_SSL: 是否使用隱式的SSL加密鏈接。

若是你沒有本地SMTP服務,你可使用你的email服務供應商提供的SMTP服務。下面提供了一個簡單的例子展現如何經過使用Google帳戶的Gmail服務來發送email:

EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_account@gmail.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True

運行命令python manage.py shell來打開Python shell,發送一封email以下所示:

>>> from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.','your_account@gmail.com', ['your_account@gmail.com'], fail_silently=False)

send_mail()方法須要這些參數:郵件主題,內容,發送人以及一個收件人的列表。經過設置可選參數fail_silently=False,咱們告訴這個方法若是email沒有發送成功那麼須要拋出一個異常。若是你看到輸出是1,證實你的email發送成功了。若是你使用以前的配置用Gmail來發送郵件,你可能須要去 https://www.google.com/settings/security/lesssecureapps 去開通一下低安全級別應用的權限。(譯者注:練習時老老實實用QQ郵箱吧)

如今,咱們要將以上代碼添加到咱們的視圖(view)中。在blog應用下的views.py文件中編輯post_share視圖(view)以下所示:

from django.core.mail import send_mail

def post_share(request, post_id):
    # Retrieve post by id
    post = get_object_or_404(Post, id=post_id, status='published')
    sent = False
    if request.method == 'POST':
        # Form was submitted
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # Form fields passed validation
            cd = form.cleaned_data
            post_url = request.build_absolute_uri(
                                    post.get_absolute_url())
            subject = '{} ({}) recommends you reading "{}"'.format(cd['name'], cd['email'], post.title)
            message = 'Read "{}" at {}\n\n{}\'s comments: {}'.format(post.title, post_url, cd['name'], cd['comments'])
            send_mail(subject, message, 'admin@myblog.com',[cd['to']])
            sent = True
    else:
        form = EmailPostForm()
        
    return render(request, 'blog/post/share.html', {'post': post,
                                                    'form': form,
                                                    'sent': sent})

請注意,咱們聲明瞭一個sent變量而且當帖子被成功發送時賦予它True。當表單成功提交的時候,咱們以後將在模板(template)中使用這個變量顯示一條成功提示。因爲咱們須要在email中包含帖子的超連接,因此咱們經過使用post.get_absolute_url()方法來獲取到帖子的絕對路徑。咱們將這個絕對路徑做爲request.build_absolute_uri()的輸入值來構建一個完整的包含了HTTP schema和主機名的url。咱們經過使用驗證過的表單數據來構建email的主題和消息內容並最終給表單to字段中包含的全部email地址發送email。

如今你的視圖(view)已經完成了,別忘記爲它去添加一個新的URL模式。打開你的blog應用下的urls.py文件添加post_share的URL模式以下所示:

urlpatterns = [
# ...
url(r'^(?P<post_id>\d+)/share/$', views.post_share,
    name='post_share'),
]

在模板(templates)中渲染表單

在經過建立表單,編寫視圖(view)以及添加URL模式後,咱們就只剩下爲這個視圖(view)添加模板(tempalte)了。在blog/templates/blog/post/目錄下建立一個新的文件並命名爲share.html。在該文件中添加以下代碼:

{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
  {% if sent %}
    <h1>E-mail successfully sent</h1>
    <p>
      "{{ post.title }}" was successfully sent to {{ cd.to }}.
    </p>
  {% else %}
    <h1>Share "{{ post.title }}" by e-mail</h1>
    <form action="." method="post">
      {{ form.as_p }}
      {% csrf_token %}
      <input type="submit" value="Send e-mail">
    </form>
  {% endif %}
{% endblock %}

這個模板(tempalte)專門用來顯示一個表單或一條成功提示信息。如你所見,咱們建立的HTML表單元素裏面代表了它必須經過POST方法提交:

<form action="." method="post">

接下來咱們要包含真實的表單實例。咱們告訴Django用as_p方法利用HTML的<p>元素來渲染它的字段。咱們也可使用as_ul利用無序列表來渲染表單或者使用as_table利用HTML表格來渲染。若是咱們想要逐一渲染每個字段,咱們能夠迭代字段。例以下方的例子:

{% for field in form %}
  <div>
    {{ field.errors }}
    {{ field.label_tag }} {{ field }}
  </div>
{% endfor %}

{% csrf_token %}模板(tempalte)標籤(tag)引進了能夠避開Cross-Site request forgery(CSRF)攻擊的自動生成的令牌,這是一個隱藏的字段。這些攻擊由惡意的站點或者能夠在你的站點中爲用戶執行惡意行爲的程序組成。經過訪問 https://en.wikipedia.org/wiki/Cross-site_request_forgery你能夠找到更多的信息 。

上述的標籤(tag)生成的隱藏字段就像下面同樣:

<input type='hidden' name='csrfmiddlewaretoken' value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />

默認狀況下,Django在全部的POST請求中都會檢查CSRF標記(token)。請記住要在全部使用POST方法提交的表單中包含csrf_token標籤。(譯者注:固然你也能夠關閉這個檢查,註釋掉app_list中的csrf應用便可,我就是這麼作的,由於我懶)

編輯你的blog/post/detail.html模板(template),在{{ post.body|linebreaks }}變量後面添加以下的連接來分享帖子的URL:

<p>
  <a href="{% url "blog:post_share" post.id %}">
    Share this post
  </a>
</p>

請記住,咱們經過使用Django提供的{% url %}模板(template)標籤(tag)來動態的生成URL。咱們以blog爲命名空間,以post_share爲URL,同時傳遞帖子ID做爲參數來構建絕對的URL。

如今,經過python manage.py runserver命令來啓動開發服務器,在瀏覽器中打開 http://127.0.0.1:8000/blog/ 。點擊任意一個帖子標題查看詳情頁面。在帖子內容的下方,你會看到咱們剛剛添加的連接,以下所示:
django-2-1

點擊Share this post,你會看到包含經過email分享帖子的表單的頁面。看上去以下所示:
django-2-2

這個表單的CSS樣式被包含在示例代碼中的 static/css/blog.css文件中。當你點擊Send e-mail按鈕,這個表單會提交併驗證。若是全部的字段都經過了驗證,你會獲得一條成功信息以下所示:
django-2-3

若是你輸入了無效數據,你會看到表單被再次渲染,並展現出驗證錯誤信息,以下所示:
django-2-4

建立一個評論系統

如今咱們準備爲blog建立一個評論系統,這樣用戶能夠在帖子上進行評論。須要作到如下幾點來建立一個評論系統:

  • 建立一個模型(model)用來保存評論
  • 建立一個表單用來提交評論而且驗證輸入的數據
  • 添加一個視圖(view)來處理表單和保存新的評論到數據庫中
  • 編輯帖子詳情模板(template)來展現評論列表以及用來添加新評論的表單

首先,讓咱們建立一個模型(model)來存儲評論。打開你的blog應用下的models.py文件添加以下代碼:

class Comment(models.Model):
    post = models.ForeignKey(Post, related_name='comments')
    name = models.CharField(max_length=80)
    email = models.EmailField()
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)
    
    class Meta:
        ordering = ('created',)
        
    def __str__(self):
        return 'Comment by {} on {}'.format(self.name, self.post)

以上就是咱們的Comment模型(model)。它包含了一個外鍵將一個單獨的帖子和評論關聯起來。在Comment模型(model)中定義多對一(many-to-one)的關係是由於每一條評論只能在一個帖子下生成,而每個帖子又可能包含多個評論。related_name屬性容許咱們給這個屬性命名,這樣咱們就能夠利用這個關係從相關聯的對象反向定位到這個對象。定義好這個以後,咱們經過使用 comment.post就能夠從一條評論來取到對應的帖子,以及經過使用post.comments.all()來取回一個帖子全部的評論。若是你沒有定義related_name屬性,Django會使用這個模型(model)的名稱加上*_set*(在這裏是:comment_set)來命名從相關聯的對象反向定位到這個對象的manager。

訪問https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/, 你能夠學習更多關於多對一的關係。

咱們用一個active布爾字段用來手動禁用那些不合適的評論。默認狀況下,咱們根據created字段,對評論按時間順序進行排序。

你剛建立的這個新的Comment模型(model)並無同步到數據庫中。運行如下命令生成一個新的反映了新模型(model)建立的數據遷移:

python manage.py makemigrations blog

你會看到以下輸出:

Migrations for 'blog':
  0002_comment.py:
    - Create model Comment

Django在blog應用下的migrations/目錄中生成了一個0002_comment.py文件。如今你須要建立一個關聯數據庫模式而且將這些改變應用到數據庫中。運行如下命令來執行已經存在的數據遷移:

python manage.py migrate

你會獲取如下輸出:

Applying blog.0002_comment... OK

咱們剛剛建立的數據遷移已經被執行,如今一張blog_comment表已經存在數據庫中。

如今,咱們能夠添加咱們新的模型(model)到管理站點中並經過簡單的接口來管理評論。打開blog應用下的admin.py文件,添加comment model的導入,添加以下內容:

from .models import Post, Comment

class CommentAdmin(admin.ModelAdmin):
    list_display = ('name', 'email', 'post', 'created', 'active')
    list_filter = ('active', 'created', 'updated')
    search_fields = ('name', 'email', 'body')
admin.site.register(Comment, CommentAdmin)

運行命令python manage.py runserver來啓動開發服務器而後在瀏覽器中打開 http://127.0.0.1:8000/admin/ 。你會看到新的模型(model)在Blog區域中出現,以下所示:
django-2-5

咱們的模型(model)如今已經被註冊到了管理站點,這樣咱們就可使用簡單的接口來管理評論實例。

經過模型(models)建立表單

咱們仍然須要構建一個表單讓咱們的用戶在blog帖子下進行評論。請記住,Django有兩個用來建立表單的基礎類:FormModelForm。你先前已經使用過第一個讓用戶經過email來分享帖子。在當前的例子中,你將須要使用ModelForm,由於你必須經過你的Comment模型(model)動態的建立表單。編輯blog應用下的forms.py,添加以下代碼:

from .models import Comment

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ('name', 'email', 'body')

根據模型(model)建立表單,咱們只須要在這個表單的Meta類裏代表使用哪一個模型(model)來構建表單。Django將會解析model併爲咱們動態的建立表單。每一種模型(model)字段類型都有對應的默認表單字段類型。表單驗證時會考慮到咱們定義模型(model)字段的方式。Django爲模型(model)中包含的每一個字段都建立了表單字段。然而,使用fields 列表你能夠明確的告訴框架你想在你的表單中包含哪些字段,或者使用exclude 列表定義你想排除在外的那些字段。對於咱們的CommentForm來講,咱們在表單中只須要name,email,和body字段,由於咱們只須要用到這3個字段讓咱們的用戶來填寫。

在視圖(views)中操做ModelForms

爲了能更簡單的處理它,咱們會使用帖子的詳情視圖(view)來實例化表單。編輯views.py文件(注: 原文此處有錯,應爲 views.py),導入Comment模型(model)和CommentForm表單,而且修改post_detail視圖(view)以下所示:

from .models import Post, Comment
from .forms import EmailPostForm, CommentForm

def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post, slug=post,
                                   status='published',
                                   publish__year=year,
                                   publish__month=month,
                                   publish__day=day)
    # List of active comments for this post
    comments = post.comments.filter(active=True)
    new_comment = None #原文沒有這行,可是經測試必須有這行
        
    if request.method == 'POST':
        # A comment was posted
        comment_form = CommentForm(data=request.POST)
        if comment_form.is_valid():
            # Create Comment object but don't save to database yet
            new_comment = comment_form.save(commit=False)
            # Assign the current post to the comment
            new_comment.post = post
            # Save the comment to the database
            new_comment.save()
    else:
        comment_form = CommentForm()
    return render(request,
                  'blog/post/detail.html',
                  {'post': post,
                  'comments': comments, 
                  'new_comment': new_comment,
                  'comment_form': comment_form})

讓咱們來回顧一下咱們剛纔對視圖(view)添加了哪些操做。咱們使用post_detail視圖(view)來顯示帖子和該帖子的評論。咱們添加了一個查詢集(QuerySet)來獲取這個帖子全部有效的評論:

comments = post.comments.filter(active=True)

咱們從post對象開始構建這個查詢集(QuerySet)。咱們使用關聯對象的manager,這個manager是咱們在Comment 模型(model)中使用related_name關係屬性爲comments定義的。
咱們還在這個視圖(view)中讓咱們的用戶添加一條新的評論。所以,若是這個視圖(view)是經過GET請求被加載的,那麼咱們用comment_fomr = commentForm()來建立一個表單實例。若是是經過POST請求,咱們使用提交的數據而且用is_valid()方法驗證這些數據去實例化表單。若是這個表單是無效的,咱們會用驗證錯誤信息渲染模板(template)。若是表單經過驗證,咱們會作如下的操做:

  • 1.咱們經過調用這個表單的save()方法建立一個新的Comment對象,以下所示:

new_comment = comment_form.save(commit=False)

Save()方法建立了一個表單連接的model的實例,並將它保存到數據庫中。若是你調用這個方法時設置commit=False,你建立的模型(model)實例不會即時保存到數據庫中。當你想在最終保存以前修改這個model對象會很是方便,咱們接下來將作這一步驟。save()方法是給ModelForm用的,而不是給Form實例用的,由於Form實例沒有關聯上任何模型(model)。

  • 2.咱們爲咱們剛建立的評論分配一個帖子:

    new_comment.post = post

    經過以上動做,咱們指定新的評論是屬於這篇給定的帖子。

  • 3.最後,咱們用下面的代碼將新的評論保存到數據庫中:

    new_comment.save()

咱們的視圖(view)已經準備好顯示和處理新的評論了。

在帖子詳情模板(template)中添加評論

咱們爲帖子建立了一個管理評論的功能。如今咱們須要修改咱們的post_detail.html模板(template)來適應這個功能,經過作到如下步驟:

  • 顯示這篇帖子的評論總數
  • 顯示評論的列表
  • 顯示一個表單給用戶來添加新的評論

首先,咱們來添加評論的總數。打開blog_detail.html模板(template)在content區塊中添加以下代碼:

{% with comments.count as total_comments %}
  <h2>
    {{ total_comments }} comment{{ total_comments|pluralize }}
  </h2>
{% endwith %}

在模板(template)中咱們使用Django ORM執行comments.count() 查詢集(QuerySet)。注意,Django模板(template)語言中不使用圓括號來調用方法。{% with %} 標籤(tag)容許咱們分配一個值給新的變量,這個變量能夠一直使用直到遇到{% endwith %}標籤(tag)。

{% with %}模板(template)標籤(tag)是很是有用的,能夠避免直接操做數據庫或避免屢次調用花費較多的方法。

根據total_comments的值,咱們使用pluralize 模板(template)過濾器(filter)爲單詞comment顯示覆數後綴。模板(Template)過濾器(filters)獲取到他們輸入的變量值,返回計算後的值。咱們將會在第三章 擴展你的博客應用中討論更多的模板過濾器(tempalte filters)。

pluralize模板(template)過濾器(filter)在值不爲1時,會在值的末尾顯示一個"s"。以前的文本將會被渲染成相似:0 comments, 1 comment 或者 N comments。Django內置大量的模板(template)標籤(tags)和過濾器(filters)來幫助你以你想要的方式來顯示信息。

如今,讓咱們加入評論列表。在模板(template)中以前的代碼後面加入如下內容:

{% for comment in comments %}
  <div class="comment">
    <p class="info">
      Comment {{ forloop.counter }} by {{ comment.name }}
      {{ comment.created }}
    </p>
    {{ comment.body|linebreaks }}
  </div>
{% empty %}
  <p>There are no comments yet.</p>
{% endfor %}

咱們使用{% for %}模板(template)標籤(tag)來循環全部的評論。若是comments列爲空咱們會顯示一個默認的信息,告訴咱們的用戶這篇帖子尚未任何評論。咱們使用 {{ forloop.counter }}變量來枚舉全部的評論,在每次迭代中該變量都包含循環計數。以後咱們顯示發送評論的用戶名,日期,和評論的內容。

最後,當表單提交成功後,你須要渲染表單或者顯示一條成功的信息來代替以前的內容。在以前的代碼後面添加以下內容:

{% if new_comment %}
  <h2>Your comment has been added.</h2>
{% else %}
  <h2>Add a new comment</h2>
  <form action="." method="post">
    {{ comment_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Add comment"></p>
  </form>
{% endif %}

這段代碼很是簡潔明瞭:若是new_comment對象存在,咱們會展現一條成功信息由於成功建立了一條新評論。不然,咱們用段落<p>元素渲染表單中每個字段,而且包含POST請求須要的CSRF令牌。在瀏覽器中打開 http://127.0.0.1:8000/blog/ 而後點擊任意一篇帖子的標題查看它的詳情頁面。你會看到以下頁面展現:

django-2-6

使用該表單添加數條評論。這些評論會在你的帖子下面根據時間排序來展現,相似下圖:
django-2-7

在你的瀏覽器中打開 http://127.0.0.1:8000/admin/blog/comment/ 。你會在管理頁面中看到你建立的評論列表。點擊其中一個進行編輯,取消選擇Active複選框,而後點擊Save按鈕。你會再次被重定向到評論列表頁面,剛纔編輯的評論Save列將會顯示成一個沒有激活的圖標。相似下圖的第一個評論:
django-2-8

增長標籤(tagging)功能

在實現了咱們的評論系統以後,咱們準備創建立一個方法來給咱們的帖子添加標籤。咱們將經過在咱們的項目中集成第三方的Django標籤應用來完成這個功能。django-taggit是一個可複用的應用,它會提供給你一個Tag模型(model)和一個管理器(manager)來方便的給任何模型(model)添加標籤。你能夠在 https://github.com/alex/django-taggit 看到它的源碼。

首先,你須要經過pip安裝django-taggit,運行如下命令:

pip install django-taggit==0.17.1**(譯者注:根據@孤獨狂飲 驗證,直接 `pip install django-taggit` 安裝最新版便可,原做者提供的版本過舊會有問題,感謝@孤獨狂飲)**

以後打開mysite項目下的settings.py文件,在INSTALLED_APPS設置中設置以下:

INSTALLED_APPS = (
    # ...
    'blog',
    'taggit',
)

打開你的blog應用下的model.py文件,給Post模型(model)添加django-taggit提供的TaggableManager管理器(manager),使用以下代碼:

from taggit.managers import TaggableManager
class Post(models.Model):
    # ...
    tags = TaggableManager()

這個tags管理器(manager)容許你給Post對象添加,獲取以及移除標籤。

運行如下命令爲你的模型(model)改變建立一個數據庫遷移:

python manage.py makemigrations blog

你會看到以下輸出:

Migrations for 'blog':
  0003_post_tags.py:
    - Add field tags to post

如今,運行如下代碼在數據庫中生成django-taggit模型(model)對應的表以及同步你的模型(model)的改變:

python manage.py migrate

你會看到如下輸出,:

Applying taggit.0001_initial... OK
Applying taggit.0002_auto_20150616_2121... OK
Applying blog.0003_post_tags... OK

你的數據庫如今已經可使用django-taggit模型(model)。打開終端運行命令python manage.py shell來學習如何使用tags管理器(manager)。首先,咱們取回咱們的其中一篇帖子(該帖子的ID爲1):

>>> from blog.models import Post
>>> post = Post.objects.get(id=1)

以後爲它添加一些標籤而且取回它的標籤來檢查標籤是否添加成功:

>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
[<Tag: jazz>, <Tag: django>, <Tag: music>]

最後,移除一個標籤而且再次檢查標籤列表:

>>> post.tags.remove('django')
>>> post.tags.all()
[<Tag: jazz>, <Tag: music>]

很是簡單,對吧?運行命令python manage.py runserver啓動開發服務器,在瀏覽器中打開 http://127.0.0.1:8000/admin/taggit/tag/ 。你會看到管理頁面包含了taggit應用的Tag對象列表:

django-2-9

轉到 http://127.0.0.1:8000/admin/blog/post/ 並點擊一篇帖子進行編輯。你會看到帖子中包含了一個新的Tags字段以下所示,你能夠很是容易的編輯它:
django-2-10

如今,咱們準備編輯咱們的blog帖子來顯示這些標籤。打開blog/post/list.html 模板(template)在帖子標題下方添加以下HTML代碼:

<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>

join模板(template)過濾器(filter)的功能相似python字符串的join()方法,將給定的字符串鏈接起來。在瀏覽器中打開 http://127.0.0.1:8000/blog/ 。 你會看到每個帖子的標題下面的標籤列表:

django-2-11

如今,讓咱們來編輯咱們的post_list視圖(view)讓用戶能夠列出打上了特定標籤的全部帖子。打開blog應用下的views.py文件,從django-taggit中導入Tag模型(model),而後修改post_list視圖(view)讓它能夠經過標籤選擇性的過濾,以下所示:

from taggit.models import Tag

def post_list(request, tag_slug=None): 
    object_list = Post.published.all() 
    tag = None
    
    if tag_slug:
        tag = get_object_or_404(Tag, slug=tag_slug) 
        object_list =   object_list.filter(tags__in=[tag]) 
        # ...

這個視圖(view)作了如下工做:

  • 1.視圖(view)帶有一個可選的tag_slug參數,默認是一個None值。這個參數會帶進URL中。
  • 2.視圖(view)的內部,咱們構建了初始的查詢集(QuerySet),取回全部發布狀態的帖子,假如給予一個標籤 slug,咱們經過get_object_or_404()用給定的slug來獲取標籤對象。
  • 3.以後咱們過濾全部帖子只留下包含給定標籤的帖子。由於有一個多對多(many-to-many)的關係,咱們必須經過給定的標籤列表來過濾,在咱們的例子中標籤列表只包含一個元素。

要記住查詢集(QuerySets)是惰性的。這個查詢集(QuerySets)只有當咱們在模板(template)中循環渲染帖子列表時纔會被執行。

最後,修改視圖(view)最底部的render()函數來,傳遞tag變量給模板(template)。這個視圖(view)完成後以下所示:

def post_list(request, tag_slug=None):
   object_list = Post.published.all()
   tag = None
   
   if tag_slug:
       tag = get_object_or_404(Tag, slug=tag_slug)
       object_list = object_list.filter(tags__in=[tag])
       
   paginator = Paginator(object_list, 3) # 3 posts in each page
   page = request.GET.get('page')
   try:
       posts = paginator.page(page)
   except PageNotAnInteger:
       # If page is not an integer deliver the first page
       posts = paginator.page(1)
   except EmptyPage:
       # If page is out of range deliver last page of results
       posts = paginator.page(paginator.num_pages)
   return render(request, 'blog/post/list.html', {'page': page,
                                                  'posts': posts,
                                                  'tag': tag})

打開blog應用下的url.py文件,註釋基於類的PostListView URL模式,而後取消post_list視圖(view)的註釋,以下所示:

url(r'^$', views.post_list, name='post_list'),
# url(r'^$', views.PostListView.as_view(), name='post_list'),

添加下面額外的URL pattern到經過標籤過濾過的帖子列表中:

url(r'^tag/(?P<tag_slug>[-\w]+)/$',views.post_list,
    name='post_list_by_tag'),

如你所見,兩個模式都指向了相同的視圖(view),可是咱們能夠給它們不一樣的命名。第一個模式會調用post_list視圖(view)而且不帶上任何可選參數。然而第二個模式會調用這個視圖(view)帶上tag_slug參數。

由於咱們要使用post_list視圖(view),編輯blog/post/list.html 模板(template),使用posts對象修改pagination,以下所示:

{% include "pagination.html" with page=posts %}

{% for %}循環上方添加以下代碼:

{% if tag %}
  <h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}

若是用戶正在訪問blog,他會看到全部帖子列表。若是他指定一個標籤來過濾全部的帖子,他就會看到以上的信息。如今,修改標籤的顯示方式,以下所示:

<p class="tags">
  Tags:
  {% for tag in post.tags.all %}
    <a href="{% url "blog:post_list_by_tag" tag.slug %}">
      {{ tag.name }}
    </a>
    {% if not forloop.last %}, {% endif %}
  {% endfor %}
</p>

如今,咱們循環一個帖子的全部標籤,經過某一標籤來顯示一個自定義的連接URL。咱們經過{% url "blog:post_list_by_tag" tag.slug %},用URL的名稱以及標籤 slug做爲參數來構建URL。咱們使用逗號分隔這些標籤。

在瀏覽器中打開 http://127.0.0.1:8000/blog/ 而後點擊任意的標籤連接,你會看到經過該標籤過濾過的帖子列表,以下所示:

django-2-12

檢索相似的帖子

現在,咱們已經能夠給咱們的blog帖子加上標籤,咱們能夠經過它們作更多有意思的事情。經過使用標籤,咱們可以很好的分類咱們的blog帖子。擁有相似主題的帖子通常會有幾個共同的標籤。咱們準備建立一個功能:經過帖子共享的標籤數量來顯示相似的帖子。這樣的話,當一個用戶閱讀一個帖子,咱們能夠建議他們去讀其餘有關聯的帖子。

爲了經過一個特定的帖子檢索到相似的帖子,咱們須要作到如下幾點:

  • 返回當前帖子的全部標籤。
  • 返回全部帶有這些標籤的帖子。
  • 在返回的帖子列表中排除當前的帖子,避免推薦相同的帖子。
  • 經過和當前帖子共享的標籤數量來排序全部的返回結果。
  • 假設有兩個或多個帖子擁有相同數量的標籤,推薦最近的帖子。
  • 限制咱們想要推薦的帖子數量。

這些步驟能夠轉換成一個複雜的查詢集(QuerySet),該查詢集(QuerySet)咱們須要包含在咱們的post_detail視圖(view)中。打開blog應用中的view.py文件,在頂部添加以下導入:

from django.db.models import Count

這是Django ORM的Count聚合函數。這個函數容許咱們處理聚合計算。而後在post_detail視圖(view)的render()函數以前添加以下代碼:

# List of similar posts
post_tags_ids = post.tags.values_list('id', flat=True)
similar_posts = Post.published.filter(tags__in=post_tags_ids)\
                               .exclude(id=post.id)
similar_posts = similar_posts.annotate(same_tags=Count('tags'))\
                            .order_by('-same_tags','-publish')[:4]

以上代碼的解釋以下:

  • 1.咱們取回了一個包含當前帖子全部標籤的ID的Python列表。values_list() 查詢集(QuerySet)返回包含給定的字段值的元祖。咱們傳給元祖flat=True來獲取一個簡單的列表相似[1,2,3,...]
  • 2.咱們獲取全部包含這些標籤的帖子排除了當前的帖子。
  • 3.咱們使用Count聚合函數來生成一個計算字段same_tags,該字段包含與查詢到的全部 標籤共享的標籤數量。
  • 4.咱們經過共享的標籤數量來排序(降序)結果而且經過publish字段來挑選擁有相同共享標籤數量的帖子中的最近的一篇帖子。咱們對返回的結果進行切片只保留最前面的4篇帖子。

render()函數中給上下文字典增長similar_posts對象,以下所示:

return render(request,
                'blog/post/detail.html',
                {'post': post,
                'comments': comments,
                'comment_form': comment_form,
                'similar_posts': similar_posts})

如今,編輯blog/post/detail.html模板(template)在帖子評論列表前添加以下代碼:

<h2>Similar posts</h2>
  {% for post in similar_posts %}
    <p>
      <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </p>
  {% empty %}
    There are no similar posts yet.
  {% endfor %}

推薦你在你的帖子詳情模板中添加標籤列表,就像咱們在帖子列表模板所作的同樣。如今,你的帖子詳情頁面看上去以下所示:
django-2-13

你已經成功的爲你的用戶推薦了相似的帖子。django-taggit還內置了一個similar_objects() 管理器(manager)使你能夠經過共享的標籤返回全部對象。你能夠經過訪問 http://django-taggit.readthedocs.org/en/latest/api.html 看到全部django-taggit管理器。

總結

在本章中,你學習瞭如何使用Django的表單和模型(model)表單。你建立了一個經過email分享你的站點內容的系統,還爲你的博客建立了一個評論系統。經過集成一個可複用的應用,你爲你的帖子增長了打標籤的功能。同時,你還構建了一個複雜的查詢集(QuerySets)用來返回相似的對象。

在下一章中,你會學習到如何建立自定義的模板(temaplate)標籤(tags)和過濾器(filters)。你還會爲你的博客應用構建一個自定義的站點地圖,集成一個高級的搜索引擎。

書籍出處:https://www.packtpub.com/web-development/django-example
原做者:Antonio Melé

2016年12月13日發佈(3天完成第二章的翻譯,但沒有進行校對,有不少錯別字以及模糊不清的語句,請你們見諒)

2017年2月17日校對完成(不是精校,但願你們多指出須要修改的地方)

2017年3月6日精校完成(感謝感謝大牛 @kukoo 的精校!)

2017年3月21日再度精校(感謝大牛 @媽媽不在家 的精校!第一版我已經不敢再看!)

相關文章
相關標籤/搜索