kstr
kstr

Reputation: 119

Restrict User from view based on existing model

I have a few really simple views in my views.py.

class IndexView(generic.ListView):
    template_name = 'voting/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """
        Return the last five published questions (not including those set to be
        published in the future).
        """    
        return Poll.objects.filter(
            pub_date__lte=timezone.now()
        ).order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Poll
    template_name = 'voting/detail.html'
    context_object_name = 'question'

    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Poll.objects.filter(pub_date__lte=timezone.now())


class ResultsView(generic.DetailView):
    model = Poll
    template_name = 'voting/results.html'
    context_object_name = 'question'

I also have created a model where I store which users are invited to which polls along with some other data.

class EligibleVoters(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    poll = models.ForeignKey(Poll, on_delete=models.CASCADE, null=True)
    encrypted_keypart = models.BinaryField(max_length=200, blank=True)
    decrypted_keypart = models.BinaryField(max_length=200, blank=True)
    class Meta:
        unique_together = ["user", "poll"]

I want to restrict users that aren't invited to a poll from seeing any of these polls.

I think that what I want to do is something like this for each view, but I'm not sure if that's the correct way to go.

class DetailView(generic.DetailView):
    model = Poll
    template_name = 'voting/detail.html'
    context_object_name = 'question'

    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        if EligibleVoters.objects.filter(poll=Poll.objects.id, user=self.request.user.id).exists():
            return Poll.objects.filter(pub_date__lte=timezone.now())
        else:
            return render('voting/somethingsomething.html')
            }) 

Should I restrict access to specific polls that way? Also the above code doesn't really work and gives some errors but I'm not sure if I should continue and try to fix it that way.

Upvotes: 1

Views: 324

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476537

Filtering the queryset on eligblevoters

The get_queryset does not render the output, it only produces a queryset.

Nevertheless, we can use this to restrict access, by adding extra filtering:

class DetailView(generic.DetailView):
    model = Poll
    template_name = 'voting/detail.html'
    context_object_name = 'question'

    def get_queryset(self):
        return super(DetailView, self).get_queryset().filter
            eligiblevoters__user=self.request.user,
            pub_date__lte=timezone.now()
        )

Similar approaches should be used in the other views.

How does this work

The query works as follows. By defining a ForeignKey from EligibleVoters to User (named user), then Django creates an implicit relation in reverse: you can for example query with User.eligblevoters_set to obtain the related EligableVoters queryset. We can also filter on it with elgiblevoters.

We thus add two extra filter conditions: pub_date__lte=timezone.now() to filter posts that are published before now (now included), and eligiblevoters__user=self.request.user. This means that we add a constraint that at least one of the related eligablevoters should have as user the self.request.user (the user of this specific session).

So in case there is no such Post, then we can not get the Poll with the id that is requested.

Render in case the object is not found

We can also render a page in case the object is not found, for example by patching the .get(..) function with a try-except with Http404, and then render a specific page, for example:

from django.http import Http404

class DetailView(generic.DetailView):
    model = Poll
    template_name = 'voting/detail.html'
    context_object_name = 'question'

    def get_queryset(self):
        return super(DetailView, self).get_queryset().filter
            eligiblevoters__user=self.request.user,
            pub_date__lte=timezone.now()
        )

    def get(request, *args, **kwargs):
        try:
            return super(DetailView, self).get(request, *args, **kwargs)
        except Http404:
            return render(request, 'app/template_not_found.html', {})

So here we render a (possibly different template) with here an empty context, although you can of course make this more advanced.

Upvotes: 2

Daniel Roseman
Daniel Roseman

Reputation: 599470

You should add a many-to-many field from Poll to User, using EligibleVoter as a through field:

eligible_users = models.ManyToManyField('User', through='EligibleVoter')

Now you can do:

return Poll.objects.filter(eligible_users=self.request.user, pub_date__lte=timezone.now())

Upvotes: 2

Related Questions