Django QuerySet查詢基礎與技巧.有了她,再也不用擔心SQL注入了.

2021-02-21 Python Web與Django開發

本來想自己動手寫篇文章詳細介紹下Django的QuerySet查詢方法的,看了網上有篇博客已經總結得很不錯了,所以轉下(原文地址: http://yshblog.com/blog/157。作者楊仕航)。

一開始使用Django的QuerySet獲取數據不太習慣。簡單的條件可以通過get或filter獲取,但複雜的我總是用SQL語句查詢得到。隨著接觸QuerySet越來越多,發現它相當鋒利、健全。

QuerySet是Django的查詢集,可以通過QuerySet條件查詢得到對應模型的對象集合。

相對直接使用SQL而言,QuerySet可以防止大部分SQL注入,而且提高代碼可讀性。畢竟Django的模型和具體表名不太一樣,需要查閱才得知表名,而且寫一大串SQL代碼可能直接把人看暈。假如碰到模型變動(改名、增刪欄位等),SQL可能又要重新調整。所以,能使用QuerySet的情況下,最好使用QuerySet。

還有重要一點,QuerySet是懶惰的。創建一個QuerySet對象,它不會直接返回數據集。等到使用它的時候,才解析該對象得到數據集。而且解析過一次會被緩存起來,下次使用時直接返回緩存中的數據,緩存的使用提高多次查詢的效率。

先假設有如下模型,以供下面的QuerySet查詢使用:

#coding:utf-8

from django.db import models

from django.contrib.auth.models import User

 

#博客模型

class Blog(models.Model):

    #標題

    caption = models.CharField(max_length=50)

    

    #作者(外鍵關聯User模型)

    author = models.ForeignKey(User)

    

    #內容

    content = models.TextField()

    

    #分類(和Tag模型關聯,多對多)

    tags = models.ManyToManyField(Tag,blank=True)

    

    #發表時間

    publish_time = models.DateTimeField(auto_now_add=True)

    

#博客分類標籤

class Tag(models.Model):

    #分類名

    tag_name = models.CharField(max_length=20,blank=True)

1、QuerySet和SQL語句

首先講如何將SQL語句解析成RawQuerySet對象。先讓不會使用QuerySet的同學順利使用SQL語句查詢。

不熟悉SQL的可以跳過該部分,如下代碼:

sql = 'select * from blog_blog' #需要查詢資料庫具體Blog對應表名

qs = Blog.objects.raw(sql) #將sql語句轉成RawQuerySet對象

該SQL是獲取全部記錄,相當於QuerySet如下查詢:

qs = Blog.objects.all()

以上的方法可將SQL語句轉成Blog模型的查詢集。

不過這個RawQuerySet和QuerySet稍微有些不同,屬性和方法沒有QuerySet多。例如我想知道這個查詢有多少條記錄。QuerySet可以直接使用count方法或者len方法獲取,而RawQuerySet沒有。這個也有方法處理,如下代碼:

#給rawqueryset對象加上len()方法

def add_len_to_raw_query(query):

    from django.db.models.query import RawQuerySet

    def __len__(self): 

        from django.db import connection

        sql = 'select count(*) from (%s) as newsql' % query.raw_query

        with connection.cursor() as cursor:

            cursor.execute(sql)

            row = cursor.fetchone()

        return row[0]

    setattr(RawQuerySet, '__len__', __len__)

    

sql = 'select * from blog_blog'

qs = Blog.objects.raw(sql)

 

add_len_to_raw_query(qs)  #給qs加上len()方法

print(len(qs))

我們可通過該方法設置並獲取相關對象。另外,使用SQL查詢,我們還可以通過SQL語句給RawQuerySet對象添加額外的屬性。raw方法會解析SQL語句中的欄位,將欄位轉成屬性的方式方便調用。例如:

sql = '''

    Select blog_blog.id, blog_blog.caption, count(blog_blog_tags.id) as tag_count

    From blog_blog

    Left join blog_blog_tags on blog_blog.id = blog_blog_tags.blog_id

    Group by blog_blog.id, blog_blog.caption

'''

 

qs = Blog.objects.raw(sql)

print(qs[0].tag_count)

上面SQL語句是得到每篇Blog對應分類標籤的個數,是不是覺得SQL很複雜了。

不過,我們可以在查詢結果直接使用tag_count屬性。

另外,為了方便測試,我們可以輸出QuerySet對象的SQL語句:

qs = Blog.objects.filter(id=1)

print(qs.query)

2、filter和get方法

上面使用了filter。filter是篩選的意思,通過filter篩選得到符合條件的數據集。

例如我分別獲取如下情況的數據集。

#獲取blog id為1的數據集

qs1 = Blog.objects.filter(id=1)

 

#獲取作者user所發表的博客

user = request.user #先使用當前登錄的用戶判斷

qs2 = Blog.objects.filter(author=user)

 

#多個條件用逗號隔開

qs3 = Blog.objects.filter(id=1, author=user)

Blog id為1相當於SQL:

select * from blog_blog where id = 1

這裡你會發現只有一條記錄,而filter返回是一個只有一條記錄的數據集。獲取該數據還需要進一步獲取:

q = qs1[0]

這時可以使用get方法直接獲取該條記錄:

q = Blog.objects.get(id=1)

不過,若符合條件的沒有記錄或多條記錄會拋出異常。我們可以利用這個特點做一些操作:

try:

    q = Blog.objects.get(caption='test') #找標題為test的博客

except Blog.DoesNotExist:

    q = Blog(caption='test') #創建標題為test的博客

    #...

    q.save()

當然,django有個get_or_create方法可以簡化該過程:

#獲取標題為test的博客,不存在則創建

q = Blog.objects.get_or_create(caption='test')

3、不等於條件

filter篩選的條件都是符合的條件,也就是我們SQL語句中的等於。有時候,我們希望排除一些條件,也就是不等於或者not條件:

select * from blog_blog where id <> 1

select * from blog_blog where not(id = 1)

這種類型的條件,需要使用exclude方法。例如:

qs = Blog.objects.exclude(id=1)

如果我要實現複雜一些的查詢,獲取當前登錄用戶發表的博客,並排除id為1的博客。查詢如下:

user = request.user

qs = Blog.objects.filter(author=user).exclude(id=1)

這裡延伸QuerySet另一特性,可以在QuerySet上繼續查詢,即QuerySet支持鏈式查詢。

4、大於和小於條件

繼續延伸,既然QuerySet可以實現等於和不等於。那麼大於和小於應當如何實現?

這種filter和exclude都無能為力。需要藉助欄位條件修飾,例如:

#查詢id大於10的記錄

qs1 = Blog.objects.filter(id__gt=10)

 

#查詢id大於等於10的記錄

qs2 = Blog.objects.filter(id_gte=10)

 

#排除id小於10的記錄

qs3 = Blog.objects.exclude(id__lt=10)

 

#排除id小於等於10的記錄

qs4 = Blog.objects.exclude(id__lte=10)

從這裡我們可以總結一下,objects查詢基本就3種方法:get、filter、exclude。

條件再用欄位修飾拓展,欄位修飾是兩個下劃線加修飾碼。

有個方法可以幫助記憶大於、小於的修飾碼。

gt 是 greater than 的縮寫,或者你熟悉html可以記憶為"&gt;","&gt;"是大於號的轉義。

gte 後面的 e 是等於equal單詞的第1個字母。

lt 和 lte 是同樣方法,less than 的縮寫;"&lt;"小於號的轉義。

這裡還有兩個拓展。

1)範圍查詢

