您好,登錄后才能下訂單哦!
下面要講的是基于模板語言的實現方法,完全沒有使用js。講的時候有點混亂,把其他與效果實現無關的小知識點也順帶講了一下。不過我最后做了小結。
這里講組合搜索,所以要2個搜索條件。這里用一個選項保存在內存中的type和一個保存在數據庫中的section:
# models.py 文件中的表結構
class Article(models.Model):
"""文章信息"""
title = models.CharField(verbose_name="文章標題", max_length=128)
create_time = models.DateTimeField(verbose_name="創建時間", auto_now_add=True)
author = models.ForeignKey('UserInfo', models.CASCADE, related_name='author', verbose_name="作者")
section = models.ForeignKey('Section', models.CASCADE, verbose_name="所屬板塊")
type_choices = [(1, "原創"), (2, "轉載"), (3, "翻譯")]
type = models.IntegerField(choices=type_choices, verbose_name="文章類型")
class Section(models.Model):
"""文章所屬的板塊"""
name = models.CharField(verbose_name="板塊", max_length=32)
def __str__(self):
return self.name
urls里使用捕獲參數的方法,這里的名字不能隨便取,要取一個和數據庫表的字段名一樣的名字:
path('search-<int:section>-<int:type>/', views.Search.as_view()),
因為這里字典的key就是字段名,這樣處理函數里就可以直接使用**kwargs來篩選了:
def search(request, **kwargs):
article_obj = models.Article.objects.filter(**kwargs)
這里還有個問題,一般搜索的條件會有一個全部。這里可以用0來表示全部,因為數據庫的id是從1開始的。但是這樣的話按照上面的代碼,將什么也搜索不到。這條命令可以搜索到全部的數據:
article_obj = models.Article.objects.filter(**{}) # 就是空字典,相當于就是.all()
最終寫成下面這樣來實現:
def search(request, **kwargs):
condition = {}
for k, v in kwargs.items():
if v == 0:
pass
else:
condition[k] = v
article_obj = models.Article.objects.filter(**condition).order_by('-id')
types = models.Article.type_choices
section_obj = models.Section.objects.all()
return render(request, 'search.html', {'article_obj': article_obj, 'types': types, 'section_obj': section_obj})
上面的實現的好處是,處理函數里對于搜索條件沒有寫死。urls直接和數據庫的字典名對應,之后如果要增減或者修改搜索條件,處理函數也不用做修改。
上面只解決了通過url來獲取到篩選的數據,但是首先得有url。如果是單個的篩選條件,那么一個a標簽就能解決問題:
<a href="detail-{{ row.id }}"></a>
但是對于多個篩選條件的組合搜索,另外一個值就無法動態的保留了。
獲取當前url的方法
先給url加個名字
path('detail-<int:hid>-<int:uid>.html', views.detail, name='detail'),
下面的2個方法都可以在處理函數里獲取到當前的url:
print(request.path_info)
from django.urls import reverse
url = reverse('detail', kwargs=kwargs)
print(url)
# reverse是生成url,如果傳入一個別的字典,就能動態的生成url
url = reverse('detail', kwargs={'hid': '1', 'uid': '2'})
print(url)
所以url的信息全部在kwargs里了,把這個kwargs也傳給前端:
def search(request, **kwargs):
# print(kwargs)
# print(reverse('search', args=kwargs.values()))
condition = {}
for k, v in kwargs.items():
if v == 0:
pass
else:
condition[k] = v
# print(condition)
article_obj = models.Article.objects.filter(**condition).order_by('-id')
types = models.Article.type_choices
section_obj = models.Section.objects.all()
return render(request, 'search.html', {'article_obj': article_obj, 'types': types, 'section_obj': section_obj, 'kwargs': kwargs})
上面順便講了2種生成當前url的方法。這里最后是在后端獲取到了當前url的參數,然后再返回給前端
現在后端傳來的kwargs參數,就是當前url動態的內容的,所以當前的url是這樣的:
href="/search-{{ kwargs.section }}-{{ kwargs.type }}/"
獲取到上面的這個動態的url的式子,這小段的重點也就講完了。
剩下的就是熟練運用之前掌握的只是了,前端htlm的代碼如下:
<style>
div.search-area>div {margin: 5px; font-size: large;}
div.search-area a {display: inline-block; padding: 3px 5px; border: 1px solid gray;}
div.search-area a:hover {display: inline-block; padding: 3px 5px; border: 1px solid red; text-decoration:none;}
div.search-area a.active {background-color: blue; color: white;}
</style>
<div class="container">
<div class="search-area">
<h3>搜索條件</h3>
<div>
<span>版塊:</span>
<a {% if kwargs.section == 0 %} class="active" {% endif %} href="/search-0-{{ kwargs.type }}/">全部</a>
{% for section in section_obj %}
<a {% if kwargs.section == section.id %} class="active" {% endif %} href="/search-{{ section.id }}-{{ kwargs.type }}/">{{ section.name }}</a>
{% endfor %}
</div>
<div>
<span>類型:</span>
<a {% if kwargs.type == 0 %} class="active" {% endif %} href="/search-{{ kwargs.section }}-0/">全部</a>
{% for type in types %}
<a {% if kwargs.type == type.0 %} class="active" {% endif %} href="/search-{{ kwargs.section }}-{{ type.0 }}/">{{ type.1 }}</a>
{% endfor %}
</div>
</div>
<div>
<h3>查詢結果</h3>
<div class="list-group">
{% for article in article_obj %}
<a href="/article-{{ article.id }}/" class="list-group-item">
{{ article.title }}
</a>
{% endfor %}
</div>
</div>
</div>
上面還對選中的項目加了一個樣式,同樣是判斷當前動態的url,如果url判斷后該項目是被選中的,則加上 class="active"
的樣式。
href="/search-{{ kwargs.section }}-{{ kwargs.type }}/"
,在這個動態的url上修改最后,上面的代碼比較長,看著也比較亂。可以用模板語言的自定義函數封裝一下,這樣前端只需要寫一行就好了,而更加復雜的邏輯則放到 templatetags/*.py
自定義的模板函數里來實現。課上是這么做了,不過我
JSONP是一種請求方式,解決瀏覽器的同源策略阻止跨域請求的問題。
準備里了可以跳過,這里通過后端轉發請求,瀏覽器端不存在跨域的問題。但是這樣多了一個中間環節。
這里需要用到requests模塊,所以先安裝一下(或者不要裝了,直接看下面用瀏覽器直接發請求會報錯的情況):
pip install requests
然后去網上找一個api接口來請求,比如天氣api的接口:http://www.weather.com.cn/data/sk/101020100.html
如下寫一個處理函數:
import requests
def get_res(request):
response = requests.get('http://www.weather.com.cn/data/sk/101020100.html') # 發起get請求
# print(response.content) # 返回的二進制內容
response.encoding = 'utf-8' # 設置編碼格式,否則中文會是亂碼
print(response.text) # 返回的文本內容
return render(request, 'demo/jsonp.html', {'res': response.text})
然后記得配好urls.py的對應關系,開啟服務,頁面獲取一下內容:
<div>
{{ res }}
</div>
這樣,頁面請求后有返回的內容的。但是上面的請求過程是前端往后端發請求,然后后端再去找api接口請求,把api接口返回的結果再返回給前端。但是前端也是可以直接給api接口發請求的,而不用經過后端的中轉。
直接從瀏覽器發請求,就會出現跨域的問題了。下面先來觸發這個問題。
直接修改前端代碼:
<h3>后臺獲取的結果:</h3>
<p>{{ res }}</p>
<h3>js直接獲取結果</h3>
<input type="button" value="獲取結果" onclick="getContent();" />
<p id="container"></p>
<script>
function getContent() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.weather.com.cn/data/sk/101020100.html');
xhr.onreadystatechange = function () {
console.log(xhr.responseText); // 這里不能alert看結果
};
xhr.send();
}
</script>
打開后臺,查看控制臺的信息,就是下面這句報錯信息:
SEC7120: [CORS] 原點“http://127.0.0.1:8000”未在“http://www.weather.com.cn/data/sk/101020100.html”的 cross-origin 資源的 Access-Control-Allow-Origin response header 中找到“http://127.0.0.1:8000”。
這里的情況是,數據已經發出了,并且服務器也處理并返回了。報錯的信息是由于瀏覽器的同源策略,拒絕接收。
上面是會有出現問題的場景,現在本地來重現一下跨域的場景。
處理函數很簡單:
def jsonp(request):
return HttpResponse('OK')
全端頁面只需要把請求的url參數修改一下:
xhr.open('GET', 'http://127.0.0.1:8000/demo/jsonp/');
如果用默認的 127.0.0.1:8000
這個本地域名訪問,是不跨域的。用這個地址 localhost:8000
來訪問,也是訪問本地,然后再向 http://127.0.0.1:8000
發請求,就被認為跨域了。
另外還有一個方法,去settiongs.py里修改設置一下下面這個參數:
ALLOWED_HOSTS = []
這里提一下,就不展開了。
瀏覽器有同源策略,但是其實并不是所有的請求都會被同源策略阻止。比如:
CDN: <script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
圖片: <img src="https://cache.yisu.com/upload/information/20200310/57/122381.jpg">
可能是所有的有src屬性的標簽,都不受同源策略的影響。
這里就要通過script標簽來繞過瀏覽器的同源策略,把前端的按鈕事件綁定到下面這個新的函數上:
<script>
function getJSONP() {
var tag = document.createElement('script');
tag.src = 'http://127.0.0.1:8000/demo/jsonp/';
document.head.appendChild(tag)
}
</script>
上面這個函數的效果是,創建一個script標簽,設置了src后,追加到head標簽里。瀏覽器處理的時候,就會添加這個script標簽,并且會去src的地址獲取內容,并且由于這是一個script標簽,所以獲取到的內容,瀏覽器更當做js語句來處理。這里由于獲取到的是 return HttpResponse('OK')
,js語法錯誤,所以還是會有個錯誤信息。修改一下處理函數,返回一句js語句看看:
def jsonp(request):
return HttpResponse("alert('OK');")
然后現在再看看效果,點擊按鈕后,會解析并執行返回的 alert('OK');
這句js語句。
現在修改一下處理函數,返回一個復雜一點的JSON字符串,并且使用一個自定義個函數名,字符串作為函數的參數:
import json
def jsonp(request):
res = {'status': True, 'data': 'Test123'}
return HttpResponse("callback(%s);" % json.dumps(res))
然后前端也要定義好這個自定義的js函數:
<input type="button" value="獲取結果" onclick="getJSONP();" />
<script>
function getJSONP() {
var tag = document.createElement('script');
tag.src = 'http://127.0.0.1:8000/demo/jsonp/';
document.head.appendChild(tag)
}
function callback(arg) {
alert(JSON.stringify(arg))
}
</script>
現在的效果就是,前端通過script標簽,跨域接收到了一個callback函數調用的命令,并且參數就是我們需要的數據。自己通過在頁面里定義這個callback函數,就可以獲取到返回的數據了。如此成功的繞開了瀏覽器的同源策略,實現了跨域請求。
上面還有2個問題:
先把處理函數修改一下解決第一個問題:
import json
def jsonp(request):
func = request.GET.get('callback', 'callback')
res = {'status': True, 'data': 'Test123'}
return HttpResponse("%s(%s);" % (func, json.dumps(res)))
現在發送get請求的時候可以通過callback參數來指定需要函數的回調函數的函數名。之前的前端不用修改,依然可以使用。
一般約定這個指定返回的函數的函數名的key就是callback
然后修改前端,這次回調函數換一個名字試試。另外還要解決第二個問題,就是獲取回復數據之后,把之前生成的script標簽移除掉:
<input type="button" value="獲取結果" onclick="getJSONP();" />
<script>
function getJSONP() {
var tag = document.createElement('script');
tag.src = 'http://127.0.0.1:8000/demo/jsonp/?callback=myJSONP'; // get請求加一個callback參數
document.head.appendChild(tag);
document.head.removeChild(tag); // 移除創建的標簽
}
function myJSONP(arg) {
alert(JSON.stringify(arg))
}
</script>
JSONP只能發get請求。使用jQuery的話,就算指定method是POST,jQuery內部也是轉成GET處理的。
這里主要看一下jQuery的用法。基本上使用了jQuery之后,和發送普通的AJAX請求形式差不多:
<input type="button" value="獲取結果" onclick="jqJSONP();" />
<script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"></script>
<script>
function jqJSONP() {
$.ajax({
url: 'http://127.0.0.1:8000/demo/jsonp/',
type: 'POST', // 沒用,因為發的還是GET
dataType: 'jsonp', // 指定使用jsonp來發送這個請求
jsonp: 'callback', // 就是指定回調函數的參數的key
jsonpCallback: 'myJSONP' // 指定回調函數的函數名,和上面的和起來就是 ?callback=myJSONP
})
}
function myJSONP(arg) {
alert(JSON.stringify(arg))
}
</script>
解決跨域的問題,除了上面的JSONP,還有這個CORS。
講師的博客:https://www.cnblogs.com/wupeiqi/p/5703697.html
在最后有介紹,課上沒展開講。
XSS×××是通過對網頁注入可執行代碼且成功地被瀏覽器執行,達到×××的目的。這里主要講針對富文本編輯器的情況。
在使用富文本編輯器的時候,尤其要注意XSS×××。因為別的地方還可以過濾html標簽,但是富文本編輯器本身就要使用html標簽,如果全部過濾掉,就無法正常顯示文檔格式了。
防范的手段就是把特定的標簽過濾掉,比如script標簽。最安全的做法就是設置白名單,留著編輯器使用的標簽,其他的全部過濾。編輯器可能會自帶過濾,不過前端XSS過濾都會被繞過,只有在后端過濾才能萬無一失。
通用的手段就是,在收到數據提交之后進行過濾,然后把過濾后的數據保存到數據庫。保存后的數據就認為是安全的,之后頁面顯示的時候,就一律放行。
過濾標簽的方法當然可以通過正則匹配來實現。不過這里推薦一個模塊,beatifulsoup4。安裝模塊:
pip install beautifulsoup4
另外這個模塊貌似也是爬蟲利器,都是要處理html標簽嘛。
下面是BeautifulSoup的基本用法,使用find()方法找到指定的標簽,然后清除掉:
content = """
<h2>測試頁面</h2>
<p class="c1">
第一個段落<span class="color" >這里是紅色的</span>
<script>alert('p1');</script>
</p>
<p class="c2 p2" id="i2">
第二個段落<strong id="click" onclick="alert('p2');">點我看看</strong>
</p>
<p class="c3" id="i3">
第三個段落
<script>alert('p3');</script>
</p>
"""
from bs4 import BeautifulSoup
# 下面第二個參數是指定解析器,這個是python標準庫內置的。也支持其他第三方的解析器(需安裝)
soup = BeautifulSoup(content, "html.parser")
tag = soup.find('script') # 查找第一個標簽
while tag: # 這個循環應該是能把所有的標簽都查找出來了
print(tag)
# tag.hidden = True # 去掉注釋,可以把整個空標簽也去掉,否則就是去掉標簽的內容,保留標簽
tag.clear() # 清空標簽里的內容
tag = tag.find_next('script') # 查找后一個標簽
content = soup.decode() # 轉成字符串
print(type(content), content)
HTML解析器,這里用了python自帶的,就不用另外安裝了。也有其他第三方更好的,但是需要安裝,就看怎么取舍了。
如歌直接打印soup,print(soup)
,顯示的效果也是一樣的。但是soup本身是 <class 'bs4.BeautifulSoup'>
,直接打印這個對象的時候,內部調用的也是return self.encode()
。
還是上面的html,進一步處理以下標簽中的屬性
from bs4 import BeautifulSoup
# 下面第二個參數是指定解析器,這個是python標準庫內置的。也支持其他第三方的解析器(需安裝)
soup = BeautifulSoup(content, "html.parser")
tag = soup.find('script') # 查找第一個標簽
while tag: # 這個循環應該是能把所有的標簽都查找出來了
print(tag)
tag.hidden = True
tag.clear() # 清空標簽里的內容
tag = tag.find_next('script') # 查找后一個標簽
span = soup.find('span')
print(span.attrs) # 打印這個標簽的所有的屬性
del span.attrs['style'] # 刪除特定的屬性
strong = soup.find('strong')
print(strong.attrs)
del strong.attrs # 刪除所有屬性
content = soup.decode() # 轉成字符串
print(content, type(content), type(soup))
這次設置一個白名單,只保留白名單中的標簽的內容:
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, "html.parser")
tags = ['p', 'span', 'strong'] # 設置一個白名單,下面只保留白名單的里的標簽內容
# 下面的這個循環,遍歷一遍所有的標簽
for tag in soup.find_all():
if tag.name not in tags:
tag.hidden = True
tag.clear()
content = soup.decode() # 轉成字符串
print(content)
包含標簽屬性的白名單。上面的做法,只處理了標簽,沒有處理標簽中的屬性。這里需要一個更加復雜的白名單:
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, "html.parser")
tags = {
'p': ('class', 'id'), # 只允許class 和 id 這2個屬性
'span': ('class',),
'strong': (), # 值允許標簽,不能帶任何屬性
}
# 下面的這個循環,遍歷一遍所有的標簽
for tag in soup.find_all():
if tag.name not in tags:
tag.hidden = True
tag.clear()
else: # 處理白名單的屬性,再遍歷一遍標簽的屬性
# 下面的list()相當于再復制了一份列表,然后遍歷這個列表。防止下面在迭代過程中禁止把迭代的元素刪除
for attr in list(tag.attrs):
if attr not in tags[tag.name]:
del tag.attrs[attr]
content = soup.decode() # 轉成字符串
print(content)
一個類,每次實例化都會生成一個對象:
class Foo(object):
def __init__(self):
pass
c1 = Foo()
c2 = Foo()
print(c1, c2)
# 結果如下:
# <__main__.Foo object at 0x0000018AAF5C8A20> <__main__.Foo object at 0x0000018AAF76A2B0>
上面的情況,生成了2個對象,每個對象分別占用各自的內存空間。
下面自定義了一個方法,用這個方法生成對象時候,只有對一次會創建實例,之后用的都是第一次的對象:
class Foo(object):
__instance = None
def __init__(self):
pass
@classmethod
def get_instance(cls):
if not Foo.__instance:
Foo.__instance = Foo()
return Foo.__instance
def process(self):
return 'Foo.process'
c1 = Foo.get_instance()
c2 = Foo.get_instance()
print(c1.process(), c2.process())
print(c1, c2)
# 結果如下:
# Foo.process Foo.process
# <bound method Foo.process of <__main__.Foo object at 0x0000022276B0A320>> <bound method Foo.process of <__main__.Foo object at 0x0000022276B0A320>>
為了更加直觀的說明問題,我這個類里還定義了一個process方法,返回的結果也是不變的。所以這種情況下,這個類不需要多個實例,因為每個實例返回的結果都是一樣的。也就是說,這種類,只需要一個實例,即只有在第一次實例化的時候需要創建對象,之后每次都只需要用之前創建的對象就好了,不用另外再創建對象了。
最LOW的做法大概就是,自己再實例化這個類后,把創建的對象保存下來,之后不要再進行實例化操作了。上面的例子中使用了特定的方法來進行實例化,之后再第一次實例化的時候才會創建對象。從打印的結果來看,c1 和 c2 的內存地址是一樣的。
上面的例子算是實現效果,但是改變了調用的方法。并且依然是可以用標準的方法來創建不同的對象的。下面的例子通過定義new方法,實現了真正的單例模式:
class Foo(object):
__instance = None
def __init__(self):
pass
# 單例模式,就是處在類里加上這個new方法和上面的__instance靜態屬性
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance = object.__new__(cls, *args, **kwargs)
return cls.__instance
def process(self):
return 'Foo.process'
c1 = Foo()
c2 = Foo()
print(c1.process(), c2.process())
print(c1, c2)
# 結果如下:
# Foo.process Foo.process
# <__main__.Foo object at 0x000001DF3132A2E8> <__main__.Foo object at 0x000001DF3132A2E8>
上面如果注釋掉new方法,process方法返回的結果是一樣的,但是每個對象占用的內存就是不同的了(浪費資源)。如果一個類,它的每個對象里封裝的內容都是一樣的,就可以使用單例模式。
所以實現了單例模式后,調用類中的方法可以實例化之后直接調用方法或屬性:
res = Foo().process()
Django提供了單獨API來控制事務:
atomic(using=None, savepoint=True)[source]
原子性是數據庫事務的一個屬性。使用atomic,我們就可以創建一個具備原子性的代碼塊。一旦代碼塊正常運行完畢,所有的修改會被提交到數據庫。反之,如果有異常,更改會被回滾。
被atomic管理起來的代碼塊還可以內嵌到方法中。這樣的話,即便內部代碼塊正常運行,如果外部代碼塊拋出異常的話,它也沒有辦法把它的修改提交到數據庫中。
一般還是用下面例子中的方法來使用把。
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。