Reputation: 554
I'd like to restrict access to some detail article pages that have an attribute is_private=True
. They can be displayed in article list, but only users who have a permission view_private_articles
must be able to access article DetailView.
models.py:
class Article(models.Model):
title = models.CharField(
max_length=150,
)
is_private = models.BooleanField(
default=False,
)
views.py:
class ArticleListView(LoginRequiredMixin,ListView):
model = Article
template_name = 'article_list.html'
context_object_name = 'article_objects_list'
login_url = 'login'
class Meta:
permissions = [
("view_private_articles", "Can view private articles"),
]
def __str__(self):
return self.value
class ArticleDetailView(
LoginRequiredMixin,
PermissionRequiredMixin,
DetailView):
model = Article
context_object_name = 'article_object'
template_name = 'detail/article_detail.html'
login_url = 'login'
permission_required = 'view_private_articles'
Problem is, as you could notice, that approach, described here can only restrict users who don't have view_private_articles
permission to view ALL ARTICLES (not only with is_private=True
attribute).
So how to restrict that users to view only articles with attribute is_private=True
?
UPDATE01 I've tried to add UserPassesTestMixin with no result:
class ArticleDetailView(
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin,
DetailView):
model = Article
context_object_name = 'article_object'
template_name = 'detail/article_detail.html'
login_url = 'login'
permission_required = 'view_private_articles'
def test_func(self):
article_obj = self.get_object()
user_obj = self.request.user
if article_obj.is_private is True and user_obj.has_perms("view_private_articles"):
return True
else:
return False
I am still get 403 forbidden if my user hasn't view_private_articles
permission regardless of whether there is an attribute is_private
or not because of PermissionRequiredMixin. But if i delete PermissionRequiredMixin, my user without view_private_articles
permission can access to detail article. Obviously method i wrote is crap, but how to get it right?
UPDATE02 it's works like that:
class ArticleDetailView(
LoginRequiredMixin,
UserPassesTestMixin,
DetailView):
model = Article
context_object_name = 'article_object'
template_name = 'detail/article_detail.html'
login_url = 'login'
permission_required = 'view_private_articles'
def test_func(self):
article_obj = self.get_object()
user_obj = self.request.user
if article_obj.is_private is True and user_obj.has_perm("blog.view_private_articles"):
return True
return False
where blog - name of the app containing model . Thank to @dirkgroten!
UPDATE03 also it can be done by view method dispatch
overriding:
from django.core.exceptions import PermissionDenied
class ArticleDetailView(
LoginRequiredMixin,
DetailView):
model = Article
context_object_name = 'article_object'
template_name = 'detail/article_detail.html'
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
user_obj = self.request.user
if not user_obj.has_perm("blog.view_private_articles"):
raise PermissionDenied
return super().dispatch(request,*args,**kwargs)
Upvotes: 2
Views: 1977
Reputation: 20682
You probably also don't want to show all articles in your ListView
. So there are two different approaches for the ListView
and the DetailView
.
For the ListView
you should override get_queryset()
. Here you can send back different lists depending on the user:
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs)
if self.request.user.is_staff:
return qs
if self.request.user.has_perm("view_private_articles"):
return qs.filter(is_private=True)
return qs.none()
For the DetailView
, add a UserPassesTestMixin
to your view and use get_object()
to verify the user is allowed to view the specific object:
def test_func(self):
return self.request.user.may_view(self.get_object()) # assuming you write a `may_view(article)` method on a `User` model.
Upvotes: 7