有時候,不單單是大於或小於。可能是一個範圍,例如 1<=id<=9。你可以用兩個條件組合。

qs = Blog.objects.filter(id__gte=1).filter(id__lte=9)

或者

qs = Blog.objects.filter(id__gte=1, id__lte=9)

可以使用__range範圍修飾:

qs = Blog.objects.filter(id__range=[1, 9])

2)in條件查詢

有時候不是簡單大於或小於,需要從一個列表的值獲取。in條件查詢用__in修飾,例如:

#獲取id為 1、3、6、7、9的記錄

qs = Blog.objects.filter(id__in=[1, 3, 6, 7, 9])

5、字符串模糊匹配

數值有大於小於等判斷,那麼字符串有模糊匹配判斷的需求。例如開頭是什麼,結尾是什麼,包含什麼字符等等。這些同樣需要藉助欄位條件修飾。例如:

#標題包含django,若忽略大小寫使用__icontains

qs1 = Blog.objects.filter(caption__contains='django')

 

#標題是django開頭的,若忽略大小寫使用__istartswith

qs2 = Blog.objects.filter(caption__startswith='django')

 

#標題是django結尾的,若忽略大小寫使用__iendswith

qs2 = Blog.objects.filter(caption__endswith='django')

那"不包含"、"開頭不是"、"結尾不是"用什麼?當然用exclude啦。

