溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python自動(dòng)化開(kāi)發(fā)學(xué)習(xí)25-Django

發(fā)布時(shí)間:2020-07-18 05:13:37 來(lái)源:網(wǎng)絡(luò) 閱讀:3179 作者:騎士救兵 欄目:編程語(yǔ)言

組合搜索

下面要講的是基于模板語(yǔ)言的實(shí)現(xiàn)方法,完全沒(méi)有使用js。講的時(shí)候有點(diǎn)混亂,把其他與效果實(shí)現(xiàn)無(wú)關(guān)的小知識(shí)點(diǎn)也順帶講了一下。不過(guò)我最后做了小結(jié)。

準(zhǔn)備表結(jié)構(gòu)

這里講組合搜索,所以要2個(gè)搜索條件。這里用一個(gè)選項(xiàng)保存在內(nèi)存中的type和一個(gè)保存在數(shù)據(jù)庫(kù)中的section:

# models.py 文件中的表結(jié)構(gòu)
class Article(models.Model):
    """文章信息"""
    title = models.CharField(verbose_name="文章標(biāo)題", max_length=128)
    create_time = models.DateTimeField(verbose_name="創(chuàng)建時(shí)間", 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, "原創(chuàng)"), (2, "轉(zhuǎn)載"), (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

動(dòng)態(tài)的根據(jù)url處理篩選

urls里使用捕獲參數(shù)的方法,這里的名字不能隨便取,要取一個(gè)和數(shù)據(jù)庫(kù)表的字段名一樣的名字:

path('search-<int:section>-<int:type>/', views.Search.as_view()),

因?yàn)檫@里字典的key就是字段名,這樣處理函數(shù)里就可以直接使用**kwargs來(lái)篩選了:

def search(request, **kwargs):
    article_obj = models.Article.objects.filter(**kwargs)

這里還有個(gè)問(wèn)題,一般搜索的條件會(huì)有一個(gè)全部。這里可以用0來(lái)表示全部,因?yàn)閿?shù)據(jù)庫(kù)的id是從1開(kāi)始的。但是這樣的話按照上面的代碼,將什么也搜索不到。這條命令可以搜索到全部的數(shù)據(jù):

article_obj = models.Article.objects.filter(**{})  # 就是空字典,相當(dāng)于就是.all()

最終寫(xiě)成下面這樣來(lái)實(shí)現(xiàn):

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})

上面的實(shí)現(xiàn)的好處是,處理函數(shù)里對(duì)于搜索條件沒(méi)有寫(xiě)死。urls直接和數(shù)據(jù)庫(kù)的字典名對(duì)應(yīng),之后如果要增減或者修改搜索條件,處理函數(shù)也不用做修改。

生成url的方法

上面只解決了通過(guò)url來(lái)獲取到篩選的數(shù)據(jù),但是首先得有url。如果是單個(gè)的篩選條件,那么一個(gè)a標(biāo)簽就能解決問(wèn)題:

<a href="detail-{{ row.id }}"></a>

但是對(duì)于多個(gè)篩選條件的組合搜索,另外一個(gè)值就無(wú)法動(dòng)態(tài)的保留了。
獲取當(dāng)前url的方法
先給url加個(gè)名字

path('detail-<int:hid>-<int:uid>.html', views.detail, name='detail'),

下面的2個(gè)方法都可以在處理函數(shù)里獲取到當(dāng)前的url:

print(request.path_info)
from django.urls import reverse
url = reverse('detail', kwargs=kwargs)
print(url)
# reverse是生成url,如果傳入一個(gè)別的字典,就能動(dòng)態(tài)的生成url
url = reverse('detail', kwargs={'hid': '1', 'uid': '2'})
print(url)

所以u(píng)rl的信息全部在kwargs里了,把這個(gè)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種生成當(dāng)前url的方法。這里最后是在后端獲取到了當(dāng)前url的參數(shù),然后再返回給前端

在前端用模板語(yǔ)言實(shí)現(xiàn)

現(xiàn)在后端傳來(lái)的kwargs參數(shù),就是當(dāng)前url動(dòng)態(tài)的內(nèi)容的,所以當(dāng)前的url是這樣的:

href="/search-{{ kwargs.section }}-{{ kwargs.type }}/"

獲取到上面的這個(gè)動(dòng)態(tài)的url的式子,這小段的重點(diǎn)也就講完了。
剩下的就是熟練運(yùn)用之前掌握的只是了,前端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>查詢結(jié)果</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>

