Suppose I have a Item
model, where Item
objects can either be public
(accessible to all users) or private
(accessible only to authenticated users):
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',)
What I need is to list all items using a generic.ListView
– keeping the order – but hide the secret_key
of those items with is_public=False
for anonymous users.
So in the template, I hide the secret_key
if the user is not authenticated, like:
{% 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 %}
and the ListView
is like:
class ItemListView(ListView):
model = Item
paginate_by = 10
I’m aware that I can send two separate querysets for non logged-in users to the template, one for public items and the other for private ones; but I’m not sure how can I keep the order ('-created_at'
) in this approach.
The question is:
-
Is it safe to send all the
secret_keys
to the template and hide them for non logged-in users there? -
(if it is safe, then) Is there a more efficient way of doing this?
I tried overriding the get_queryset
method of my ItemListView
and move the if
condition from template to there (I think this would increase the performance, right?). I handled the situation where the users is authenticated (simply return all the objects); but for non logged-in users, I thought about somehow joining two separate querysets, one holding the public items and the other holding only the title
and created_at
of private items; but I didn’t find to keep the order in this approach:
class ItemListView(ListView):
model = Item
paginate_by = 10
def get_queryset(self):
if self.request.user.is_authenticated:
return Item.objects.all()
else:
# ???
This was only a minimal-reproducible-example; Actually in the project, I have multiple access_levels
; Each user has an access_level
, based on their plan
(e.g. basic, normal, pro, etc.) and each Item
has an access_level
; And an I’m dealing with about +100K objects, fetched from different databases (postgresql
– some cached on redis
) so the performance really matters here. Also the system is up-and-running now; so I prefer less fundamental solutions.
Thanks for your time. Your help is greatly appreciated.
3
Answers
Another option is to annotate the queryset to add an extra attribute for
display_secret_key
which is going to be more efficient than checking the user access level for each item in the queryset while templating.Then in your template:
Your template is rendered server-side, and the client only get the rendered markup, so yes, it is totally safe. Well, unless someone in your team messes with the template code of course 😉
Just filter the queryset in your view – you don’t need two distinct querysets, and filtering the queryset will not change it’s ordering.
and in your template:
EDIT: bdoubleu rightly mentions in his answer that his solution makes testing easier. If you only need fields from your model (no method call), you can also use QuerySet.values() instead:
This will also make your code a bit more efficient since it doesn’t have to build full model instances.
You could use 2 Templates, one for the authenticated user one for the unauthenticated. (just overwrite the get_template_names() for authentication check and add something like _sectempl.html to the found name and add the appropriate copy of the template with the secret data)
But I would say with bruno desthuilliers that if you switched off the debug mode there could be no constellation where unauthenticated users see content within
or
If you got a complex user-grouping-combination outside the standard django user right management (where you could ask for user-permissions in templates) then I would write the user_status (your “plan” or accesslevel) into the user-session (while authentication) and check for this user_status in the output-function of the attribute of the object.
Sketch:
Use in template:
In the model you create a “print_secret”-method witch returns the secret according to the previous recorded user_status in the session-data.