Django - 權限(4)- queryset、二級菜單的默認顯示、動態顯示按鈕權限

1、queryset

  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

五、exists()與iterator()方法

(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,可能會形成額外的數據庫查詢。所以,要根據場景靈活使用。 

2、二級菜單的默認顯示

一、修改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的別名沒有關係,固然也能夠起同樣的名字,內心明白他們其實並沒有關係便可。

相關文章
相關標籤/搜索