如今虽然博客的功能大都实现了,可是界面仍是比较朴素,特别是首页的文章列表几乎全是文字,看多了不免疲劳。所以,给每一个文章标题配一张标题图,不只美观,用户也能经过图片快速了解文章内容。实际上大部分社交网站也都是这么干的,毕竟人的天性就是懒,能看图就坚定不看字。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()。
接下来都是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>
...
复制代码
接下来又是喜闻乐见的测试环节。
启动服务器,打开发表文章页面:
选择几张分辨率各不相同的图片做为标题图,
发表几篇文章并回到文章列表页面:
看起来彷佛不错。
查看一下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之内)是花不了多少钱的。