4.3 为文章点赞
“点赞”这个词语不知道从什么时候开始逐渐替代了“赞赏”“称赞”等词汇,下面就实现对文章点赞的功能,如下图所示。

4.3.1 修改数据模型类
编辑熟悉的ArticlePost数据模型类,它在./article/models.py文件中,在其众多字段中增加如下代码。
1 users_like = models.ManyToManyField(User,on_delete=models.CASCADE,related_name="articles_like",blank=True) #①
关注语句①中的一个新的字段类型ManyToManyField。在本书前面的章节中,已经出现过OneToOneField和ForeignKey两种字段类型。以上三个字段类型都是用于定义具有关联关系字段的,但它们之间有所差别。
很显然,一篇 文章可以被多个用户阅读并点赞,而每个用户可以给多篇文章点赞,这就是所谓的“多对多”关系。
将语句①添加到文件中后,要及时完成数据迁移,即执行python manage.py makemigrations和python manage.py migrate。然后打开Friefox浏览器,用前面使用过的SQLite Manager查看当前的数据库状态,如下图所示。


可以看到,数据库中多了一个名为article_articlepost_users_like的表(并没有像ArticlePost数据模型类中的其他属性那样,在article_articlepost表中增加一个users_like字段),这个表的字段有两个:articlepost_id和user_id。这说明语句①的作用就是建立起两个数据模型(数据库表)的对应关系。
4.3.2 编写视图函数
数据模型确定好之后,就有了保存数据的数据库表了,接着编写视图函数,完成用户的操作行为。
在./article/list_views.py文件中引入下列代码的内容。
1 from django.contrib.auth.decorators import login_required 2 from django.views.decorators.csrf import csrf_exempt 3 from django.views.decorators.http import require_POST 4 from django.http import HttpResponse
上述三个方法都是我们熟悉的装饰器,接下来编写如下代码。
1 @csrf_exempt 2 @require_POST 3 @login_required(login_url='/account/login') 4 def like_article(request): 5 article_id = require.POST.get("id") #① 6 action = require.POST.get("action") #② 7 if article_id and action: 8 try: 9 article = ArticlePost.objects.get(id=article_id) 10 if action=="like": 11 article.users_like.add(request.user) #③ 12 return HttpResponse("1") 13 else: 14 article.users_like.remove(request.user) #④ 15 return HttpResponse("2") 16 except: 17 return HttpResponse("no")
语句①和语句②得到前端以POST方式传过来的两个值,显然这里已经规定好了前端必须用类字典的形式向视图函数提交id和action的值。
语句③用于给article这个实例的属性users_like增加一个用户,即该用户“点赞”了此文章。如果不同对象之间建立了一对多或者多对多的关联关系,那么就可以使用add(*objs,bulk=True)方法增加属性的值,从而建立起两个对象的关系。例如语句③中的add()方法,就将article实例对象和user实例对象之间建立了关联,如果这一步成功执行,那么在数据库表article_articlepost_users_like中就能够看到article和user各自的id值。以上是多对多关系,如果是一对多(ForeignKey)关系,同样可以使用add()方法,在Django的官方文档中有一个举例,可以赏析(https://docs.djangoproject.com/en/1.10/ref/models/relations/#django.db.models.fields.related.RelateManager.add)。
语句④与语句③相反的操作,其使用方法和add()类似,即把相关联的对象的关系删除。
完成了视图函数的编写,就可以在./article/urls.py中配置URL了,增加如下代码。
1 path('like-article/',list_view.like_article,name="like_article"),
4.3.3 修改模板文件
不需要重新编写模板文件,只要在原来展示文章详细内容的模板中增加“点赞”相关的内容即可。./templates/article/list/article_detail.html文件的全部代码如下。
1 {% extends "base.html" %} 2 {% load staticfiles %} 3 {% block title %}{{ article.title }}{% endblock %} 4 {% block content %} 5 6 {% with total_likes=article.users_like.count users_like=article.users_like.all %} #① 7 <div class="container"> 8 <header> 9 <h1>{{ article.title }}</h1> 10 <p> 11 <a href="{% url 'article:author_articles' article.author.username %}"> 12 {{ article.author.username }} 13 </a> 14 <span style="margin-left:20px" class="glyghicon glyphicon-thumbs-up">{{ total_likes }}like{{ total_likes | pluralize }}</span> #② 15 </p> 16 </header> 17 18 <link rel="stylesheet" href='{% static "editor/css/editormd.preview.css" %}' /> 19 <div id="editormd-view"> 20 <textarea id="append-test" style="display:none;"> 21 {{ article.body }} 22 </textarea> 23 </div> 24 <div> 25 <p class="text-center"> 26 <a onclick="like_article({{article.id}}, 'like')" href="#"><span class="glyghicon glyphicon-thumbs-up">like</span></a> #③ 27 <a onclick="like_article({{article.id}}, 'unlike')" href="#"><span style="margin_left:15px;" class="glyghicon glyphicon-thumbs-down">unlike</span></a> #④ 28 </p> 29 </div> 30 <div> 31 <p class="text-center"><strong>点赞文本的读者</strong></p> 32 {% for user in article.users_like.all %} #⑤ 33 <p class="text-center">{{ user.username }}</p> 34 {% empty %} 35 <p class="text-center">还没有人对此文章表态</p> 36 {% endfor %} 37 </div> 38 39 </div> 40 <script src="{% static 'js/jquery-3.3.1.js' %}"></script> 41 <script src="{% static 'editor/lib/marked.min.js' %}"></script> 42 <script src="{% static 'editor/lib/prettify.min.js' %}"></script> 43 <script src="{% static 'editor/lib/raphael.min.js' %}"></script> 44 <script src="{% static 'editor/lib/underscore.js' %}"></script> 45 <script src="{% static 'editor/lib/sequence-diagram.min.js' %}"></script> 46 <script src="{% static 'editor/lib/flowchart.min.js' %}"></script> 47 <script src="{% static 'editor/lib/jquery.flowchart.min.js' %}"></script> 48 <script src="{% static 'editor/editormd.js' %}"></script> 49 <script type="text/javascript" src="{% static 'js/layer.js'%"></script> #⑥ 50 51 <script type="text/javascript"> 52 $(function(){ 53 editormd.markdownToHTML("editormd-view",{ 54 htmlDecode:"style,script,iframe", // you can filter decode 55 emoji:true, 56 taskList:true, 57 tex:true, //默认不解析 58 flowChart:true, //默认不解析 59 sequenceDiagram:true, //默认不解析 60 }); 61 }); 62 63 function like_article(id,action){ #⑦ 64 $.ajax({ 65 url:"{% url 'article:like_article' %}", 66 type:"POST", 67 data:{"id":id,"action":action}, 68 success:function(e){ 69 if(e=="1"){ 70 layer.msg("感谢点赞"); 71 window.location.reload(); 72 }else{ 73 layer.msg("我会继续努力"); 74 window.location.reload(); 75 } 76 }, 77 }); 78 } 79 </script> 80 {% endwith %} 81 {% endblock %}
语句①包含两部分,一部分是{% with ...%},另一部分是结尾的{% endwith %},这里使用with发起了一个赋值操作,total_likes=article.users_like.count 和users_like=article.users_like.all分别为点赞用户总数和用户对象。这样,在两部分所圈定的区间中就可以使用变量total_likes和users_like。请读者学习并掌握这种模板语法。
语句②中增加了<span style="margin-left:20px" class="glyghicon glyphicon-thumbs-up">{{ total_likes }}like{{ total_likes | pluralize }}</span>,需要解释的是{{ total_likes | pluralize }}。当没有用户“点赞”或者超过1个用户“点赞”时,显示的是likes这种复数形式;如果只有一个用户“点赞”,则显示单数形式like。这就是{{ total_likes | pluralize }}的作用。
语句③④都是新增的内容,在文章内容的末尾增加“点赞”的操作,而“点赞”功能的实现则是通过JavaScript函数(语句⑦)来实现的。提醒读者小心的是,在语句⑦中我们又使用了前面熟悉的弹出层的插件,所以要增加语句⑥。
语句⑤是另外一种模板语法的应用。对于for循环,我们已经不陌生了。如果循环对象是空(bool()的值为False),则可能在循环之前用if来判断,这里使用{% empty %},当然,不要忘记最后用{% endfor %}结束,也就是我们在这里使用了{% for ...%}...{% empty %}...{% endfor %}的语法结构。
“点赞”功能完成了,看看效果吧。
如下图所示是没有用户“点赞”的效果。

读者注意观察上图中的likes目前是复数形式,当用户“点赞”这篇文章之后(刷新),效果如下图所示。

下面看一下数据库中的存储结果,如下图所示。

数据库中记录了本次操作的数据。
读者还可以继续测试,单击“unlike”或者换一个用户再次单击“like”等。

4.3.4 知识点
1、模型:“多对多”
本节在ArticlePost类中增加了一个字段(users_like =models.ManyToManyField(User,related_name="articles_like",blank=True)),使ArticlePost和User之间建立了“多对多(ManyToMany)”关系。每篇文章可以被多个用户点赞,而每个用户可以对多篇文章点赞,这就是ArticlePost和User之间的关系,这种关系被称为“多对多”关系。她跟以前遇到的“一对多”关系一样,也是两个数据模型类之间的关系。
要将两个数据模型类建立“多对多”的关系,只需要在其中一个类中声明一个字段即可,ORM会自动为另一个类生成使用这种关系的必要的方法和属性(生成一个_set manager对象)。又因为这种“多对多”是对称关系的,所以在哪一个类中声明都可以。例如在本节中,是在ArticlePost中声明了users_like。
在Django的交互模式,可以看看这种关系的基本特点,代码如下。

article.users_like.all()查询所有对本article实例点赞的User实例(对某文章点赞的所有用户);one_user.article_like.all()查询本one_user实例所点赞的ArticlePost实例(某用户点赞过的所有文章),请注意,因为有users_like=models.ManyToManyField(User,related_name="articles_like",blank=True)中的related_name的定义,才能使用one_user.article_like.all()。
“多对多”关系建立之后,在数据库中建立了名为“article_articlepost_users_like”的数据库表,数据库表中保存的是双方的id。
2、模板:with
with是模板的一个内置标签,它的使用方法类似于赋值语句,例如本节中的实例:
{% with total_likes=article.users_like.count users_like=article.users_like.all %} <!--{{ total_likes }}相关代码--> {% endwith %}
在变量中使用article.users_like.count,其拼写太长,不方便,不如使用{{ total_like }}更方便,这就如同在Python中把一个对象赋值给一个简单的变量,更便于使用。注意,这个变量的有效范围在{% with %}和{% endwith %}之间。
3、模板:for ... empty
前面对模板的for标签已经有所介绍,现在补充介绍for...empty标签。有时候我们会遇到下面的代码(以下实例来自官网)
<ul> {% if athlete_list %} {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} {% else %} <li>Sorry,no athletes in this list.</li> {% endif %} </ul>
用if来判断某个变量是否为空,如果不为空,就循环。因为这种判断在模板中经常会遇到,所以Dajngo提供了一种名为for...empty的标签,它的使用方法是:
<ul> {% if athlete_list %} <li>{{ athlete.name }}</li> {% empty %} <li>Sorry,no athletes in this list.</li> {% endfor %} </ul>
浙公网安备 33010602011771号