3.3 删除和修改文章

  网站上对文章的操作通常简称为“增删改查”:“增”即发布,“删”即删除,“改”即修改,“查”即查看。继上节内容,本节学习“删改”之法,如下图所示。

3.3.1 删除

  如同“栏目管理”中的功能,对文章也有删除和修改的操作。还是采用bootstrap默认的两个图标,先在./templates/article/column/article_list.html文件中的相应<td>内将该图标显示出来,代码如下。

1  <tr id={{ article.id }}>
2             <td>{{ forloop.counter }}</td>
3             <td><a href="{{ article.get_absolute_url }}">{{ article.title }}</a> </td>
4             <td>{{ article.column }}</td>
5             <span class="glyphicon glyphicon-pencil"></span>
6             <span class="glyphicon glyphicon-trash" style="margin=left:20px;"></span>
7             <td>--</td>
8         </tr>

这次换一个开发顺序,不再先写视图函数了(当然数据模型和表单类都不用写了),而是“从前向后”先写“删除”的前端功能。因此,还要继续编辑上述模板文件,在“删除”功能的代码外围加上<a>标签。

 1 {% extends "article/base.html" %}
 2 {% load staticfiles %}
 3 {% block title %}articles list{% endblock %}
 4 {% block content %}
 5 <div>
 6     <table class="table table-hover">
 7         <tr>
 8             <td>序号</td>
 9             <td>标题</td>
10             <td>栏目</td>
11             <td>操作</td>
12         </tr>
13         {% for article in articles %}
14         <tr id={{ article.id }}>
15             <td>{{ forloop.counter }}</td>
16             <td><a href="{{ article.get_absolute_url }}">{{ article.title }}</a> </td>
17             <td>{{ article.column }}</td>
18             <td>
19                 <span class="glyphicon glyphicon-pencil"></span>
20                 <a name="delete" href="javascript:" onclick="del_article(this,{{ article.id }})"></a><span class="glyphicon glyphicon-trash" style="margin-left:20px;"></span>
21             </td>
22         </tr>
23         {% endfor %}
24     </table>
25 </div>
26 
27 <script type="text/javascript" src="{% static 'js/jquery-3.3.1.js' %}"></script>
28 <script type="text/javascript" src="{% static 'js/layer.js' %}"></script>
29 <script type="text/javascript">
30     function del_article(the, article_id){
31         var article_name = $(the).parents("tr").children("td").eq(1).text();
32         layer.open({
33             type:1,
34             skin:"layui-layer-rim",
35             area:["400px","200px"],
36             title:"删除文章",
37             content:'<div class="text-center" style="margin-top:20px"><p>是否确定删除《'+article_name+'》</p></div>',
38             btn:['确定','取消'],
39             yes:function(){
40                 $.ajax({
41                     url:'{% url "article:del_article" %}'
42                     type:"POST",
43                     data:{"article_id":article_id},
44                     success:function(e){
45                         if(e=="1"){
46                             parent.location.reload();
47                             layer.msg("has been deleted.");
48                         }else{
49                             layer.msg("删除失败");
50                         }
51                     },
52                 })
53             },
54         });
55     }
56                         
57 </script>
58 
59 {% endblock %}

前端设置完成后,就要编写删除文章的视图函数了,编辑./article/views.py文件,增加del_article()函数。

 1 @login_required(login_url='/account/login/')
 2 @require_POST
 3 @csrf_exempt
 4 def del_article(request):
 5     article_id = request.POST['article_id']
 6     try:
 7         article = ArticlePost.objects.get(id=article_id)
 8         article.delete()
 9         return HttpResponse("1")
10     except:
11         return HttpResponse("2")

还要在./article/urls.py文件中对删除操作进行URL设置。

1 path('del-article/',views.del_article,name="del_article"),

现在就可以测试一下删除文章的功能,应该不会出错。

点击“确定”按钮,页面自动刷新,如下图所示。

3.3.2 修改

  在编写修改文章的功能之前,先梳理清楚实现这个功能的流程。

  1. 单击修改文章的图标,将该文章的id传给相应的视图函数。
  2. 视图函数从数据库中读取该文章id的相关内容。
  3. 将文章相关内容呈现在页面上,并且处于编辑状态,要求依然使用Markdown编辑器。
  4. 用户编辑之后,单击“提交”按钮,再次将页面中编辑的内容保存在上述id所对应的数据库记录中。

  从上述流程中可以看出,我们必须编写一个视图函数(依然在./article/views.py文件中),这个视图函数能够接受文章的id,而且只是GET和POST两种请求方式。先编写满足GET请求的部分,代码如下。

 1 @login_required(login_url='/account/login/')
 2 @csrf_exempt
 3 def redit_article(request,article_id): #①
 4     if request.method == "GET":
 5         article_columns = request.user.article_column.all()
 6         article = ArticlePost.objects.get(id=article_id)
 7         this_article_form = ArticlePostForm(initial={"title":article.title})
 8         this_article_column = article.column
 9         return render(request,"article/column/redit_article.html",
10                       {"article":article,"article_columns":article_columns,"this_article_column":this_article_column,
11                        "this_article_form":this_article_form})

重点关注语句①的参数,除熟知的request外,又增加了article_id,这就是用来接收文章id的。正式通过article_id,才能获得该文章对象。

接着在./templates/article/column/目录中创建名为redit_article.html的文件,这就是上述视图函数所指定的前端模板文件,其代码如下。

 1 {% extends "article/base.html" %}
 2 {% load staticfiles %}
 3 {% block title %}article column{% endblock %}
 4 {% block content %}
 5 <link rel="stylesheet" href="{% static 'editor/css/style.css' %}">
 6 <link rel="stylesheet" href="{% static 'editor/css/editormd.css' %">
 7 <div class="container">
 8  9     <div class="col-md-10">
10         <div style="margin-left:10px">
11             <form class="form-horizontal" action="." method="post">{% csrf_token %}
12                 <div class="row" style="margin-top:10px;">
13                     <div class="col-md-2 text-right"><span>标题:</span></div>
14                     <div class="col-md-10 text-left">{{this_article_form.title}}</div>
15                 </div>
16                 <div class="row" style="margin-top:10px;">
17                     <div class="col-md-2 text-right"><span>栏目:</span></div>
18                     <div class="col-md-10 text-left">
19                         <select id="which_column">
20                             {% for column in article_columns %}
21                                 {% if column == this_article_column.column %} #②
22                                     <option value="{{column.id}}" selected="selected">{{column.column}}</option> #③
23                                 {% else %}
24                                     <option value="{{column.id}}">{{column.column}}</option>
25                                 {% endif %}
26                             {% endfor %}
27                         </select>
28                     </div>
29                 </div>
30                 <div class="row" style="margin-top:10px;">
31                     <div class="col-md-2 text-right"><span>内容:</span></div>
32                     <div id="editormd" class="col-md-10 text-left">
33                         <!--{{article_post_form.body}}-->
34                         <textarea style="display:none;" id="id_body">
35   {{article.body}} #④
36                         </textarea>
37                     </div>
38                 </div>
39                 <div class="row">
40                     <input type="button" class="btn btn-primary btn-lg" value="发布" onclick="redit_article()">
41                 </div>
42             </form>
43         </div>
44     </div>
45 </div>
46 <script type="text/javascript" src="{% static 'js/jquery-3.3.1.js' %}"></script>
47 <script src="{% static 'editor/editormd.min.js' %}"></script>
48 <script type="text/javascript" src="{% static 'js/layer.js' %}"></script>
49 <script type="text/javascript">
50     $(function(){
51         var editor = editormd("editormd",{
52             width:"100%",
53             height:640,
54             //syncScrolling:"single",
55             path:"{% static 'editor/lib/' %}",
56         });
57     });
58 </script>
59 {% endblock %}

  因为此文航在发布时已经选定栏目了,所以在循环体内部要通过语句②判断当前文章原有的栏目名称,并将下拉菜单中的该名称确定为选定状态,即语句③。

  语句④得到文章的内容,但是要注意,前面还是不要留有空格,原因见前文叙述。

  下面配置URL,在./article/urls.py文件中增加如下内容:

1 re_path('redit-article/(?P<article_id>\d+)/$', views.redit_article, name="redit_article"),

检查Django服务是否已经启动,在浏览器的地址栏输入http://127.0.0.1:8000/article/redit-article/10/ ,最后的数字就是某篇文章的id,读者可以根据自己的项目输入其他数值,但不能省略,也不能输入不存在的id值,效果如下图所示。