上面還對(duì)選中的項(xiàng)目加了一個(gè)樣式,同樣是判斷當(dāng)前動(dòng)態(tài)的url,如果url判斷后該項(xiàng)目是被選中的,則加上 class="active" 的樣式。

小結(jié)

  • 在 urls.py 里,路由的捕獲參數(shù)不能隨便寫(xiě),最好是和表的字段名一致(這樣之后都是直接引用,不用修改變量名了)
  • 后端處理函數(shù)里要寫(xiě)一個(gè)for循環(huán),處理一下選擇全部傳入?yún)?shù)是0的問(wèn)題。
  • 把kwargs這個(gè)url的參數(shù)也return給前端處理
  • href="/search-{{ kwargs.section }}-{{ kwargs.type }}/",在這個(gè)動(dòng)態(tài)的url上修改

最后,上面的代碼比較長(zhǎng),看著也比較亂??梢杂媚0逭Z(yǔ)言的自定義函數(shù)封裝一下,這樣前端只需要寫(xiě)一行就好了,而更加復(fù)雜的邏輯則放到 templatetags/*.py 自定義的模板函數(shù)里來(lái)實(shí)現(xiàn)。課上是這么做了,不過(guò)我

JSONP

JSONP是一種請(qǐng)求方式,解決瀏覽器的同源策略阻止跨域請(qǐng)求的問(wèn)題。

準(zhǔn)備

準(zhǔn)備里了可以跳過(guò),這里通過(guò)后端轉(zhuǎn)發(fā)請(qǐng)求,瀏覽器端不存在跨域的問(wèn)題。但是這樣多了一個(gè)中間環(huán)節(jié)。
這里需要用到requests模塊,所以先安裝一下(或者不要裝了,直接看下面用瀏覽器直接發(fā)請(qǐng)求會(huì)報(bào)錯(cuò)的情況):

pip install requests

然后去網(wǎng)上找一個(gè)api接口來(lái)請(qǐng)求,比如天氣api的接口:http://www.weather.com.cn/data/sk/101020100.html
如下寫(xiě)一個(gè)處理函數(shù):

import requests
def get_res(request):
    response = requests.get('http://www.weather.com.cn/data/sk/101020100.html')  # 發(fā)起get請(qǐng)求
    # print(response.content)  # 返回的二進(jìn)制內(nèi)容
    response.encoding = 'utf-8'  # 設(shè)置編碼格式,否則中文會(huì)是亂碼
    print(response.text)  # 返回的文本內(nèi)容
    return render(request, 'demo/jsonp.html', {'res': response.text})

然后記得配好urls.py的對(duì)應(yīng)關(guān)系,開(kāi)啟服務(wù),頁(yè)面獲取一下內(nèi)容:

<div>
    {{ res }}
</div>

這樣,頁(yè)面請(qǐng)求后有返回的內(nèi)容的。但是上面的請(qǐng)求過(guò)程是前端往后端發(fā)請(qǐng)求,然后后端再去找api接口請(qǐng)求,把a(bǔ)pi接口返回的結(jié)果再返回給前端。但是前端也是可以直接給api接口發(fā)請(qǐng)求的,而不用經(jīng)過(guò)后端的中轉(zhuǎn)。

直接使用瀏覽器發(fā)請(qǐng)求

直接從瀏覽器發(fā)請(qǐng)求,就會(huì)出現(xiàn)跨域的問(wèn)題了。下面先來(lái)觸發(fā)這個(gè)問(wèn)題。
直接修改前端代碼:

<h3>后臺(tái)獲取的結(jié)果:</h3>
<p>{{ res }}</p>
<h3>js直接獲取結(jié)果</h3>
<input type="button" value="獲取結(jié)果" 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看結(jié)果
        };
        xhr.send();
    }
</script>

打開(kāi)后臺(tái),查看控制臺(tái)的信息,就是下面這句報(bào)錯(cuò)信息:

SEC7120: [CORS] 原點(diǎn)“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”。

這里的情況是,數(shù)據(jù)已經(jīng)發(fā)出了,并且服務(wù)器也處理并返回了。報(bào)錯(cuò)的信息是由于瀏覽器的同源策略,拒絕接收。

本地重現(xiàn)跨域的問(wèn)題

上面是會(huì)有出現(xiàn)問(wèn)題的場(chǎng)景,現(xiàn)在本地來(lái)重現(xiàn)一下跨域的場(chǎng)景。
處理函數(shù)很簡(jiǎn)單:

def jsonp(request):
    return HttpResponse('OK')

全端頁(yè)面只需要把請(qǐng)求的url參數(shù)修改一下:

        xhr.open('GET', 'http://127.0.0.1:8000/demo/jsonp/');

如果用默認(rèn)的 127.0.0.1:8000 這個(gè)本地域名訪問(wèn),是不跨域的。用這個(gè)地址 localhost:8000 來(lái)訪問(wèn),也是訪問(wèn)本地,然后再向 http://127.0.0.1:8000 發(fā)請(qǐng)求,就被認(rèn)為跨域了。
另外還有一個(gè)方法,去settiongs.py里修改設(shè)置一下下面這個(gè)參數(shù):

ALLOWED_HOSTS = []

這里提一下,就不展開(kāi)了。

通過(guò)JSONP支持跨域

瀏覽器有同源策略,但是其實(shí)并不是所有的請(qǐng)求都會(huì)被同源策略阻止。比如:
CDN&lt;script src="http://lib.sinaapp.com/js/jquery/1.12.4/jquery-1.12.4.min.js"&gt;&lt;/script&gt;
圖片: &lt;img src="https://cache.yisu.com/upload/information/20200310/57/122381.jpg"&gt;
可能是所有的有src屬性的標(biāo)簽,都不受同源策略的影響。
這里就要通過(guò)script標(biāo)簽來(lái)繞過(guò)瀏覽器的同源策略,把前端的按鈕事件綁定到下面這個(gè)新的函數(shù)上:

<script>
    function getJSONP() {
        var tag = document.createElement('script');
        tag.src = 'http://127.0.0.1:8000/demo/jsonp/';
        document.head.appendChild(tag)
    }
</script>

上面這個(gè)函數(shù)的效果是,創(chuàng)建一個(gè)script標(biāo)簽,設(shè)置了src后,追加到head標(biāo)簽里。瀏覽器處理的時(shí)候,就會(huì)添加這個(gè)script標(biāo)簽,并且會(huì)去src的地址獲取內(nèi)容,并且由于這是一個(gè)script標(biāo)簽,所以獲取到的內(nèi)容,瀏覽器更當(dāng)做js語(yǔ)句來(lái)處理。這里由于獲取到的是 return HttpResponse('OK') ,js語(yǔ)法錯(cuò)誤,所以還是會(huì)有個(gè)錯(cuò)誤信息。修改一下處理函數(shù),返回一句js語(yǔ)句看看:

def jsonp(request):
    return HttpResponse("alert('OK');")

然后現(xiàn)在再看看效果,點(diǎn)擊按鈕后,會(huì)解析并執(zhí)行返回的 alert('OK'); 這句js語(yǔ)句。
現(xiàn)在修改一下處理函數(shù),返回一個(gè)復(fù)雜一點(diǎn)的JSON字符串,并且使用一個(gè)自定義個(gè)函數(shù)名,字符串作為函數(shù)的參數(shù):

import json
def jsonp(request):
    res = {'status': True, 'data': 'Test123'}
    return HttpResponse("callback(%s);" % json.dumps(res))

然后前端也要定義好這個(gè)自定義的js函數(shù):

<input type="button" value="獲取結(jié)果" 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>

現(xiàn)在的效果就是,前端通過(guò)script標(biāo)簽,跨域接收到了一個(gè)callback函數(shù)調(diào)用的命令,并且參數(shù)就是我們需要的數(shù)據(jù)。自己通過(guò)在頁(yè)面里定義這個(gè)callback函數(shù),就可以獲取到返回的數(shù)據(jù)了。如此成功的繞開(kāi)了瀏覽器的同源策略,實(shí)現(xiàn)了跨域請(qǐng)求。

繼續(xù)優(yōu)化JSONP

上面還有2個(gè)問(wèn)題:

  • 回調(diào)函數(shù)的函數(shù)名寫(xiě)死了,可能會(huì)和本地的函數(shù)名重名
  • 每請(qǐng)求一次,都會(huì)生成一個(gè)script標(biāo)簽

先把處理函數(shù)修改一下解決第一個(gè)問(wèn)題:

import json
def jsonp(request):
    func = request.GET.get('callback', 'callback')
    res = {'status': True, 'data': 'Test123'}
    return HttpResponse("%s(%s);" % (func, json.dumps(res)))

現(xiàn)在發(fā)送get請(qǐng)求的時(shí)候可以通過(guò)callback參數(shù)來(lái)指定需要函數(shù)的回調(diào)函數(shù)的函數(shù)名。之前的前端不用修改,依然可以使用。
一般約定這個(gè)指定返回的函數(shù)的函數(shù)名的key就是callback
然后修改前端,這次回調(diào)函數(shù)換一個(gè)名字試試。另外還要解決第二個(gè)問(wèn)題,就是獲取回復(fù)數(shù)據(jù)之后,把之前生成的script標(biāo)簽移除掉:

<input type="button" value="獲取結(jié)果" onclick="getJSONP();" />
<script>
    function getJSONP() {
        var tag = document.createElement('script');
        tag.src = 'http://127.0.0.1:8000/demo/jsonp/?callback=myJSONP';  // get請(qǐng)求加一個(gè)callback參數(shù)
        document.head.appendChild(tag);
        document.head.removeChild(tag);  // 移除創(chuàng)建的標(biāo)簽
    }
    function myJSONP(arg) {
        alert(JSON.stringify(arg))
    }
</script>

JSONP只能發(fā)get請(qǐng)求。使用jQuery的話,就算指定method是POST,jQuery內(nèi)部也是轉(zhuǎn)成GET處理的。

jQuery發(fā)送JSONP

這里主要看一下jQuery的用法?;旧鲜褂昧薺Query之后,和發(fā)送普通的AJAX請(qǐng)求形式差不多:

<input type="button" value="獲取結(jié)果" 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',  // 沒(méi)用,因?yàn)榘l(fā)的還是GET
            dataType: 'jsonp',  // 指定使用jsonp來(lái)發(fā)送這個(gè)請(qǐng)求
            jsonp: 'callback',  // 就是指定回調(diào)函數(shù)的參數(shù)的key
            jsonpCallback: 'myJSONP'  // 指定回調(diào)函數(shù)的函數(shù)名,和上面的和起來(lái)就是 ?callback=myJSONP
        })
    }
    function myJSONP(arg) {
        alert(JSON.stringify(arg))
    }
</script>

CORS(跨站資源共享)

解決跨域的問(wèn)題,除了上面的JSONP,還有這個(gè)CORS。
講師的博客:https://www.cnblogs.com/wupeiqi/p/5703697.html
在最后有介紹,課上沒(méi)展開(kāi)講。

XSS過(guò)濾

XSS×××是通過(guò)對(duì)網(wǎng)頁(yè)注入可執(zhí)行代碼且成功地被瀏覽器執(zhí)行,達(dá)到×××的目的。這里主要講針對(duì)富文本編輯器的情況。
在使用富文本編輯器的時(shí)候,尤其要注意XSS×××。因?yàn)閯e的地方還可以過(guò)濾html標(biāo)簽,但是富文本編輯器本身就要使用html標(biāo)簽,如果全部過(guò)濾掉,就無(wú)法正常顯示文檔格式了。
防范的手段就是把特定的標(biāo)簽過(guò)濾掉,比如script標(biāo)簽。最安全的做法就是設(shè)置白名單,留著編輯器使用的標(biāo)簽,其他的全部過(guò)濾。編輯器可能會(huì)自帶過(guò)濾,不過(guò)前端XSS過(guò)濾都會(huì)被繞過(guò),只有在后端過(guò)濾才能萬(wàn)無(wú)一失。
通用的手段就是,在收到數(shù)據(jù)提交之后進(jìn)行過(guò)濾,然后把過(guò)濾后的數(shù)據(jù)保存到數(shù)據(jù)庫(kù)。保存后的數(shù)據(jù)就認(rèn)為是安全的,之后頁(yè)面顯示的時(shí)候,就一律放行。
過(guò)濾標(biāo)簽的方法當(dāng)然可以通過(guò)正則匹配來(lái)實(shí)現(xiàn)。不過(guò)這里推薦一個(gè)模塊,beatifulsoup4。安裝模塊:

pip install beautifulsoup4  

另外這個(gè)模塊貌似也是爬蟲(chóng)利器,都是要處理html標(biāo)簽嘛。

查找標(biāo)簽-清空、清除

下面是BeautifulSoup的基本用法,使用find()方法找到指定的標(biāo)簽,然后清除掉:

content = """
<h2>測(cè)試頁(yè)面</h2>
<p class="c1">
    第一個(gè)段落<span class="color" >這里是紅色的</span>
    <script>alert('p1');</script>
</p>
<p class="c2 p2" id="i2">
    第二個(gè)段落<strong id="click" onclick="alert('p2');">點(diǎn)我看看</strong>
</p>
<p class="c3" id="i3">
    第三個(gè)段落
    <script>alert('p3');</script>
</p>
"""

from bs4 import BeautifulSoup
# 下面第二個(gè)參數(shù)是指定解析器,這個(gè)是python標(biāo)準(zhǔn)庫(kù)內(nèi)置的。也支持其他第三方的解析器(需安裝)
soup = BeautifulSoup(content, "html.parser")
tag = soup.find('script')  # 查找第一個(gè)標(biāo)簽
while tag:  # 這個(gè)循環(huán)應(yīng)該是能把所有的標(biāo)簽都查找出來(lái)了
    print(tag)
    # tag.hidden = True  # 去掉注釋,可以把整個(gè)空標(biāo)簽也去掉,否則就是去掉標(biāo)簽的內(nèi)容,保留標(biāo)簽
    tag.clear()  # 清空標(biāo)簽里的內(nèi)容
    tag = tag.find_next('script')  # 查找后一個(gè)標(biāo)簽
content = soup.decode()  # 轉(zhuǎn)成字符串
print(type(content), content)

HTML解析器,這里用了python自帶的,就不用另外安裝了。也有其他第三方更好的,但是需要安裝,就看怎么取舍了。
如歌直接打印soup,print(soup),顯示的效果也是一樣的。但是soup本身是 &lt;class 'bs4.BeautifulSoup'&gt;,直接打印這個(gè)對(duì)象的時(shí)候,內(nèi)部調(diào)用的也是return self.encode()

查找標(biāo)簽的屬性-清除

還是上面的html,進(jìn)一步處理以下標(biāo)簽中的屬性

from bs4 import BeautifulSoup
# 下面第二個(gè)參數(shù)是指定解析器,這個(gè)是python標(biāo)準(zhǔn)庫(kù)內(nèi)置的。也支持其他第三方的解析器(需安裝)
soup = BeautifulSoup(content, "html.parser")
tag = soup.find('script')  # 查找第一個(gè)標(biāo)簽
while tag:  # 這個(gè)循環(huán)應(yīng)該是能把所有的標(biāo)簽都查找出來(lái)了
    print(tag)
    tag.hidden = True
    tag.clear()  # 清空標(biāo)簽里的內(nèi)容
    tag = tag.find_next('script')  # 查找后一個(gè)標(biāo)簽
span = soup.find('span')
print(span.attrs)  # 打印這個(gè)標(biāo)簽的所有的屬性
del span.attrs['style']  # 刪除特定的屬性
strong = soup.find('strong')
print(strong.attrs)
del strong.attrs  # 刪除所有屬性
content = soup.decode()  # 轉(zhuǎn)成字符串
print(content, type(content), type(soup))

標(biāo)簽白名單

這次設(shè)置一個(gè)白名單,只保留白名單中的標(biāo)簽的內(nèi)容:

from bs4 import BeautifulSoup
soup = BeautifulSoup(content, "html.parser")
tags = ['p', 'span', 'strong']  # 設(shè)置一個(gè)白名單,下面只保留白名單的里的標(biāo)簽內(nèi)容
# 下面的這個(gè)循環(huán),遍歷一遍所有的標(biāo)簽
for tag in soup.find_all():
    if tag.name not in tags:
        tag.hidden = True
        tag.clear()
content = soup.decode()  # 轉(zhuǎn)成字符串
print(content)

包含標(biāo)簽屬性的白名單。上面的做法,只處理了標(biāo)簽,沒(méi)有處理標(biāo)簽中的屬性。這里需要一個(gè)更加復(fù)雜的白名單:

from bs4 import BeautifulSoup
soup = BeautifulSoup(content, "html.parser")
tags = {
    'p': ('class', 'id'),  # 只允許class 和 id 這2個(gè)屬性
    'span': ('class',),
    'strong': (),  # 值允許標(biāo)簽,不能帶任何屬性
}
# 下面的這個(gè)循環(huán),遍歷一遍所有的標(biāo)簽
for tag in soup.find_all():
    if tag.name not in tags:
        tag.hidden = True
        tag.clear()
    else:  # 處理白名單的屬性,再遍歷一遍標(biāo)簽的屬性
        # 下面的list()相當(dāng)于再?gòu)?fù)制了一份列表,然后遍歷這個(gè)列表。防止下面在迭代過(guò)程中禁止把迭代的元素刪除
        for attr in list(tag.attrs):
            if attr not in tags[tag.name]:
                del tag.attrs[attr]
content = soup.decode()  # 轉(zhuǎn)成字符串
print(content)

單例模式

一個(gè)類,每次實(shí)例化都會(huì)生成一個(gè)對(duì)象:

class Foo(object):

    def __init__(self):
        pass

c1 = Foo()
c2 = Foo()
print(c1, c2)

# 結(jié)果如下:
# <__main__.Foo object at 0x0000018AAF5C8A20> <__main__.Foo object at 0x0000018AAF76A2B0>

上面的情況,生成了2個(gè)對(duì)象,每個(gè)對(duì)象分別占用各自的內(nèi)存空間。
下面自定義了一個(gè)方法,用這個(gè)方法生成對(duì)象時(shí)候,只有對(duì)一次會(huì)創(chuàng)建實(shí)例,之后用的都是第一次的對(duì)象:

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)

# 結(jié)果如下:
# Foo.process Foo.process
# <bound method Foo.process of <__main__.Foo object at 0x0000022276B0A320>> <bound method Foo.process of <__main__.Foo object at 0x0000022276B0A320>>

為了更加直觀的說(shuō)明問(wèn)題,我這個(gè)類里還定義了一個(gè)process方法,返回的結(jié)果也是不變的。所以這種情況下,這個(gè)類不需要多個(gè)實(shí)例,因?yàn)槊總€(gè)實(shí)例返回的結(jié)果都是一樣的。也就是說(shuō),這種類,只需要一個(gè)實(shí)例,即只有在第一次實(shí)例化的時(shí)候需要?jiǎng)?chuàng)建對(duì)象,之后每次都只需要用之前創(chuàng)建的對(duì)象就好了,不用另外再創(chuàng)建對(duì)象了。
最LOW的做法大概就是,自己再實(shí)例化這個(gè)類后,把創(chuàng)建的對(duì)象保存下來(lái),之后不要再進(jìn)行實(shí)例化操作了。上面的例子中使用了特定的方法來(lái)進(jìn)行實(shí)例化,之后再第一次實(shí)例化的時(shí)候才會(huì)創(chuàng)建對(duì)象。從打印的結(jié)果來(lái)看,c1 和 c2 的內(nèi)存地址是一樣的。
上面的例子算是實(shí)現(xiàn)效果,但是改變了調(diào)用的方法。并且依然是可以用標(biāo)準(zhǔn)的方法來(lái)創(chuàng)建不同的對(duì)象的。下面的例子通過(guò)定義new方法,實(shí)現(xiàn)了真正的單例模式:

class Foo(object):
    __instance = None

    def __init__(self):
        pass

    # 單例模式,就是處在類里加上這個(gè)new方法和上面的__instance靜態(tài)屬性
    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)

# 結(jié)果如下:
# Foo.process Foo.process
# <__main__.Foo object at 0x000001DF3132A2E8> <__main__.Foo object at 0x000001DF3132A2E8>

上面如果注釋掉new方法,process方法返回的結(jié)果是一樣的,但是每個(gè)對(duì)象占用的內(nèi)存就是不同的了(浪費(fèi)資源)。如果一個(gè)類,它的每個(gè)對(duì)象里封裝的內(nèi)容都是一樣的,就可以使用單例模式。
所以實(shí)現(xiàn)了單例模式后,調(diào)用類中的方法可以實(shí)例化之后直接調(diào)用方法或?qū)傩裕?/p>

res = Foo().process()

Django的事務(wù)操作

Django提供了單獨(dú)API來(lái)控制事務(wù):

atomic(using=None, savepoint=True)[source] 

原子性是數(shù)據(jù)庫(kù)事務(wù)的一個(gè)屬性。使用atomic,我們就可以創(chuàng)建一個(gè)具備原子性的代碼塊。一旦代碼塊正常運(yùn)行完畢,所有的修改會(huì)被提交到數(shù)據(jù)庫(kù)。反之,如果有異常,更改會(huì)被回滾。
被atomic管理起來(lái)的代碼塊還可以內(nèi)嵌到方法中。這樣的話,即便內(nèi)部代碼塊正常運(yùn)行,如果外部代碼塊拋出異常的話,它也沒(méi)有辦法把它的修改提交到數(shù)據(jù)庫(kù)中。
一般還是用下面例子中的方法來(lái)使用把。

作為裝飾器來(lái)使用的例子

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

作為上下文管理器來(lái)使用的例子:

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()
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI