本文將BBS+Blog項目開發中所須要的細節知識點進行補充,其中內容包括KindEditor編輯器的使用,BeautifulSoup 模塊及其防XSS攻擊,Django中admin管理工具的使用,media配置之MEDIA_ROOT,FBV和CBV之間的關係,Django模塊之Meta選項詳解,HTML中submit和button的區別等七大內容。php
富文本編輯器,Rich Text Editor,簡稱RTE,是一種可內嵌於瀏覽器,所見即所得的文本編輯器。css
富文本編輯器不一樣於文本編輯器,程序員可導網上下載免費的富文本編輯器內嵌於本身的網站或程序裏(固然付費的更強大些),方便用戶編輯文章或信息,富文本編輯器在web開發中能夠說是不可缺乏的。咱們能夠本身集成,但在這裏推薦KindEditor。html
下載 KindEditor 最新版本,下載以後打開 examples/index.html 就能夠看到演示。前端
下載頁面: http://www.kindsoft.net/down.phppython
解壓 kindeditor-x.x.x.zip 文件,將全部文件上傳到您的網站程序目錄裏,例如:http://您的域名/editor/程序員
Noteweb
您能夠根據需求刪除如下目錄後上傳到服務器。數據庫
1,在須要顯示編輯器的位置添加 testarea 輸入框django
<textarea id="editor_id" name="content" style="width:700px;height:300px;"> <strong>HTML內容</strong> </textarea>
Notejson
2,在該HTML頁面添加如下腳本。
<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>
Note
var options = { cssPath : '/css/index.css', filterMode : true }; var editor = K.create('textarea[name="content"]', options);
// 取得HTML內容 html = editor.html(); // 同步數據後能夠直接取得textarea的value editor.sync(); html = document.getElementById('editor_id').value; // 原生API html = K('#editor_id').val(); // KindEditor Node API html = $('#editor_id').val(); // jQuery // 設置HTML內容 editor.html('HTML內容');
Note
// 關閉過濾模式,保留全部標籤 KindEditor.options.filterMode = false; KindEditor.ready(function(K)) { K.create('#editor_id'); }
編輯器中上傳的文件將保存在這裏
# 與用戶上傳相關的配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/"
from django.contrib import admin from django.urls import path, re_path from blog import views from django.views.static import serve urlpatterns = [ ...... # media配置 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), ]
咱們直接看我寫的代碼:
def upload(request): ''' 編輯器上傳文件接收視圖函數 :param request: :return: ''' print(request.FILES) img_obj = request.FILES.get('upload_img') print(img_obj.name) path = os.path.join(settings.MEDIA_ROOT, 'add_article_img', img_obj.name) with open(path, 'wb') as f: for line in img_obj: f.write(line) response = { 'error': 0, 'url': '/media/add_article_img/%s' % img_obj.name } import json return HttpResponse(json.dumps(response))
而後在url配置其路徑
from django.contrib import admin from django.urls import path, re_path from blog import views from cnblog_review import settings from django.views.static import serve urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), # media配置 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), re_path(r'^(?P<username>\w+)/articles/(?P<article_id>\d+)$', views.article_detail), # 文本編輯器上傳圖片url path('upload/', views.upload), ]
上面這些步驟富文本編輯器應該能夠正常使用了,包括上傳圖片,視頻。
XSS(Cross Site Script)攻擊又叫作跨站腳本攻擊,是爲不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫爲XSS。他的原理是用戶在使用具備XSS漏洞的網站的時候,向這個網站提交一些惡意的代碼,當用戶在訪問這個網站的某個頁面的時候,這個惡意的代碼就會被執行,從而來破壞網頁的結構,獲取用戶的隱私信息等。
博客項目中用戶後臺添加文章時,若經過富文本編輯器輸入標籤內容或者JS指令的時候,會致使文章排版錯亂,甚至進行XSS攻擊。,咱們具體一點。
script
字符是否存在,注意,這裏不建議判斷,由於隨便加幾個tab或者空格便可讓你的判斷失效!因此轉義script先後的大於小於號纔是重點,利用django很容易作到這件事,好比在html中去掉加入safe標籤(默認狀況下django是不加safe的,也就是會默認過濾轉義掉幾乎一切的違規字符),固然你也能夠直接在view中利用django.template.defaultfilters.escape()方法,來直接對傳送進來的字符串進行手動轉義,這樣當存進去數據庫的時候就不會有"<",">",取而代之的是「<」和「>」,其餘字符同理。。。下面給出了escape()默認轉義的字符,它是django內定的:
_html_escapes = { ord('&'): '&', ord('<'): '<', ord('>'): '>', ord('"'): '"', ord("'"): ''', }
將文本內容在保存數據庫以前就要進行一次篩選,去除script標籤,固然能夠去除不少其餘標籤之類的,爲了操縱簡便,這其中須要用到BS模塊。
代碼以下:
from bs4 import BeautifulSoup def add_article(request): """ 後臺管理的添加書籍視圖函數 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") # 防止xss攻擊,過濾script標籤 soup = BeautifulSoup(content, "html.parser") # soup.find_all():獲取 標籤字符串全部的標籤對象 for tag in soup.find_all(): print(tag.name) # tag.name獲取標籤名字 if tag.name == "script": # 刪除script標籤 tag.decompose() # 構建摘要數據,獲取標籤字符串的文本前150個符號 desc = soup.text[0:150]+"..." 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")
Django自帶的後臺管理是Django明顯特點之一,可讓咱們快速便捷的管理數據。後臺管理能夠在各個APP的 admin.py 文件中進行控制。Django提供了基於web 的管理工具。
Django自動管理工具是 django.contrib 的一部分。咱們能夠在項目的settings.py 的INSTALLED_APPS中看到其代碼:
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', )
django.contrib 是一套龐大的功能集,它是Django 基本代碼的組成部分。
一般咱們再生成項目時會在 urls.py 中自動設置好,咱們只須要去掉註釋便可。可是通常咱們不用管,他就設置好了。
urls.py代碼以下:
from django.conf.urls import url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), ]
啓動開發服務器,而後在瀏覽器中訪問 http://127.0.0.1:8000/admin/,獲得以下界面:
咱們能夠經過下面命令來建立超級用戶:
python manage.py createsuperuser
以後輸入用戶密碼登陸,界面以下:
爲了讓admin界面管理某個數據模型,咱們須要先註冊該數據模型到admin。
好比,咱們以前在TestModel中已經建立了模型Test,修改TestModel/admin.py:
from django.contrib import admin from TestModel.models import Test # Register your models here. admin.site.register(Test)
刷新以後,咱們能夠在後臺管理頁面看到Testmodel數據表:
管理頁面的功能強大,徹底有能力處理更加複雜的數據模型。
先在TestModel/model.py中增長一個更復雜的數據模型:
from django.db import models # Create your models here. class Test(models.Model): name = models.CharField(max_length=20) class Contact(models.Model): name = models.CharField(max_length=200) age = models.IntegerField(default=0) email = models.EmailField() def __str__(self): return self.name class Tag(models.Model): contact = models.ForeignKey(Contact) name = models.CharField(max_length=50) def __str__(self): return self.name
這裏有兩個表,Tag以Contact爲外部鍵,一個Contact能夠應對多個Tag。
咱們還能夠看到許多屬性類型:
在TestModel/admin.py 註冊多個模型並顯示:
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. admin.site.register([Test, Contact, Tag])
刷新管理頁面,顯示結果以下:
在以上管理工具,咱們就能進行復雜模型操做。
咱們能夠自定義管理頁面,來取代默認的頁面。好比上面的add 頁面。咱們想只顯示 name 和 email 部分。修改 TestModel/admin.py
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. class ContactAdmin(admin.ModelAdmin): fields = ('name', 'email') admin.site.register(Contact, ContactAdmin) admin.site.register([Test, Tag])
以上代碼定義了一個ContactAdmin類,用以說明管理頁面的顯示格式。
裏面的fields屬性定義了要顯示的字段。
因爲該類對應的是Contact數據模型,咱們在註冊的時候,須要將他們一塊兒註冊。顯示效果以下:
咱們還能夠將輸入欄分塊,每一個欄也能夠定義本身的格式。修改TestModel/admin.py爲:
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. class ContactAdmin(admin.ModelAdmin): fieldsets = ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes': ('collapse',), # CSS 'fields': ('age',), }] ) admin.site.register(Contact, ContactAdmin) admin.site.register([Test, Tag])
上面的欄目分爲了Main 和 Advance兩部分。classes說明它所在的部分的CSS格式,這裏讓Advance部分隱藏。
Advance 部分旁邊有一個Show按鈕,用於展開,展開後可點擊Hide將其隱藏,以下圖所示:
上面的Contact是Tag的外部鍵,因此有外部參考的關係。
而在默認的頁面顯示中,將二者分離開,沒法體現出二者的從屬關係。咱們可使用內聯顯示,讓Tag附加在Contact的編輯頁面上顯示。
修改TestModel/admin.py
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. class TagInline(admin.TabularInline): model = Tag class ContactAdmin(admin.ModelAdmin): inlines = [TagInline] # Inline fieldsets = ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes': ('collapse',), 'fields': ('age',), }] ) admin.site.register(Contact, ContactAdmin) admin.site.register([Test])
顯示效果以下:
列表頁的顯示
在Contact輸入數條記錄後,Contact 的列表頁看起來以下:
咱們也能夠自定義該頁面的顯示,好比在列表中顯示更多的欄目,只須要在ContactAdmin中增長list_display屬性。
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. class TagInline(admin.TabularInline): model = Tag class ContactAdmin(admin.ModelAdmin): list_display = ('name','age', 'email') # list inlines = [TagInline] # Inline fieldsets = ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes': ('collapse',), 'fields': ('age',), }] ) admin.site.register(Contact, ContactAdmin) admin.site.register([Test])
刷新頁面顯示效果以下:
搜索功能在管理大量記錄時很是有用,咱們可使用serach_fileds 爲該列表頁增長搜索欄:
from django.contrib import admin from TestModel.models import Test,Contact,Tag # Register your models here. class TagInline(admin.TabularInline): model = Tag class ContactAdmin(admin.ModelAdmin): list_display = ('name','age', 'email') # list search_fields = ('name',) inlines = [TagInline] # Inline fieldsets = ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes': ('collapse',), 'fields': ('age',), }] ) admin.site.register(Contact, ContactAdmin) admin.site.register([Test])
Django 有兩種靜態文件:
/static/ : js,css,img 這指的是服務器本身使用的文件。
/media/: 這指的是用戶上傳的文件。
這裏區分開,解耦性更好。因此media絕對有存在的意義。那他們的區別是什麼呢?
1,在項目根目錄下新建 static文件夾。
2,在settings.py中設置以下:
# 靜態文件配置部分 import os STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 這個配置是爲了經過ip地址直接訪問到靜態文件 STATICFILES_DIRS = ( os.path.join(STATIC_ROOT, 'static') )
3,在模板文件的<!DOCTYPE html> 下面(不要寫到最開頭),寫入{% load staticfiles %},引入靜態文件。
4,在模板文件中引用:{% static '<static文件夾中的目標文件路徑>' %}
1,在項目根目錄新建 media 文件夾
2,在settings.py中設置以下:
# 媒體文件配置部分 MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
3,在項目url文件中設置以下:
# 媒體文件想要經過ip地址訪問到靜態文件要作以下配置 from django.views.static import serve # 導入 from django.conf import settings url(r'^/media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT})
咱們再models裏面想上傳 models.FileField 或者 models.ImageField 字段的時候,他們裏面一般又一個:
upload_to=屬性, default=屬性;
這裏的 upload_to 咱們一般會寫 media路徑,(由於都是下載的),若是要保存到 media下面,那麼咱們寫路徑的時候等因而在 media 文件夾下建立一個新的文件夾,存在咱們文件default也能夠,可是當咱們存在別的路徑下,就要從新找路徑。
下面咱們先看視圖中這段代碼:
avatar_obj = request.FILES.get('avatar') user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj)
下面去settings中配置MEDIA路徑
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
一旦配置了MEDIA路徑,Django就會對文件對象下載到MEDIA_ROOT中avatar文件夾中(若是沒有avatar文件夾,Django會自動建立)。
瀏覽器如何能直接訪問到media中的數據。
首先配置settings.py中相關路徑
# 與用戶上傳相關的配置 MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_ROOT 表明着要上傳的路徑和你在 models中寫的上傳的路徑進行拼接造成的最終文件上傳的路徑。
MEDIA_URL 主要是映射了在前端使用media_url ,當你的 media_root 發生改變的時候不用去更改前端模板中的內容。
再配置根目錄下urls.py
固定的格式,裏面的內容不能改變。首先須要導入下面的庫,和在settings中配置的MEDIA_ROOT上傳路徑。
from django.contrib import admin from django.urls import path, include, re_path from django.views.static import serve from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('blog/', include('blog.urls')), # media配置 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}) ]
FBV(function base views)就是在視圖裏使用函數處理請求。
在以前django的學習中,咱們一直使用的是這種方式。
CBV(class base views)就是在視圖函數裏使用類處理請求。
Python是一個面向對象的變成語言,若是隻用函數來開發,有不少面向對象的優勢就錯失了(繼承,封裝,多態)。因此Django在後來加入了Class-Base-View 。可讓咱們用類寫View。這樣作的優勢主要有下面兩種:
若是咱們要寫一個處理GET方法的view,用函數寫的話是下面這樣:
from django.http import HttpResponse def my_view(requset): if requset.method == 'GET': return HttpResponse("OK")
若是使用class-base-view寫的話,就是下面這樣:
from django.http import HttpResponse from django.views import View class MyView(View): def get(self, request): return HttpResponse("OK")
Django的url是將一個請求分配給可調用的函數的,而不是一個class。針對這個問題,class-bases-views提供了一個as_view()靜態方法(也就是類方法),調用這個方法,會去建立一個類的實例,而後經過實例調用dispatch(0方法,dispatch()方法會根據request的method的不一樣調用相應的方法來處理request(如get(),post()等)。到這裏,這些方法和function-based view差很少了,要接收request,獲得一個response返回。若是方法沒有定義,會拋出HTTPResponseNotAllowed異常。
在url中,就是這麼寫:
# urls.py from django.conf.urls import url from myapp.views import MyView urlpatterns = [ url(r"^index/$", MyView.as_view()), ]
類的屬性能夠經過兩種方法設定,第一種是常見的Python方法,能夠被子類覆蓋。
from django.http import HttpResponse from django.views import View class GreetingView(View): name = 'james' def get(self, request): return HttpResponse(self.name) # you can override that in a subclass class GreetingView2(GreetingView): name = 'durant'
第二種方法,你也能夠在url中指定類的屬性:在url中設置類的屬性Python
urlpatterns = [ url(r'^index/$', GreetingView.as_view(name='harden')) ]
我以爲要理解django的class-based-view(如下簡稱CBV),首先要明白django引入CBV的目的是什麼。在django1.3以前,generic view 也就是所謂的通用視圖,使用的是function-based-view(FBV),也就是基於函數的視圖。可是python的一大重要的特性就是面向對象,而CBV更能體現python的面向對象,CBV是經過class的方法來實現視圖方法的。class相對於function,更能利用多態的特定,所以更容易從宏觀層面上將項目內的比較通用的功能抽象出來。
總之,能夠理解爲一個東西具備多種形態的特性。CBV的實現原理經過看django的源碼就很容易明白,答題急速由URL理由到這個CBV以後,經過CBV內部的dispatch方法進行分發,將get請求分發給CBV.get方法處理,將post請求分發給CBV.post方法處理。
那麼怎麼利用多態呢?CBV裏引入了mixin的概念。Mixin就是寫好了的一些基礎類,而後經過不一樣的Mixin組合成爲最終想要的類。
所以,理解CBV的基礎是理解Mixin。Django使用Mixin來重用代碼,一個View Class能夠繼承多個Mixin,可是隻能繼承一個View(包括View的子類),推薦把View寫在最右邊,多個Mixin寫在左邊。
Django模型類的Meta是一個內部類,它用於定義一些Django模型類的行爲特性。內部類Meta對於models來講,不是必須的,可是對於用戶在實際使用中具備重要的做用,有些元數據選項能給咱們極大的幫助。而可用的選項大體包含如下幾類。
這個屬性是定義當前的模型是否是一個抽象類。所謂抽象類是不會對應數據表的。通常咱們用它來概括一些公共屬性字段,而後繼承它的子類能夠繼承這些字段。
Optional.abstract 若是abstract = True ,這個model就是一個抽象基類
這個選型只在一種狀況下使用,就是你的模型再也不默認的應用程序包下的models.py文件中,這時候須要指定你這個模型是哪一個應用程序的。
Options.app_label 若是一個model定義在默認的models.py以外(例如,若是你的APP的models在myapp.models 子模塊下),你必須定義app_label 讓DJango知道他屬於哪個APP app_label = 'myapp'
db_table 是指定自定義數據庫代表的。Django有一套默認的按照必定規則生成數據模型對應的數據庫代表。
Options.db_table 定義該model在數據中的表名稱: db_table = 'music_album' 若是你想使用自定義的表名,能夠經過如下該屬性 table_name = 'my_owner_table'
Options.db_teblespace 定義這個model所使用的數據庫表空間。若是在項目的settings.py中定義那麼它會使用這個值
Options.get_latest_by 在model中指定一個DateField或者DateTimeField。這個設置讓你在使用model的 Manager上的lastest方法時,默認使用指定字段來排序
Options.managed 默認值爲True,這意味着Django可使用syncdb和reset命令來建立或移除對應 的數據庫。默認值爲True,若是你不但願這麼作,能夠把manage的值設置爲False
這個選型通常用於多對多的關係中,它指向一個關聯對象,就是說關聯對象找到這個後它是通過排序的。指定這個屬性後你會獲得一個get_xxx_order()和set_xxx_order()的方法,經過它們你能夠設置或者回去排序的對象。
這個字段是告訴Django模型對象返回的記錄結果集是按照哪一個字段排序的。這是一個字符串的元組或列表,沒有一個字符串都是一個字段和用一個可選的代表降序的'-'構成。當字段名前面沒有'-'時,將默認使用升序排列。使用'?'將會隨機排列
permissions主要是爲了在Django Admin管理模塊下使用的,若是你設置了這個屬性可讓指定的方法權限描述更清晰可讀。Django自動爲每一個設置了admin的對象建立添加,刪除和修改的權限。
permissions = (('can_deliver_pizzas','Can deliver pizzas'))
這是爲了實現代理模型使用的,若是proxy = True,表示model是其父的代理 model
unique_together這個選項用於:當你須要經過兩個字段保持惟一性時使用。好比假設你但願,一個Person的FirstName和LastName二者的組合必須是惟一的,那麼須要這樣設置:
unique_together = (("first_name", "last_name"),)
一個ManyToManyField不能包含在unique_together中。若是你須要驗證關聯到ManyToManyField字段的惟一驗證,嘗試使用signal(信號)或者明確指定through屬性。
使用Django中設定model的時候,經常會遇到這樣的需求,對一個表的幾個字段作聯合惟一索引。例如student表中name和classes兩個字段一塊兒表示一個惟一記錄。
class StudentModel(models.Model): name = models.CharField(max_length=50) classes = models.CharField(max_length=50) def __str__(self): return self.name class Meta: unique_together = ('name', 'classes',)
對應到MySQL中的SQL語句以下:
CREATE UNIQUE INDEX index_name ON tablename(field1, field2);
verbose_name的意思很簡單,就是給你的模型類起一個更可讀的名字通常定義爲中文。
Django模型中的verbose_name 咱們經常可能須要使用,好比將數據庫裏面的數據導出成 csv文件。那麼csv文件的表頭的名字能夠經過取每一個字段的verbose_name來獲取,數據能夠經過QuerySet語句來獲取,這樣製做出來的csv表就能像數據庫同樣,字段名和字段值一一對應了。
Options.verbose_name 指明一個易於理解和表述的對象名稱,單數形式: verbose_name = 'user_name' 若是這個值沒有設定,Django將會使用該model的類名的分詞形式做爲其對象的表述名 CamelCase將會轉換爲camel case
這個選項是指定,模型的複數形式是什麼,好比:
verbose_name_plural = "學校"
若是不指定Django會自動在模型名稱後加一個’s’
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 數據庫中生成的表名稱 默認 app名稱 + 下劃線 + 類名 db_table = "table_name" # 聯合索引 index_together = [ ("pub_date", "deadline"), ] # 聯合惟一索引 unique_together = (("driver", "restaurant"),) # admin中顯示的表名稱 verbose_name # verbose_name加s verbose_name_plural
submit 是button 的一個特例,也是 button 的一種,它把提交這個動做自動集成了。若是表單在點擊提交按鈕後須要用JS進行處理(包括輸入驗證)後再提交的話,一般都必須把submit改爲button,即取消其自動提交的行爲,不然,將會形成兩次的效果,對於動態網頁來講,也就是對數據庫操做兩次。或者在使用 submit 驗證時加上 return true 或者 false。
submit 和button ,二者都是以按鈕的形式展現,看起來都是按鈕,所不一樣的是type屬性和觸發響應的事件上,submit會提交表單,button不會提交表單。
submit 默認爲form提交,能夠提交表單(form)。
button則響應用戶自定義的事件,若是不指定 oncllick 等事件處理函數,它是不作任何事情。固然,button也能夠完成表單提交的工做。
input type = submit 即發送表單,按回車提交表單 input type = button 是的單純的按鈕功能,提交的是 inner TEXT
submit :特殊的button,會自動將表單的數據提交,onClick方法不加 return 會自動提交,並不會起到約束的做用,因此,使用submit時須要驗證請加 return true 或者 false 。
<input type='submit' name='Submit' value='註冊' onClick = 'return check();'> 在JS中判斷的時候,寫 return true;或者 return false
button:普通的按鈕,不會自動提交表單數據,能夠在JS中顯式提交:document.form1.submit();使用場合:一個頁面有多個提交按鈕,須要根據用戶的操做來肯定到底提交到哪一個控制器,這種狀況下,就須要在JS中判斷用戶的操做,而後根據操做給 document.form1.action 賦值而且 document.form1.submit() 來提交。