现在就可以在本页面中编辑文章了。

至此,完成了编辑文章的一部分功能。但是,总不能让用户记住自己每篇文章的id,所以要再做一个单击“铅笔”图标就能够跳转到上述界面的功能,需要再次对./templates/article/column/article_list.html文件进行编辑。

前面已经给“删除”图标加上了<a>标签,实现了删除功能。现在再给“编辑”图标加上<a>标签,实现编辑功能,代码如下。

1 <a name="edit" href="{% url 'article:redit_article' article.id %}"><span
2                         class="glyphicon glyphicon-pencil"></span></a>
{% url 'article:redit_article' article.id %}这个写法较较以往的URL写法稍有不同,后面多了一个参数article.id,它对应URL中的article_id。
完成之后,刷新http://127.0.0.1:8000/article/article-list/页面,单击“编辑”图标,检查是否跳转到上述编辑文章的界面。

在万丈编辑界面单击“发布”按钮时,将数据提交给视图函数,还是用熟悉的Ajax方法。其实,在模板文件中已经有了伏笔onclick="redit_article()",在./templates/article/column/redit_article.html的尾部编写redit_article()函数,代码如下。

 1 <script type="text/javascript">
 2     function redit_article(){
 3         var title = $("#id_title").val();
 4         var column_id = $("#which_column").val();
 5         var body = $("#id_body").val();
 6         $.ajax({
 7             url:"{% url 'article:redit_article' article.id %}",
 8             type:"POST",
 9             data:{"title":title,"body":body,"column_id":column_id},
10             success:function(e){
11                 if(e=="1"){
12                     layer.msg("successful");
13                     location.href = "{% url 'article:article_list' %}";
14                 }else{
15                     layer.msg("sorry.");
16                 }
17             },
18         });
19     }
20 </script>

在POST对象地址中,使用了{% url 'article:redit_article' article.id %},这种形式的URL不仅可以用在HTML代码中,还可以用在JavaScript代码中。

前面./article/views.py中的视图函数redit_article()只编写了响应GET请求的部分,现在编写响应POST请求的部分,对此视图函数增加如下代码。

 1  else:
 2         redit_article = ArticlePost.objects.get(id=article_id)
 3         try:
 4             redit_article.column = request.user.article_column.get(id=request.POST['column_id'])
 5             redit_article.title = request.POST['title']
 6             redit_article.body = request.POST['body']
 7             redit_article.save()
 8             return HttpResponse("1")
 9         except:
10             return HttpResponse("2")

这样就将本小节开头所说的修改文章的功能都实现了。测试一下吧!从文章列表界面单击“编辑”图标,进入文章编辑界面,对文章进行修改,再单击“发布”按钮,看看是否实现了修改的目的。

3.3.3 设置分页功能

在文章标题列表中,应该再增加一个分页功能,因为当发布的文章多了之后,在同一页面中显示出所有的标题是要耗费较长时间的,所以分页是必须的。

Django也认为分页是常见的操作,所以提供了内置的分页方法,可以在视图函数./article/views.py的顶部引入分页功能用到的三个方法。

1 from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger

下面重写article_list()视图函数,代码如下。

 1 @login_required(login_url='/account/login/')
 2 def article_list(request):
 3     articles_list = ArticlePost.objects.filter(author=request.user)
 4     paginator = Paginator(articles_list,2)  #①
 5     page = request.GET.get('page') #②
 6     try:
 7         current_page = paginator.page(page) #③
 8         articles = current_page.object_list #④
 9     except PageNotAnInteger: #⑤
10         current_page = paginator.page(1)
11         articles = current_page.object_list
12     except EmptyPage: #⑥
13         current_page = paginator.page(paginator.num_pages) #⑦
14         articles = current_page.object_list
15     return render(request,"article/column/article_list.html",{"articles":articles,"page":current_page})

语句①根据所查询到的文章对象articles_list创建分页的实例对象,并且规定每页最多2个。Paginator类的初始化的完整参数列表是Paginator(object_list,per_page,orphans=0,allow_empty_first_page=True),详细代码可以参考https://docs.djangoproject.com/en/1.10/_modules/django/core/paginator/#Paginator。

