Django搭建個人部落格:給文章加個漂亮的標題圖

杜賽_dusai發表於2019-02-27

現在雖然部落格的功能大都實現了,但是介面還是比較樸素,特別是首頁的文章列表幾乎全是文字,看多了難免疲勞。因此,給每個文章標題配一張標題圖,不僅美觀,使用者也能通過圖片快速瞭解文章內容。實際上大部分社交網站也都是這麼幹的,畢竟人的天性就是懶,能看圖就堅決不看字。

上傳使用者頭像章節中,我們已經接觸過上傳、展示圖片了。標題圖的實現也差不多,不同的是本章會更近一步,對圖片進行縮放等處理,使頁面整潔美觀、並且高效。

準備工作

與使用者頭像類似,標題圖是屬於每篇博文自己的“資產”,因此需要修改model,新建一個欄位:

article/models.py

class ArticlePost(models.Model):
    ...
    
    # 文章標題圖
    avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)
    
    ...
複製程式碼

注意上傳地址中的%Y%m%d是日期格式化的寫法。比如上傳時間是2019年2月26日,則標題圖會上傳到media/article/20190226這個目錄中。

記得資料遷移

標題圖通常在建立新文章的時候就設定好了,而新文章是通過表單上傳到資料庫中的。因此接下來就是修改發表文章的表單類

article/forms.py

...
class ArticlePostForm(forms.ModelForm):
    class Meta:
        ...
        fields = ('title', 'body', 'tags', 'avatar')

複製程式碼

增加了avatar欄位而已,沒有新內容。

下一步就是修改檢視。因為POST的表單中包含了圖片檔案,所以要將request.FILES也一併繫結到表單類中,否則圖片無法正確儲存:

article/views.py

...
def article_create(request):
    if request.method == "POST":
        # 增加 request.FILES
        article_post_form = ArticlePostForm(request.POST, request.FILES)
        
        ...
複製程式碼

很好,功能差不多已經通了,接下來就是對圖片進行處理。

處理圖片

寫程式碼之前先構思一下需要進行怎樣的處理:

  • 標題圖對畫質沒有太高的要求,因此需要縮小圖片的體積,以便提高網頁的載入速度。
  • 其次還需要對圖片的長寬進行規範化。我比較喜歡將圖片的寬度設定得相同,這樣標題可以比較整齊。

下一個問題是,程式碼應該寫到什麼地方呢?似乎在modelform或者view裡處理圖片都可以。在這裡我打算把程式碼寫到model中去,這樣不管你在任何地方上傳圖片(包括後臺中!),圖片都會得到處理。

想好之後,就要行動了。還記得Pillow這個庫嗎,我們很早就把它安裝好了,現在是使用它的時候了:

article/models.py

...

# 記得匯入!
from PIL import Image

class ArticlePost(models.Model):
    ...
    # 前面寫好的程式碼
    avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)
    
    # 儲存時處理圖片
    def save(self, *args, **kwargs):
        # 呼叫原有的 save() 的功能
        article = super(ArticlePost, self).save(*args, **kwargs)

        # 固定寬度縮放圖片大小
        if self.avatar and not kwargs.get('update_fields'):
            image = Image.open(self.avatar)
            (x, y) = image.size
            new_x = 400
            new_y = int(new_x * (y / x))
            resized_image = image.resize((new_x, new_y), Image.ANTIALIAS)
            resized_image.save(self.avatar.path)

        return article
    
...
複製程式碼

**程式碼不多,但是有很多細節,值得仔細推敲。**不急,一行一行來:

  • save()是model內建的方法,它會在model例項每次儲存時呼叫。這裡改寫它,將處理圖片的邏輯“塞進去”。

  • super(ArticlePost, self).save(*args, **kwargs)的作用是呼叫父類中原有的save()方法,即將model中的欄位資料儲存到資料庫中。因為圖片處理是基於已經儲存的圖片的,所以這句一定要在處理圖片之前執行,否則會得到找不到原始圖片的錯誤。

  • if判斷語句的條件有兩個:

    • 博文的標題圖不是必須的,self.avatar剔除掉沒有標題圖的文章,這些文章不需要處理圖片。

    • 不太好理解的是這個not kwargs.get('update_fields')。還記得article_detail()檢視中為了統計瀏覽量而呼叫了save(update_fields=['total_views'])嗎?沒錯,就是為了排除掉統計瀏覽量呼叫的save(),免得每次使用者進入文章詳情頁面都要處理標題圖,太影響效能了。

      這種判斷方法雖然簡單,但會造成模型和檢視的緊耦合。讀者在實踐中可探索更優雅的方法,比如專門設定一個引數,用來判斷是哪類檢視呼叫了save()。

  • 接下來都是Pillow處理圖片的流程了:開啟原始圖片,取得解析度,將新圖片的寬度設定為400並根據比例縮小高度,最後用新圖片將原始圖片覆蓋掉。Image.ANTIALIAS表示縮放採用平滑濾波。

  • 最後一步,將父類save()返回的結果原封不動的返回去。

