Обеспечение прав доступа через связанную модель в Django Rest Framework

Я работаю над созданием разрешений для API, построенного с помощью Django REST Framework. Допустим, у меня есть следующие модели:

from django.db import models

class Study(models.Model):
   pass

class Result(models.Model):
   study = models.ForeignKey(Study)
   value = models.IntegerField(null=False)

У меня есть базовые сериализаторы и представления для обеих этих моделей. Я буду использовать разрешения на каждый объект для предоставления пользователям доступа к одному или нескольким исследованиям. Я хочу, чтобы пользователи могли просматривать Result только те Study, на которые у них есть права. Я могу предложить два способа сделать это, но ни один из них не кажется идеальным:

  1. Keep per-object permissions on Results in sync with Study. This is just a non-starter since we want Study to always be the source of truth.
  2. Write a custom permissions class which checks permissions on the related Study when a user tries to access a Result. This actually isn't too bad, but I couldn't find examples of others doing it this way and it got me thinking that I may be thinking about this fundamentally wrong.

Есть ли уже существующие решения для этого? Или же необходимо создать собственный класс разрешений? Если да, то есть ли у вас примеры других людей, которые реализовали этот способ?

Я следовал 2-му методу, который вы упомянули, используя BasePermission, например:

class ReadResult(permissions.BasePermission):
    

    def has_permission(self, request, view):
        if request.user.is_authenticated:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        # your conditions here return true or false 
        # returning false will raise permission denied error
        return True 

Третье решение - использование метода "to_representation()" в вашем сериализаторе. Если пользователь аутентифицирован, значит, вы уже знаете его/ее. В этом случае легко проверить, имеет ли он/она доступ к ресурсу.

class ResultSerializer():
    def to_representation(self, instance):
        if instance.study_id == self.user.id:
            return {"id":instance.id, "xxxx":instance.xxx}
        else:
            return {"detail":"There is not any result"}

Как вы указали, вы можете сделать пользовательское разрешение в соответствии со вторым способом: И включить разрешение в ваше представление:

from rest_framework import permissions

class ResultOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS and obj.study.exists()  :
            # read only requests
            return True
        else:
            return obj.study == request.user.some_request_study_paramter

И включить,

class ReviewDetail(viewsets.ViewSet):

     permission_classes =[ResultOrReadOnly]

Я бы создал новое поле enrolled_users в Study модели, чтобы указать, какой пользователь имеет доступ к определенному Study объекту.

from django.db import models
from django.conf import settings


class Study(models.Model):
    enrolled_users = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name="studies"
    )
    # other fields


class Result(models.Model):
    study = models.ForeignKey(Study)
    value = models.IntegerField(null=False)

Тогда в DRF будет очень легко фильтровать набор запросов в представлениях

# views.py
from .models import Study, Result
from rest_framework.permissions import IsAuthenticated


class StudyModelViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        return Study.objects.filter(enrolled_users=self.request.user)


class ResultModelViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        return Result.objects.filter(study__enrolled_users=self.request.user)

Примечания

  • Это будет обрабатывать запрос разрешения объекта (Detail View) также
  • .
  • Это вернет код состояния не HTTP 403, а HTTP 404
  • .
  • Здесь я использовал класс ModelViewSet, но, вы можете использовать любые представления, но фильтр играет роль
  • .
Вернуться на верх