假设我有一个Item
模型,其中Item
对象可以是public
(所有用户都可以访问(或private
(只有经过身份验证的用户才能访问(:
class Item(models.Model):
title = models.CharField(max_length=100)
is_public = models.BoleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
#...
secret_key = ...
class Meta:
# I need to keep items in order:
ordering = ('-created_at',)
我需要的是使用generic.ListView
列出所有项目 - 保持顺序 - 但隐藏这些项目的secret_key
,is_public=False
匿名用户。
因此,在模板中,如果用户未经过身份验证,我将隐藏secret_key
,例如:
{% if request.user.is_authenticated %}
<p>{{ item.title }} - {{ item.secret_key }}</p>
{% else %}
<p>{{ item.title }} - This item is private. Sign up to see the secret_key!</p>
{% endif %}
ListView
是这样的:
class ItemListView(ListView):
model = Item
paginate_by = 10
我知道我可以将两个单独的查询集发送给非登录用户到模板,一个用于公共项目,另一个用于私有项目;但我不确定如何在这种方法中保持顺序('-created_at'
(。
问题是:
将所有
secret_keys
发送到模板并为那里的非登录用户隐藏它们是否安全?(如果它是安全的,那么(有没有更有效的方法?
我尝试覆盖ItemListView
的get_queryset
方法并将if
条件从模板移动到那里(我认为这会提高性能,对吧?我处理了用户经过身份验证的情况(只需返回所有对象(;但是对于非登录用户,我想以某种方式加入两个单独的查询集,一个保存公共项目,另一个只保存私有项目的title
和created_at
;但我没有发现在这种方法中保持顺序:
class ItemListView(ListView):
model = Item
paginate_by = 10
def get_queryset(self):
if self.request.user.is_authenticated:
return Item.objects.all()
else:
# ???
这只是一个最小可重现的例子;实际上在项目中,我有多个access_levels
;每个用户都有一个access_level
,基于他们的plan
(例如基本,普通,专业等(,每个Item
都有一个access_level
;我正在处理大约+100K的对象,这些对象是从不同的数据库(postgresql
- 一些缓存在redis
上(获取的,所以性能在这里真的很重要。此外,系统现已启动并运行;所以我更喜欢不太基本的解决方案。
谢谢你的时间。非常感谢您的帮助。
secret_keys发送到模板并为那里的非登录用户隐藏它们是否安全?
您的模板在服务器端呈现,客户端仅获取呈现的标记,所以是的,它是完全安全的。好吧,当然,除非您团队中的某个人弄乱了模板代码;-(
(如果它是安全的,那么(有没有更有效的方法?
只需在视图中筛选查询集 - 不需要两个不同的查询集,筛选查询集不会更改其顺序。
def get_queryset(self):
qs = super(ItemListView, self).get_queryset()
if not self.request.user.is_authenticated:
qs = qs.filter(is_private=False)
return qs
在您的模板中:
{# avoids doing the same constant lookup within the loop #}
{% with is_auth=request.user.is_authenticated %}
{# I assume the queryset is named "objects" in the context ?#}
{% for item in objects %}
<p>{{ item.title }}{% if is_auth %} - {{ item.secret_key }}{% endif %}</p>
{% endfor %}
{% endwith %}
编辑:bdoubleu在他的回答中正确地提到,他的解决方案使测试更容易。如果只需要模型中的字段(无方法调用(,也可以改用 QuerySet.values((:
def get_queryset(self):
qs = super(ItemListView, self).get_queryset()
fields = ["title", "created_at"]
if self.request.user.is_authenticated:
fields.append("secret_key")
else:
qs = qs.filter(is_private=False)
return qs.values(*fields)
这也将使你的代码更有效率,因为它不必构建完整的模型实例。
另一种选择是对查询集进行批注,以便为display_secret_key
添加一个额外的属性,这将比在模板化时检查查询集中每个项目的用户访问级别更有效。
from django.db.models import F, Value as V
class ItemListView(ListView):
queryset = Item.objects.all()
paginate_by = 10
def get_queryset(self):
annotations = {
'display_secret_key': V('')
}
if self.request.user.access_level == 'PRO':
annotations['display_secret_key'] = F('secret_key')
return (
super().get_queryset()
.annotate(**annotations)
)
然后在模板中:
<p>{{ item.title }} - {{ item.display_secret_key }}</p>
您可以使用2 个模板,一个用于经过身份验证的用户,一个用于未经身份验证的用户。(只需覆盖 get_template_names(( 进行身份验证检查,并在找到的名称中添加类似 _sectempl.html 的内容,并使用机密数据添加模板的相应副本(
但我想和 bruno desthuilliers 说,如果您关闭调试模式,则不会有未经身份验证的用户在其中看到内容的星座
{% with authenticated=request.user.is_authenticated %}
{% if authenticated %}
do secret stuff
{% endif %}
{% endwith %}
或
{% if request.user.is_authenticated %}
hide secret stuff for all the others
{% endif %}
如果你在标准的 django 用户权限管理之外有一个复杂的用户分组组合(你可以在模板中请求用户权限(,那么我会将user_status(你的"计划"或访问级别(写入用户会话(身份验证时(,并在对象属性的输出函数中检查此user_status。
草图:在模板中使用:
{% for i in object_list %}
{{ i.name}}, {{ i.print_secret }}
{% endfor %}
在模型中,您创建一个"print_secret"方法女巫根据会话数据中先前记录的user_status返回机密。