gek
gek

Reputation: 554

How to restrict access to DetailView depending on user permission AND MODEL ATTRIBUTE VALUE?

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

Answers (1)

dirkgroten
dirkgroten

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

Related Questions