Danijela Popović
Danijela Popović

Reputation: 122

How to set different permissions depending on the request method?

I am creating an API for some polls. I need the author to be the only one who can view the votes, but the authenticated users to view the polls, questions, and to post votes. The author is just an instance of User, as like as the voters. I'm using Djoser to provide the authentication API, model serializers, and breaking my mind between CBVs and viewsets.

Here are my models, if it can help.

from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
import datetime

User = get_user_model()


class Poll(models.Model):
    title = models.CharField(max_length=200, verbose_name="Naslov ankete")
    description = models.TextField(verbose_name="Opis ankete")
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="polls", verbose_name="Autor")

    class Meta:
        verbose_name = "Anketa"
        verbose_name_plural = "Ankete"

    def __str__(self):
        return self.title


class Question(models.Model):
    poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="questions", verbose_name="Anketa")
    question_text = models.CharField(max_length=200, verbose_name="Tekst pitanja")
    pub_date = models.DateTimeField(verbose_name="Datum objavljivanja")

    class Meta:
        ordering = ["pub_date", "question_text"]
        verbose_name = "Pitanje"
        verbose_name_plural = "Pitanja"

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

    def verbose_question_text(self):
        return f"Pitanje: {self.question_text}"


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices", verbose_name="Pitanje")
    choice_text = models.CharField(max_length=200, verbose_name="Tekst opcije")
    # votes = models.IntegerField(default=0, verbose_name="Glasovi")

    class Meta:
        ordering = ["-votes", "pk"]
        verbose_name = "Opcija"
        verbose_name_plural = "Opcije"

    def __str__(self):
        return self.choice_text


class Vote(models.Model):
    choice = models.ForeignKey(Choice, on_delete=models.CASCADE, related_name="votes", verbose_name="Opcija")

    def __str__(self):
        return self.choice.choice_text

P. S.: If you think the voting could be solved in a better way, please suggest it too.

Upvotes: 1

Views: 478

Answers (1)

Pavlo Naumenko
Pavlo Naumenko

Reputation: 732

I think you can do it by creating a custom permission (extended from IsAuthenticated class) and override method has_object_permission, see documentation.

from rest_framework.permissions import IsAuthenticated


class AuthorFullAccessUserPostOnly(IsAuthenticated):
    def has_object_permission(self, request, view, obj):
        if request.method == "POST":
            return True
        # This part better to implement with query
        # Also, it will work only for Vote object. 
        # You need to extend this method if you are going to use for another objects
        return request.user == obj.choice.question.poll.author

You can create a Permission class for each view (or viewset), or extend this one, respecting an object type

If you are wanting to use different conditions for different request methods you can use some kind of this logic

class PoolPermissions(IsAuthenticated):
    def has_object_permission(self, request, view, obj):
        if request.method == "POST":
            self.has_post_object_permission(request, view, obj)
        elif request.method == "GET":
            self.has_get_object_permission(request, view, obj)
        elif request.method == "PUT":
            self.has_put_object_permission(request, view, obj)
        # etc.
        ...

    def has_get_object_permission(self, request, view, obj):
        # your logic
        pass

    def has_post_object_permission(self, request, view, obj):
        # your logic
        pass

    def has_put_object_permission(self, request, view, obj):
        # your logic
        pass


Upvotes: 1

Related Questions