6、日期時間處理

上面Blog模型有個發表日期publish_time欄位。該欄位是datetime類型。

日期時間類型要不數值、字符串複雜得多。同樣使用欄位條件修飾實現相應查詢:

import datetime

 

#獲取今天的datetime

now = datetime.datetime.now()

 

#找今年發布的博客

qs1 = Blog.objects.filter(publish_time__year=now.year)

 

#找本月發布的博客

qs2 = Blog.objects.filter(publish_time__year=now.year,publish_time__month=now.month)

 

#找到每月1號發布的博客

qs3 = Blog.objects.filter(publish_time__day=1)

我們還可以使用使用__range獲取某範圍內的數據:

import datetime

 

#獲取前7天發表的博客

now = datetime.datetime.now()

end_date = datetime.datetime(now.year, now.month, now.day, 0, 0) 

start_date = end_date - datetime.timedelta(7)

 

#找今年發布的博客

qs = Blog.objects.filter(publish_time__range=[start_date, end_date])

7、or條件

上面多個條件出現大多是and邏輯。那我可能使用or邏輯。例如我想模糊匹配標題為"django" 或 "python"。這時只能採用or邏輯處理。如下代碼:

qs = Blog.objects.filter(caption__icontains='django')|Blog.objects.filter(caption__icontains='python')

若你嫌棄代碼過程,可以使用Q函數:

from django.models import Q

qs = Blog.objects.filter(Q(caption__icontains='django')|Q(caption__icontains='python'))

8、limit獲取前幾條

有時頁面顯示只需要顯示前10條或前20條記錄。這個在SQL語句中可以用limit限制返回的記錄數。同樣,在QuerySet查詢中也可以實現該功能。

qs = Blog.objects.all()[:10]

上面代碼是獲取全部博客的前10條記錄,和切片一樣,但索引不能為負數。

另外,這個切片器也不會讓QuerySet直接執行,直到使用該查詢。

9、外鍵查找

我上面寫的示例模型,有兩個外鍵。

假如我需要獲取某一篇博客有什麼分類標籤,或分類標籤下有哪些博客,應當如何獲取?

獲取某一篇博客有什麼分類標籤,由於Blog有個tags外鍵關聯Tag模型。可直接獲取:

blog = Blog.objects.all()[0]

tags = blog.tags.all() #獲取第1篇博客全部標籤

而分類標籤沒有直接寫一個外鍵和Blog模型關聯,但可以用set通過tag獲取全部blog:

tag = Tag.objects.all()[0]

blogs = tag.blog_set.all() #獲取第1個分類標籤全部博客

10、排序

SQL語句可以使用Order by排序。QuerySet查詢也有order_by與之對應:

#按照日期正序排序

qs1 = Blog.objects.all().order_by('publish_time')

 

#安裝日期倒序排序(all可以省略)

qs2 = Blog.objects.order_by('-publish_time')

針對哪個排序,就寫該欄位名稱即可。默認正序排序,若加個負號表示倒序排序。

正序一般是數值由小到大,日期由遠到近。

排序還有一個神奇的用法,在SQL我還沒見過:隨機排序。通過該功能,我們可以隨機獲取記錄:

#隨機獲取前10條記錄

qs = Blog.objects.order_by('?')[:10]

