本文将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() 来提交。