语句②获得当前浏览器GET请求的参数page的值,也就是当前浏览器所请求的页码数值。在后面的前端模板中,我们会看到,通过浏览器发出的URL请求中包含page参数,即URL的样式诸如http://127.0.0.1:8000/article/article-list/?page=1,通过语句②得到URL中的参数page的值1,即当前所请求的页码是1(第1页),并赋值给page变量。

语句③中的page()是Paginator对象的一个方法,其作用在于得到指定页面内容,其参数必须是大于或等于1的整数。

语句④object_list是Page对象的属性,能够得到该页所有的对象列表。类似的属性还有Page.number(返回页码)等。

语句⑤和语句⑥捕获两个异常,一个是请求的页码数值不是整数(PageNotAnInteger),另一个是请求的页码数值为空或者在URL参数中没有page(EmptyPage)。

语句⑦paginator.num_pages返回的页数,num_pages是Paginator对象的一个属性。

重写了视图函数之后,还要再写一个专门显示分页的模板文件,创建./templates/paginator.html文件,并输入如下代码。

 1 <div class="pagination">
 2     <span class="step-links">
 3         {% if page.has_previous %}
 4         <a href="?page={{ page.previous_page_number }}">Previous</a>
 5         {% endif %}
 6         <span class="current">
 7             Page{{ page.number }} of {{ page.paginator.num_pages }}
 8         </span>
 9         {% if page.has_next %}
10         <a href="?page={{ page.next_page_number }}">Next</a>
11         {% endif %}
12     </span>
13 </div> 

在上述代码中,使用了Page对象的几个属性。

  • has_previous:判断是否有上一页。
  • previous_page_number:返回上一页的页码。
  • number:返回当前页的页码。
  • paginator.num_pages:关联Paginator对象(page.paginator),并得到其总页码数(paginator.num_pages)。
  • next_page_number:返回下一页的页码。

完成分页模板的代码编写之后,将它引入到article_list.html文件中,编辑./templates/article/column/article_list.html文件,并在表格代码<table></table>的后面加入如下内容。

1 {% include "paginator.html" %}

检查Django服务是否已经运行,为了 看到效果,请现在文章页面中多发布几篇文章,然后访问http://127.0.0.1:8000/article/article-list/,就会看到一个简明且全面的分页功能,如下图所示。

对内容维护所需要的“增删改查”功能都已经实现了。

3.3.4 知识点

 1、Markdown

在本项目中,编辑文章所用的编辑器Markdown,没有用别的编辑器。Markdown编辑器是目前最好的编辑器之一。

感谢这个编辑器的创始人Jhon Gruber。

Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,可以是普通文本内容具有一定的格式。Markdown具有一系列衍生版本,用于扩展Markdown的功能(如表格、脚注、内嵌HTML等)这些功能原始的Markdown尚不具备。而衍生版本能让Markdown转换成更多的格式,比如LaTeX、Docbook。Markdown增强版中比较有名的有Markdown Extra、MultiMarkdown、Maruku等。这些衍生版本要么基于工具,如Pandoc;要么基于网站,如GitHub和Wikipedia。他们在语法上基本兼容,但在一些渲染效果上有所不他。

关于Markdown的语法,可以查看http://wowubuntu.com/markdown/,或者访问官方网站http://daringfireball.net/projects/markdown/。

2、模型:插入和更新

SQL语句中的INSERT和UPDATE可以实现数据库表中记录的插入和更新。在ORM中,要实现这种操作,可以使用完全类似Python中创建实例或者属性赋值的方式进行。

  • 插入数据

上述代码等效于SQL语句的INSERT,最后要使用new_user实例的save()方法才能完成数据的入库操作。除这种方法外,还可以使用如下方式。

create()函数直接让数据入库。

  •  修改数据。

注意:save()函数是必须的。

从上述操作可以看出,插入和更新操作不返回QuerySet结果,这与查询操作是有显著差别的。

3、模型:删除

ORM中删除记录是方法非常简单。

语句1创建了User实例之后,语句2直接执行该实例对象的delete()方法即可将实例本身删除,即删除数据库表中的相应记录,删除后返回该记录(实例)的值。

语句3使用filter()方法查询,返回空;语句4使用get()方法,因为已经不存在该记录了,所以报错。

 


 

posted @ 2019-05-13 14:58  taoziya  阅读(554)  评论(0)    收藏  举报