urlpatterns = [ ... # 後臺管理url re_path("cn_backend/$", views.cn_backend), re_path("cn_backend/add_article/$", views.add_article), ... ]
from django.contrib.auth.decorators import login_required # 裝飾器:login_required() @login_required def cn_backend(request): """ 後臺管理頁面 :param request: :return: """ # 當前登陸人發佈過得文章列表 article_list = models.Article.objects.filter(user=request.user) return render(request, "backend/backend.html", locals()) @login_required def add_article(request): """ 後臺管理添加文章視圖函數 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") # 生成article對象 models.Article.objects.create(title=title, content=content, user=request.user) return redirect("/cn_backend/") return render(request, "backend/add_article.html")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>博客後臺管理 - 博客園</title> <!-- 引入 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> <!-- jQuery (Bootstrap 的全部 JavaScript 插件都依賴 jQuery,因此必須放在前邊) --> <script src="/static/js/jquery-3.3.1.js"></script> <!-- 引入 Bootstrap 核心 JavaScript 文件 --> <script src="/static/blog/bootstrap-3.3.7/js/bootstrap.js"></script> <!--依賴jquery--> <link rel="stylesheet" href="/static/blog/css/backend.css"> </head> <body> <div class="header"> <p class="title"> 後臺管理 <a class="info" href="/logout/">註銷</a> <span class="info"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</span> </p> </div> <div class="container"> <div class="col-md-3"> <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> 操做 </a> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> <!--添加文章url跳轉--> <p><a href="/cn_backend/add_article/">添加文章</a></p> </div> </div> </div> </div> </div> <div class="col-md-9"> <div> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">文章</a></li> <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">日記</a></li> <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">眼鏡</a> </li> <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">相冊</a> </li> </ul> <!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="home"> {% block content %} {% endblock %} </div> <div role="tabpanel" class="tab-pane" id="profile"> <img src="/static/blog/img/meinv2.jpg" alt=""> <img src="/static/blog/img/meinv3.jpg" alt=""> <img class="pull-right" src="/static/blog/img/meinv.jpg" alt=""> </div> <div role="tabpanel" class="tab-pane" id="messages"> <img width="180" height="180" src="/static/blog/img/hashiqi2.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/dogg4.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/linhaifeng.jpg" alt=""><br> <img width="180" height="180" src="/static/blog/img/dogg3.jpeg" alt=""> <img width="180" height="180" src="/static/blog/img/dogge2.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/dogg5.jpg" alt=""> </div> <div role="tabpanel" class="tab-pane" id="settings"> </div> </div> </div> </div> </div> </body> </html>
擴展模板:javascript
{% extends 'backend/base.html' %} {% block content %} <div class="article_list small"> <table class="table table-hover table-striped"> <thead> <th>標題</th> <th>評論數</th> <th>點贊數</th> <th>操做</th> <th>操做</th> </thead> <tbody> {% for article in article_list %} <tr> <td>{{ article.title }}</td> <td>{{ article.comment_count }}</td> <td>{{ article.up_count }}</td> <td><a href="">編輯</a></td> <td><a href="">刪除</a></td> </tr> {% endfor %} </tbody> </table> </div> {% endblock %}
{% extends 'backend/base.html' %} {% block content %} {# 提交form表單 #} <form action="" method="post"> {% csrf_token %} <div class="add_article"> <div class="alert-success text-center">添加文章</div> <div class="add_article_region"> <div class="title form-group"> <label for="">標題</label> <div> <input type="text" name="title"> </div> </div> <div class="content form-group"> <label for="">內容(Kindeditor編輯器,不支持拖放/粘貼上傳圖片) </label> <div> <textarea name="content" id="article_content" cols="30" rows="10"></textarea> </div> </div> <input type="submit" class="btn btn-default"> </div> </div> </form> <script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { // 找到這個標籤就會引入文本編輯器 width: "800", height: "600", resizeType: 0, uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, filePostName: "upload_img" }); }); </script> {% endblock %}
在/static/blog/css/backend.css構建模板樣式:php
* { margin: 0; padding: 0; } .header { width: 100%; height: 60px; background-color: #333333; line-height: 60px; } .header .title { color: white; font-size: 20px; margin-left: 20px; font-weight: 100; } .header .title .info { float: right; margin-right: 20px; font-size: 14px; text-decoration: none; color: white; } #accordion { margin-top: 20px; } .article_list { padding: 30px; } .alert-success { margin: 8px 0; } [name='title'] { width: 100%; } .tab-pane { margin-top: 40px; margin-left: 40px; } .tab-pane img { border-radius: 80%; margin: 20px 20px; }
kindeditor編輯器官網和文檔:http://kindeditor.net/doc.phpcss
kindeditor編輯器下載頁面: http://www.kindsoft.net/down.phphtml
(1)引入編輯器java
在頁面中添加如下腳本python
<script charset="utf-8" src="/editor/kindeditor.js"></script> <script charset="utf-8" src="/editor/lang/zh-CN.js"></script> <script> KindEditor.ready(function(K) { window.editor = K.create('#editor_id'); }); </script>
注意將下載好的文件中的kindeditor.js文件放入項目目錄中引入。在K.create()查找的標籤引入文本編輯器。jquery
(2)配置編輯器參數數據庫
經過K.create函數的第二個參數,能夠對編輯器進行配置,具體參數請參考 編輯器初始化參數 。django
width:編輯器的寬度,能夠設置px或%,比textarea輸入框樣式表寬度優先度高。json
height:編輯器的高度,只能設置px,比textarea輸入框樣式表高度優先度高。
resizeType:2或1或0,2時能夠拖動改變寬度和高度,1時只能改變高度,0時不能拖動。
uploadJson:指定上傳文件的服務器端程序。
extraFileUploadParams:上傳圖片、Flash、視音頻、文件時,支持添加別的參數一併傳到服務器。
filePostName:指定上傳文件form名稱。
在backend/add_article.html中添加編輯器:
{% extends 'backend/base.html' %} {% block content %} {# 提交form表單 #} <form action="" method="post"> ... </form>
<script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { // 找到這個標籤就會引入文本編輯器 width: "800", height: "600", resizeType: 0, uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, filePostName: "upload_img" }); }); </script> {% endblock %}
編輯器顯示效果:
(1)添加upload路由
urlpatterns = [ ... path('upload/', views.upload), ... ]
(2)直接使用編輯器的文件上傳功能,發現forbidden報錯。
這是因爲文本編輯器發送文件時使用的POST請求,被django攔截,所以須要在數據中攜帶一個csrf_token。
這裏使用extraFileUploadParams(上傳圖片、Flash、視音頻、文件時,支持添加別的參數一併傳到服務器)來實現攜帶csrf_token參數。
<script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { // 找到這個標籤就會引入文本編輯器 width: "800", height: "600", resizeType: 0, uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() } }); }); </script>
在這裏它添加的是一對鍵值,鍵是csrfmiddlewaretoken,值是取的頁面中屬性name爲csrfmiddlewaretoken對應的值。
(3)添加upload視圖函數
def upload(request): print(request.FILES) return HttpResponse("ok")
request.FILES中的鍵來自於表單中的<input type="file" name="" />的name值,request.FILES中的值均爲UploadedFile類文件對象。
上傳文件後輸出以下:
<MultiValueDict: {'imgFile': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>
(4)指定修改上傳文件的鍵
filePostName:指定上傳文件form名稱
<script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { // 找到這個標籤就會引入文本編輯器 width: "800", height: "600", resizeType: 0, uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, filePostName: "upload_img" }); }); </script>
修改後,上傳文件輸出以下:
<MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>
(1)以前將用戶上傳的頭像是存放在/media/avatars目錄下,如今再建立一個/media/add_article_img目錄來存放上傳的圖片文件。
(2)用戶上傳的文件指定到了upload視圖函數,構建upload來下載圖片到本地服務器
def upload(request): print(request.FILES) # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}> img = request.FILES.get("upload_img") # 文件對象 print(img.name) # 文件名 path = os.path.join(settings.MEDIA_ROOT, "add_article_img", img.name) with open(path, "wb") as f: for line in img: f.write(line) return HttpResponse("ok")
上傳完成後,在服務器查看下載好的圖片文件:
完成圖片上傳後,瀏覽器顯示的信息以下:
須要在文本編輯器中顯示上傳的圖片,須要繼續構建upload函數,下載完文件後把對應的路徑交給文本編輯器:
def upload(request): print(request.FILES) # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}> img = request.FILES.get("upload_img") # 文件對象 print(img.name) # 文件名 path = os.path.join(settings.MEDIA_ROOT, "add_article_img", img.name) with open(path, "wb") as f: for line in img: f.write(line) # 下載完文件後,把對應的路徑交給文本編輯器,文本編輯器構建img標籤,咱們就可以看到這個圖片了。 response = { "error": 0, "url": "/media/add_article_img/%s" % img.name, # 可以預覽這張圖片的路徑 } # 將這個字典交還給文本編輯器,作json序列化 import json return HttpResponse(json.dumps(response))
注意:
(1)顯示圖片的思路是:下載完文件後,把對應的路徑交給文本編輯器,文本編輯器構建img標籤,就可以看到這個圖片了。
構建的字典有兩對鍵值:error=0表明沒有發生任何錯誤;
url對應的路徑不是圖片存儲的絕對路徑而是可以訪問查看的路徑:127.0.0.1:8000/media/add_article_img/dog3.jpg
(2)編輯器要求返回的數據必須是json數據,所以須要將構建好的字典作json序列化後回傳
# 將這個字典交還給文本編輯器,作json序列化 import json return HttpResponse(json.dumps(response))
(3)檢查驗證:
檢查html:
@login_required def add_article(request): """ 後臺管理添加文章視圖函數 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") desc = content[0:150] # 截取文章內容做爲摘要 # 生成article對象 models.Article.objects.create(title=title, desc=desc, content=content, user=request.user) return redirect("/cn_backend/") return render(request, "backend/add_article.html")
這種狀況下,若是文章內容是純文本,摘要內容是會正常顯示的。但若是文章內容是標籤字符串,包含有各類樣式,那就會出現問題。
(1)index.html中文章摘要添加safe過濾器
若是不添加safe過濾器,那首頁顯示的文章摘要將以標籤字符串的形式展現:
添加過濾器後再也不對字符串進行轉義:
<span class="media-right"> {# 文章摘要 #} {{ article.desc|safe }} </span>
(2)添加|safe過濾器後,摘要樣式產生了錯亂
這是因爲截取的150的標籤字符串沒有閉合致使。所以不該該是截取content,而是應該截取content中的文本前150個。
HTML文件其實就是由一組尖括號構成的標籤組織起來的,每一對尖括號形式一個標籤,標籤之間存在上下關係,造成標籤樹;所以能夠說Beautiful Soup庫是解析、遍歷、維護「標籤樹」的功能庫。
安裝Beautiful Soup:
pip3 install bs4
Beautiful Soup支持Python標準庫中的HTML解析器,還支持一些第三方的解析器,這裏使用Python默認支持的解析器:html.parser。
使用方法:
from bs4 import BeautifulSoup s = "<h1>hello</h1><span>123</span>" soup = BeautifulSoup(s, "html.parser") print(soup.text) # 輸出:hello123
能夠看到輸出的是標籤內的文本。
from bs4 import BeautifulSoup @login_required def add_article(request): """ 後臺管理添加文章視圖函數 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") soup = BeautifulSoup(content, "html.parser") # 標籤字符串是content # 構建摘要數據,獲取標籤字符串的文本前150個符號 desc = soup.text[0:150]+"..." # 生成article對象 models.Article.objects.create(title=title, desc=desc, content=content, user=request.user) return redirect("/cn_backend/") return render(request, "backend/add_article.html")
再次添加文章,注意要將編輯器切換到html標籤預覽模式,再粘貼標籤字符串。
首頁摘要顯示效果:
from bs4 import BeautifulSoup s = "<h1>hello</h1><span>123</span><script>alert(123)</script>" # Python默認支持的解析器:html.parser soup = BeautifulSoup(s, "html.parser") # print(soup.text) print(soup.find_all()) # 獲取全部的標籤對象:[<h1>hello</h1>, <span>123</span>, <script>alert(123)</script>] for tag in soup.find_all(): print(tag.name) """ h1 span script """ if tag.name == "script": # 非法標籤 tag.decompose() # 從soup中刪除標籤 print(str(soup)) # <h1>hello</h1><span>123</span>
注意:
1)每一個tag都有本身的名字,經過 .name
來獲取;
2)Tag.decompose()
方法將當前節點移除文檔樹並徹底銷燬。
3)find_all( name , attrs , recursive , string , **kwargs ),find_all()
方法搜索當前tag的全部tag子節點,並判斷是否符合過濾器的條件.
from bs4 import BeautifulSoup @login_required def add_article(request): """ 後臺管理添加文章視圖函數 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") soup = BeautifulSoup(content, "html.parser") # 標籤字符串是content # 防止xss攻擊,過濾script標籤 for tag in soup.find_all(): print(tag.name) if tag.name == "script": tag.decompose() # 構建摘要數據,獲取標籤字符串的文本前150個符號 desc = soup.text[0:150]+"..." # 生成article對象 models.Article.objects.create(title=title, desc=desc, content=str(soup), user=request.user) return redirect("/cn_backend/") return render(request, "backend/add_article.html")
注意保存的content再也不是原始的content而是處理以後的標籤字符串。
建立新文章:
檢查script標籤是否清除:
數據庫查看content內容: