Django - 权限(4)- queryset、二级菜单的默认显示、动态显示按钮权限

一、queryset

  Queryset是django中构建的一种数据结构,ORM查询集往往是queryset数据类型,我们来进一步了解一下queryset的特点。

1、可切片

  使用Python 的切片语法来限制查询集记录的数目。它等同于SQL 的LIMIT 和OFFSET 子句。

  >>> Entry.objects.all()[:5]      # (LIMIT 5)
  >>> Entry.objects.all()[5:10] # (OFFSET 5 LIMIT 5)

  不支持负的索引(例如Entry.objects.all()[-1])。通常,查询集的切片返回一个新的查询集(它不会再执行sql查询语句)。

2、可迭代

  articleList = models.Article.objects.all()
  for article in articleList:
  print(article.title)

3、惰性查询

  查询集是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集需要求值时,Django 才会真正运行这个查询。

  queryResult = models.Article.objects.all()     # not hits database
  print(queryResult) # hits database
  for article in queryResult:
  print(article.title) # hits database

  一般来说,只有在“请求”查询集的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集通过访问数据库来求值。

4、缓存机制

  每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。

  在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集的求值将重用缓存的结果。

  请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

  print([a.title for a in models.Article.objects.all()])
  print([a.create_time for a in models.Article.objects.all()])

  这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它,如下:

  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

5、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,可能会造成额外的数据库查询。因此,要根据场景灵活使用。 

二、二级菜单的默认显示

1、修改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}

2、修改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>

动态显示按钮权限

  除了菜单权限,还有按钮权限,比如添加用户(账单),编辑用户(账单),删除用户(账单),这些不是菜单选项,而是以按钮的形式在页面中显示,但不是所有的用户都有所有的按钮权限,我们需要在用户不拥有这个按钮权限时就不要显示这个按钮,下面介绍一下思路和关键代码。

1、在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

2、将权限别名列表注入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

3、自定义过滤器,my_tags.py中部分代码如下:

@register.filter
def has_permission(btn_url, request):
permission_names = request.session.get("permission_names")
return btn_url in permission_names

4、使用过滤器,模板(如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的别名没有关系,当然也可以起一样的名字,心里明白他们其实并无关系即可。

上一篇:onlyoffice新版5.1.2版解决中文汉字输入重复等问题


下一篇:【高级】C++中虚函数机制的实现原理