完美!

模板與測試

剩下的工作就比較簡單了。

修改發表文章的模板,讓表單能夠上傳圖片:

templates/article/create.html

...

<!-- 記得增加 enctype ! -->
<form ... enctype="multipart/form-data">
    ...

    <!-- 文章標題圖 -->
    <div class="form-group">
        <label for="avatar">標題圖</label>
        <input type="file" class="form-control-file" name="avatar" id="avatar">
    </div>
    
    ...
</form>
...
複製程式碼

然後修改文章列表模板,讓其能夠展現標題圖。

為了美觀,這裡稍微改動了列表迴圈的整體結構:

templates/article/list.html

...

<!-- 列表迴圈 -->
<div class="row mt-2">
    {% for article in articles %}
        <!-- 標題圖 -->
        {% if article.avatar %}
            <div class="col-3">
                <img src="{{ article.avatar.url }}" 
                     alt="avatar" 
                     style="max-width:100%; border-radius: 20px"
                >
            </div>
        {% endif %}

        <div class="col">
            <!-- 欄目 -->
            ...

            <!-- 標籤 -->
            ...

            ...
            
        <hr style="width: 100%;"/>
    {% endfor %}
</div>

...
複製程式碼

接下來又是喜聞樂見的測試環節。

啟動伺服器,開啟發表文章頁面:

Django搭建個人部落格:給文章加個漂亮的標題圖

選擇幾張解析度各不相同的圖片作為標題圖,

發表幾篇文章並回到文章列表頁面:

Django搭建個人部落格:給文章加個漂亮的標題圖

看起來似乎不錯。

檢視一下media目錄下實際儲存的圖片:

Django搭建個人部落格:給文章加個漂亮的標題圖

確實儲存到想要的目錄下,並且左下角顯示圖片的寬度全都為400了。

掃尾工作

功能已經實現了,但還有掃尾工作需要去做:

  • 需要對上傳的圖片做更多的驗證工作,比如上傳的檔案是否為圖片、解析度是否滿足要求。雖然在個人部落格專案中這些驗證並不是特別重要,但在其他專案中就說不好了:誰知道使用者會上傳些什麼奇奇怪怪的東西?

  • 編輯文章、刪除文章也同樣需要處理上傳的圖片。你還可以將縮放解析度的技術應用到使用者頭像上,比如裁剪成方形。

    注意:刪除資料庫中的avatar條目只是斷開了資料表和圖片的連結而已,實際上圖片還儲存在原來的位置。要徹底刪除圖片,你還得寫作業系統檔案的程式碼才行。

怎麼實現這些功能就不贅述了,留給讀者自己去折騰吧。

輪子

雖然本文是自己動手寫的程式碼(嚴格說來Pillow也是輪子),但想必你也猜到了,還有更加智慧的輪子:django-imagekit,這個庫可以直接繼承到model欄位裡面,比如這樣:

article/models.py

# 引入imagekit
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFit

class ArticlePost(models.Model):
    ...
    
    avatar = ProcessedImageField(
        upload_to='article/%Y%m%d',
        processors=[ResizeToFit(width=400)],
        format='JPEG',
        options={'quality': 100},
    )
複製程式碼

欄位中定義好了上傳位置、處理規則、儲存格式以及圖片質量,你不需要寫任何處理圖片的程式碼了。

更多的用法見官方介紹

總結

本章學習瞭如何上傳並處理文章的標題圖,從此部落格首頁就有了漂亮的外觀。

需要指出的是,個人部落格所採用的伺服器通常效能不佳,用來儲存文章縮圖等小尺寸的圖片倒還好,但是千萬不要儲存大尺寸的圖片檔案,否則使用者等待幾分鐘都刷不開你的圖片,那是很悲劇的。

因此建議你將大尺寸的圖片、視訊等放到專業的雲物件儲存服務商中,比如七牛雲又拍雲等,在你儲存量很小時(10G以內)是花不了多少錢的。


相關文章