如今雖然博客的功能大都實現了,可是界面仍是比較樸素,特別是首頁的文章列表幾乎全是文字,看多了不免疲勞。所以,給每一個文章標題配一張標題圖,不只美觀,用戶也能經過圖片快速瞭解文章內容。實際上大部分社交網站也都是這麼幹的,畢竟人的天性就是懶,能看圖就堅定不看字。html
在上傳用戶頭像章節中,咱們已經接觸過上傳、展現圖片了。標題圖的實現也差很少,不一樣的是本章會更近一步,對圖片進行縮放等處理,使頁面整潔美觀、而且高效。python
與用戶頭像相似,標題圖是屬於每篇博文本身的「資產」,所以須要修改model,新建一個字段:git
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
這個目錄中。github
記得數據遷移。數據庫
標題圖一般在建立新文章的時候就設置好了,而新文章是經過表單上傳到數據庫中的。所以接下來就是修改發表文章的表單類:django
article/forms.py ... class ArticlePostForm(forms.ModelForm): class Meta: ... fields = ('title', 'body', 'tags', 'avatar')
增長了avatar
字段而已,沒有新內容。服務器
下一步就是修改視圖。由於POST的表單中包含了圖片文件,因此要將request.FILES
也一併綁定到表單類中,不然圖片沒法正確保存:post
article/views.py ... def article_create(request): if request.method == "POST": # 增長 request.FILES article_post_form = ArticlePostForm(request.POST, request.FILES) ...
很好,功能差很少已經通了,接下來就是對圖片進行處理。性能
寫代碼以前先構思一下須要進行怎樣的處理:學習
下一個問題是,代碼應該寫到什麼地方呢?彷佛在model、form或者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()。
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> ...
接下來又是喜聞樂見的測試環節。
啓動服務器,打開發表文章頁面:
選擇幾張分辨率各不相同的圖片做爲標題圖,
發表幾篇文章並回到文章列表頁面:
看起來彷佛不錯。
查看一下media目錄下實際保存的圖片:
確實保存到想要的目錄下,而且左下角顯示圖片的寬度全都爲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之內)是花不了多少錢的。