Queryset是django中構建的一種數據結構,ORM查詢集每每是queryset數據類型,咱們來進一步瞭解一下queryset的特色。html
使用Python 的切片語法來限制查詢集記錄的數目。它等同於SQL 的LIMIT 和OFFSET 子句。sql
>>> Entry.objects.all()[:5] # (LIMIT 5) >>> Entry.objects.all()[5:10] # (OFFSET 5 LIMIT 5)
不支持負的索引(例如Entry.objects.all()[-1])。一般,查詢集的切片返回一個新的查詢集(它不會再執行sql查詢語句)。數據庫
articleList = models.Article.objects.all() for article in articleList: print(article.title)
查詢集是惰性執行的 —— 建立查詢集不會帶來任何數據庫的訪問。你能夠將過濾器保持一成天,直到查詢集須要求值時,Django 纔會真正運行這個查詢。django
queryResult = models.Article.objects.all() # not hits database print(queryResult) # hits database for article in queryResult: print(article.title) # hits database
通常來講,只有在「請求」查詢集的結果時纔會到數據庫中去獲取它們。當你確實須要結果時,查詢集經過訪問數據庫來求值。緩存
每一個查詢集都包含一個緩存來最小化對數據庫的訪問。理解它是如何工做的將讓你編寫最高效的代碼。session
在一個新建立的查詢集中,緩存爲空。首次對查詢集進行求值 —— 同時發生數據庫查詢 ——Django 將保存查詢的結果到查詢集的緩存中並返回明確請求的結果(例如,若是正在迭代查詢集,則返回下一個結果)。接下來對該查詢集的求值將重用緩存的結果。數據結構
請牢記這個緩存行爲,由於對查詢集使用不當的話,它會坑你的。例如,下面的語句建立兩個查詢集,對它們求值,而後扔掉它們:app
print([a.title for a in models.Article.objects.all()]) print([a.create_time for a in models.Article.objects.all()])
這意味着相同的數據庫查詢將執行兩次,顯然倍增了你的數據庫負載。同時,還有可能兩個結果列表並不包含相同的數據庫記錄,由於在兩次請求期間有可能有Article被添加進來或刪除掉。爲了不這個問題,只需保存查詢集並從新使用它,以下:ide
queryResult = models.Article.objects.all() print([a.title for a in queryResult]) print([a.create_time for a in queryResult]
什麼時候查詢集不會被緩存?優化
查詢集不會永遠緩存它們的結果。當只對查詢集的部分進行求值時會檢查緩存,若是這個部分不在緩存中,那麼接下來查詢返回的記錄都將不會被緩存。因此,這意味着使用切片或索引來限制查詢集將不會填充緩存。例如,重複獲取查詢集對象中一個特定的索引將每次都查詢數據庫,以下:
>>> queryset = Entry.objects.all() >>> print queryset[5] # Queries the database >>> print queryset[5] # Queries the database again
然而,若是已經對所有查詢集求值過,則將檢查緩存,以下:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print queryset[5] # Uses cache >>> print queryset[5] # Uses cache
下面是一些其它例子,它們會使得所有的查詢集被求值並填充到緩存中:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
注意:簡單地打印查詢集不會填充緩存,以下:
queryResult = models.Article.objects.all() print(queryResult) # hits database print(queryResult) # hits database
(1)exists()
簡單的使用if語句進行判斷也會徹底執行整個queryset而且把數據放入cache,雖然你並不須要這些數據!爲了不這個,能夠用exists()方法來檢查是否有數據,以下:
if queryResult.exists(): # SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=() print("exists...")
(2)iterator()
當queryset很是巨大時,cache會成爲問題。
處理成千上萬的記錄時,將它們一次裝入內存是很浪費的。更糟糕的是,巨大的queryset可能會鎖住系統進程,讓你的程序瀕臨崩潰。要避免在遍歷數據的同時產生queryset cache,可使用iterator()方法來獲取數據,處理完數據就將其丟棄。以下:
objs = Book.objects.all().iterator() # iterator()能夠一次只從數據庫獲取少許數據,這樣能夠節省內存 for obj in objs: print(obj.title) # BUT,再次遍歷沒有打印,由於迭代器已經在上一次遍歷(next)到最後一次了,沒得遍歷了 for obj in objs: print(obj.title)
固然,使用iterator()方法來防止生成cache,意味着遍歷同一個queryset時會重複執行查詢。因此使用iterator()的時候要小心,確保你的代碼在操做一個大的queryset時沒有重複執行查詢。
總結:queryset的cache是用於減小程序對數據庫的查詢,在一般的使用下會保證只有在須要的時候纔會查詢數據庫。使用exists()和iterator()方法能夠優化程序對內存的使用。不過,因爲它們並不會生成queryset cache,可能會形成額外的數據庫查詢。所以,要根據場景靈活使用。
一、修改my_tags.py文件,代碼以下:
@register.inclusion_tag("menu.html") def get_menu_styles(request): permission_menu_dict = request.session.get("permission_menu_dict") print("permission_menu_dict", permission_menu_dict) for val in permission_menu_dict.values(): for item in val["children"]: val["class"] = "hide" ret = re.search("^{}$".format(item["url"]), request.path) if ret: val["class"] = "" return {"permission_menu_dict": permission_menu_dict}
二、修改menu.html文件,代碼以下:
<div class="multi-menu"> {% for item in permission_menu_dict.values %} <div class="item"> <div class="title"> <i class="{{ item.menu_icon }}"></i>{{ item.menu_title }} </div> <div class="body {{ item.class }}"> {% for foo in item.children %} <a href="{{ foo.url }}">{{ foo.title }}</a> {% endfor %} </div> </div> {% endfor %} </div>
除了菜單權限,還有按鈕權限,好比添加用戶(帳單),編輯用戶(帳單),刪除用戶(帳單),這些不是菜單選項,而是以按鈕的形式在頁面中顯示,但不是全部的用戶都有全部的按鈕權限,咱們須要在用戶不擁有這個按鈕權限時就不要顯示這個按鈕,下面介紹一下思路和關鍵代碼。
一、在permission表中增長一個字段name,permission模型類以下:
class Permission(models.Model): """ 權限表 """ url = models.CharField(verbose_name='含正則的URL', max_length=32) title = models.CharField(verbose_name='標題', max_length=32) menu = models.ForeignKey(verbose_name='標題', to="Menu", on_delete=models.CASCADE, null=True) name = models.CharField(verbose_name='url別名', max_length=32, default="") def __str__(self): return self.title
二、將權限別名列表注入session,setsession.py中代碼以下:
def initial_session(user_obj, request): """ 將當前登陸人的全部權限url列表和 本身構建的全部菜單權限字典和 權限表name字段列表注入session :param user_obj: 當前登陸用戶對象 :param request: 請求對象HttpRequest """ # 查詢當前登陸人的全部權限列表 ret = Role.objects.filter(user=user_obj).values('permissions__url', 'permissions__title', 'permissions__name', 'permissions__menu__title', 'permissions__menu__icon', 'permissions__menu__id').distinct() permission_list = [] permission_names = [] permission_menu_dict = {} for item in ret: # 獲取用戶權限列表用於中間件中權限校驗 permission_list.append(item['permissions__url']) # 獲取權限表name字段用於動態顯示權限按鈕 permission_names.append(item['permissions__name']) menu_pk = item['permissions__menu__id'] if menu_pk: if menu_pk not in permission_menu_dict: permission_menu_dict[menu_pk] = { "menu_title": item["permissions__menu__title"], "menu_icon": item["permissions__menu__icon"], "children": [ { "title": item["permissions__title"], "url": item["permissions__url"], } ], } else: permission_menu_dict[menu_pk]["children"].append({ "title": item["permissions__title"], "url": item["permissions__url"], }) print('權限列表', permission_list) print('菜單權限', permission_menu_dict) # 將當前登陸人的權限列表注入session中 request.session['permission_list'] = permission_list # 將權限表name字段列表注入session中 request.session['permission_names'] = permission_names # 將當前登陸人的菜單權限字典注入session中 request.session['permission_menu_dict'] = permission_menu_dict
三、自定義過濾器,my_tags.py中部分代碼以下:
@register.filter def has_permission(btn_url, request): permission_names = request.session.get("permission_names") return btn_url in permission_names
四、使用過濾器,模板(如customer_list.html)中部分權限按鈕代碼以下:
<div class="btn-group"> {% load my_tags %} {% if "customer_add"|has_permission:request %} <a class="btn btn-default" href="/customer/add/"> <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客戶 </a> {% endif %} </div>
注意:permission表中新增name字段與url的別名沒有關係,固然也能夠起同樣的名字,內心明白他們其實並沒有關係便可。