亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python自動化開發學習25-Django

發布時間:2020-07-18 05:13:37 來源:網絡 閱讀:3186 作者:騎士救兵 欄目:編程語言

組合搜索

下面要講的是基于模板語言的實現方法,完全沒有使用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

動態的根據url處理篩選

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來獲取到篩選的數據,但是首先得有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" 的樣式。

小結

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

最后,上面的代碼比較長,看著也比較亂。可以用模板語言的自定義函數封裝一下,這樣前端只需要寫一行就好了,而更加復雜的邏輯則放到 templatetags/*.py 自定義的模板函數里來實現。課上是這么做了,不過我

JSONP

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 = []

這里提一下,就不展開了。

通過JSONP支持跨域

瀏覽器有同源策略,但是其實并不是所有的請求都會被同源策略阻止。比如:
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屬性的標簽,都不受同源策略的影響。
這里就要通過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函數,就可以獲取到返回的數據了。如此成功的繞開了瀏覽器的同源策略,實現了跨域請求。

繼續優化JSONP

上面還有2個問題:

  • 回調函數的函數名寫死了,可能會和本地的函數名重名
  • 每請求一次,都會生成一個script標簽

先把處理函數修改一下解決第一個問題:

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發送JSONP

這里主要看一下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>

CORS(跨站資源共享)

解決跨域的問題,除了上面的JSONP,還有這個CORS。
講師的博客:https://www.cnblogs.com/wupeiqi/p/5703697.html
在最后有介紹,課上沒展開講。

XSS過濾

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本身是 &lt;class 'bs4.BeautifulSoup'&gt;,直接打印這個對象的時候,內部調用的也是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的事務操作

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()
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

政和县| 汕尾市| 泽州县| 宁晋县| 萍乡市| 洪洞县| 富平县| 陆丰市| 虎林市| 定边县| 遂川县| 合水县| 河南省| 高清| 宣恩县| 收藏| 广德县| 滦南县| 喀喇沁旗| 临澧县| 伊川县| 乐至县| 达尔| 榆社县| 三河市| 遂平县| 洪湖市| 寿光市| 文山县| 赤水市| 织金县| 布拖县| 丹棱县| 舞阳县| 航空| 改则县| 武鸣县| 萝北县| 威信县| 浦城县| 贞丰县|