11、其他

有些知識過於零碎,沒有和上面放在一起講。隨便也羅列出來:

1)獲取值為null的數據

qs = Blog.objects.filter(content__isnull=True)

2)去重

qs = Tag.objects.filter(id__in=[1, 3]).blog_set().distinct()

因為這裡通過Tag獲取的博客可能重複,可以用distinct去重。

相關焦點

  • Django基礎(24): aggregate和annotate方法使用詳解與示例
    但如果查詢本身比較複雜,比如需要對查詢集(queryset)的某些欄位進行計算或進行分組計算或排序, 這時我們就需要使用更高級的aggregate和annotate方法了。小編我今天就帶你看下什麼情況下需要使用aggregate和annotate方法以及如何使用它們。本文比較抽象,但非常有用, 看不懂的可以先加入微信收藏以後多看幾遍哦。
  • Django查詢資料庫操作詳解(一)
    在本節我們將全面闡述 Django 的表查詢 API。本節知識屬於重中之重,希望各位小夥伴能夠儘可能的掌握這些 API,懂得活學活用,熟悉每個 API 的使用場景,這將對後續學習 Django 框架有很大的幫助。1.
  • Python——用 Django 寫 restful api 接口
    我用的 pymsql,pymsql 是 Python 中操作MySQL 的模塊,其使用方法和 MySQLdb 幾乎相同。但目前在 python3.x 中,PyMySQL 取代了 MySQLdb。語句,%s用作字符串佔位              sql = "INSERT INTO `meizi_meizis`(`mid`,`title`,`picname`,`page_url`,`img_url`) VALUES(%s,%s,%s,%s,%s)"              try:                  cursor.execute(sql, (i['
  • Django 1.10中文文檔-聚合
    查詢集參考 有所有的聚合函數。aggregate() 是 QuerySet 的一個終止子句,意思是說,它返回一個包含鍵值對的字典。 鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照欄位和聚合函數的名稱自動生成出來的。
  • 特殊場景的sql注入思路
    上一篇介紹了sql注入的基礎知識以及手動注入方法,但是在實際的環境中往往不會像靶場中那樣簡單。今天我就來為大家介紹一種特殊場景的sql注入思路。用戶名與密碼分開驗證的情況第一個場景我們以We Chall平臺的Training: MySQL II 一題為例。
  • SQL注入攻擊詳解
    3、Sql注入產生原因及威脅剛剛講過當我們訪問動態網頁時, Web 伺服器會向數據訪問層發起 Sql 查詢請求,如果權限驗證通過就會執行 Sql 語句。注入可以藉助資料庫的存儲過程進行提權等操作4、判斷Sql注入點4.1 判斷是否存在sql注入漏洞通常情況下,可能存在 Sql 注入漏洞的 Url 是類似這種形式 :http://xxx.xxx.xxx/abcd.php?id=XX對 Sql 注入的判斷,主要有兩個方面:判斷該帶參數的 Url 是否存在 Sql 注入?
  • Django 官方推薦的姿勢:類視圖
    '要寫一個類視圖,首先需要繼承 django 提供的某個類視圖。具體的實現我們以後會專門開闢一個專欄分析類視圖的原始碼,到時候就能看出 django 使用的魔法了)。然後我們調用父類的 get_queryset 方法獲得全部文章列表,緊接著就對返回的結果調用了 filter 方法來篩選該分類下的全部文章並返回。
  • 2020年最新Django經典面試問題與答案匯總(中)-大江狗整理
    兩個方法都是Django ORM優化數據查詢必須要熟練掌握的方法。Django的aggregate()方法作用是對一組值(比如queryset的某個欄位)進行統計計算,並以字典(Dict)格式返回統計計算結果。django的aggregate方法支持的聚合操作有AVG / COUNT / MAX / MIN /SUM 等。
  • Java web安全黑客攻防之sql注入
    1.什麼是sql注入sql注入通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙伺服器執行惡意的SQL命令通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙伺服器執行惡意的SQL
  • SQL注入常規Fuzz全記錄
    前言本篇文章是在做ctf bugku的一道sql 盲注的題(題目地址:注入題目)中運用了fuzz的思路,完整記錄整個fuzz的過程
  • SQL 注入攻防入門詳解
    這幾天把sql注入的相關知識整理了下,希望大家多多提意見。(對於sql注入的攻防,我只用過簡單拼接字符串的注入及參數化查詢,可以說沒什麼好經驗,為避免後知後覺的犯下大錯,專門查看大量前輩們的心得,這方面的資料頗多,將其精簡出自己覺得重要的,就成了該文)下面的程序方案是採用 ASP.NET + MSSQL,其他技術在設置上會有少許不同。
  • 徹底幹掉噁心的 SQL 注入漏洞, 一網打盡!
    這裡需要注意的是,使用了PreparedStatement 並不意味著不會產生注入,如果在使用PreparedStatement之前,存在拆分sql語句,那麼仍然會導致注入,如// 拼接 sqlString sql = "SELECT *
  • Myql SLEEP函數和SQL注入
    sqlmap是使用Python編寫的一款資料庫sql注入掃描工具,目前支持常見的mysql、oracel、postgresql、sql server,access,db2,sqlite等數據的安全漏洞(sql注入)。
  • 一篇文章帶你了解Django ORM操作(高端篇)
    原生sql是可以指定顯示的列名的,同樣,ORM也可以。F查詢有時候,我們可能有這樣的需求,就是兩個列之間進行比較。and查詢同時出現,Q查詢必須在其他查詢之前。動態構造Q查詢一些時候,我們可能並不太確定有什麼條件。
  • SQL 注入常規 Fuzz 全記錄
    盲注的題中運用了fuzz的思路,完整記錄整個fuzz的過程,給師傅們當點心,方便大家加深對web sql注入 fuzz的理解。2、嘗試輸入admin/123456,提示密碼錯誤,因此可以確定存在用戶admin,這裡可能會有師傅要爆破了,但這裡題目要求sql注入,我們就按照預期解來吧。
  • Django限制表單中ForeignKey對應下拉菜單選項數量的兩種經典方法
    有朋友問: 假如我們有文章Article和類別Category兩個模型,其中類別和文章是一對多的關係。
  • 使用SQL理解Django中的Group By
    在本文中,我將查詢集和SQL並排放在一起。如果你更熟悉SQL,那麼這就是你的Django GROUP BY速查表。如何使用Group By篩選一個QuerySet要對一個篩選後的查詢應用聚合,你可以在查詢中的任何位置使用filter。例如,僅根據員工用戶的活動狀態來對他們進行計數:
  • SQL注入的幾種類型和原理
    注意:以下這些類型實在slqi-labs環境(也就是MySQL)下實驗,SQL是所有關係型資料庫查詢的語言,針對不同的資料庫,SQL語法會有不同,在注入時的語句也會有所不同。另外這裡介紹一些技巧避免重複手工。比如limit這種只需要改變數值查詢數據的語句,使用Burp suite 的intruder功能,關鍵參數配置字典,對返回的結果進行匹配。
  • Django分頁完整示例
    在django中可以使用兩種方法進行分頁,第一種方法是使用基於函數的視圖,第二種方法是使用基於類的視圖。現在,首先,需要使用此命令創建一個新的django項目。我稱這個項目為MyProjectdjango-admin startproject ProjectName首先,需要將目錄更改為已創建的項目,然後需要創建一個App,我將其稱為MyApp。
  • Django聚合查詢和分組查詢
    而分組查詢同樣也屬於聚合查詢中的一種,只是更加複雜一點而已,在學習本節的知識時候,如果你有較好的 MySQL 知識儲備,那麼學習本節知識將會變得再簡單不過了。下面就讓我們開始學習吧。1.它們統一定義在 django.db.models模塊中,所以再使用聚合函數時,同樣需要提前導入,為了方便使用,我們採用下面的方式引入:from django.db.models import *它的語法格式如下所示,它的返回值是一個字典,以統計結果變量名為 key,以統計值為 value:MyModel.objects.aggregate(統計結果變量名