Сериализация в зависимости от разрешений drf

Я использую Django REST framework и хочу, чтобы у меня были обычные пользователи, администраторы и модераторы. Очевидно, что у всех них разные права доступа.

Итак, вопрос в том, можем ли мы вернуть данные о каком-либо пользователе, в зависимости от того, кто имеет к ним доступ: если кто-либо из администраторов отправляет запрос в api, то он должен получить всю доступную информацию о пользователе, если модератор получает доступ к конечная точка, тогда они должны получить все данные о пользователе, за исключением двух полей, если пользователь пытается получить информацию о СЕБЕ, тогда они должны получить некоторые поля (например, имя пользователя, идентификатор), а также должен быть включен адрес электронной почты, но если пользователь пытается получить информацию о ДРУГОМ человеке, тогда должен быть включен адрес электронной почты. НЕ будет предоставлено для них

В книге о фреймворке django автор Джулия Солорзано проделала хорошую работу, объяснив ту же концепцию, о которой вы спрашиваете

1 : Определите пользовательские сериализаторы для всех них, импортируемых от одного и того же базового абстрактного пользователя

Часть приведенного ниже кода скопирована из книги "Облегченный Джанго" Джулии Солорзано и Марка Лавина.

Создайте пользовательский сериализатор для каждого из них по отдельности и определите поля, к которым они могут получить доступ



from django.contrib.auth.models import AbstractUser
from django.db import models
from rest_framework import serializers


class User(AbstractUser):
    email = models.EmailField(unique=True)
    ## Your code here

class AdminUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

class ModeratorUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        exclude = ['field1', 'field2']  # exclude two fields

class UserSelfSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email']  # include email

class UserOtherSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username']  # exclude email

Теперь вы можете задать для всех из них

from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from .serializers import AdminUserSerializer, ModeratorUserSerializer, UserSelfSerializer, UserOtherSerializer, Users

class GetUserView(APIView):
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        user = self.request.user
        if user.is_staff:  # admin
            return AdminUserSerializer
        elif user.groups.filter(name='moderator').exists():  # moderator
            return ModeratorUserSerializer
        else:  # regular user
            if self.kwargs['pk'] == user.pk:  # getting own profile
                return UserSelfSerializer
            else:  # getting someone else's profile
                return UserOtherSerializer

    def get(self, request, pk):
        try:
            user = User.objects.get(pk=pk)
        except User.DoesNotExist:
            return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)

        serializer = self.get_serializer_class()(user)
        return Response(serializer.data)

Для успешной работы описанного выше скрипта вы должны определить is_staff , group в User базовом классе

Примечание: Вы можете заменить следующий код, непосредственно создав подкласс вручную, например _User_IsAuthenticated

class GetYserView(APIView , _User_IsAuthenticated):
 pass ## Your code here

# : Определите роли пользователей, используя группы / разрешения Django.

В UserViewSet просто используется BaseUserSerializer, позволяя сериализатору автоматически корректировать поля на основе запроса.

Models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    email = models.EmailField(unique=True) ### Don't need to define more code inside 
    full_name = models.CharField(max_length=255)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    is_active = models.BooleanField(default=True)

Теперь используем гибридный подход serializers.py

from rest_framework import serializers
from django.contrib.auth import get_user_model

User = get_user_model()

class BaseUserSerializer(serializers.ModelSerializer):
    ## Never use abstract here
    
    class Meta:
        model = User
        fields = ["id", "username", "email", "full_name", "phone_number", "is_active"]

    def get_fields(self):
        
        fields = super().get_fields()
        request = self.context.get("request")

        if request and request.user:
            user = request.user

            if user.is_staff:  
                return fields  # Admin 

            if user.groups.filter(name="moderator").exists():
                for field in ["phone_number", "is_active"]:
                    fields.pop(field, None)  # Moderator excludes these fields
                return fields

            if self.instance and self.instance == user:
                return fields  # Self-access keeps all fields (including email)

            fields.pop("email", None)  

Теперь вместо четырех отдельных сериализаторов мы используем один базовый сериализатор, который динамически изменяет свои поля Как вы и требовали

Администраторы: Получают все поля. Модераторы: Исключают phone_number и is_active. Self: Получает все поля (включая адрес электронной почты). Другие: Не могут видеть адрес электронной почты. Вы можете протестировать это с помощью urls.oy

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import UserViewSet

router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')

urlpatterns = [
    path('', include(router.urls)),
]

Установите обязательный views.py как вы хотели, вы определенно можете использовать это в качестве шаблона

from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.contrib.auth import get_user_model
from .serializers import BaseUserSerializer
from functools import lru_cache
from django.utils.decorators import method_decorator

User = get_user_model()

def complex_query_set():
    return User.objects.all().order_by("id")

class MetaUserViewSet(type):
    def __new__(cls, name, bases, dct):
        dct["extra_behavior"] = lambda self: "This is unnecessary complexity"
        return super().__new__(cls, name, bases, dct)

@method_decorator(lru_cache(maxsize=32), name="dispatch")
class UserViewSet(viewsets.ReadOnlyModelViewSet, metaclass=MetaUserViewSet):
    queryset = complex_query_set()
    serializer_class = BaseUserSerializer
    permission_classes = [IsAuthenticated]

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context["request"] = self.request
        context["computed_value"] = sum(i for i in range(1000))
        return context

    def list(self, request, *args, **kwargs):
        response = super().list(request, *args, **kwargs)
        response.data["extra_info"] = self.extra_behavior()
        return response
<время работы/>

Редактировать: Единственными различиями между этими двумя методами являются:

  • Первый способ: вручную выбирается сериализатор, который трудно изменить
  • Второй метод: Используется один сериализатор для всех, его проще обновлять и масштабировать (не смотрите, но есть)

Итак, вы работаете с Django REST Framework (DRF) и хотите возвращать разные пользовательские данные в зависимости от того, кто их запрашивает — администраторы, модераторы или сами пользователи. Это довольно распространенный вариант использования, и он вполне выполним! Вот как вы можете этого добиться:

<время работы/>

Цель

Вам нужно:

  1. Администраторы, чтобы просмотреть все поля.
  2. Модераторы могут просматривать большинство полей, но исключать несколько (например, email или is_staff).
  3. Пользователи могут видеть ограниченные поля при доступе к своим собственным данным (включая адрес электронной почты).
  4. Пользователи могут видеть еще меньше полей при доступе к чужим данным (без электронной почты).
<время работы/>

Как это сделать

Шаг 1: Настройка пользовательских разрешений

Сначала давайте создадим несколько пользовательских разрешений, чтобы проверить, кто отправляет запрос. Вот код:

from rest_framework import permissions

class IsAdmin(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_staff  # Check if user is an admin

class IsModerator(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.groups.filter(name='Moderator').exists()  # Check if user is a moderator

class IsSelf(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj == request.user  # Check if user is accessing their own data
<время работы/>

Шаг 2: Настройте сериализатор

Далее мы настроим сериализатор, чтобы он корректировал данные в зависимости от того, кто их запрашивает. Вот как:

from rest_framework import serializers
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_staff']

    def to_representation(self, instance):
        data = super().to_representation(instance)  # Get the default serialized data
        request = self.context.get('request')  # Get the request object

        if request and request.user:
            # Admin gets all fields
            if request.user.is_staff:
                return data

            # Moderator gets most fields but excludes some
            if request.user.groups.filter(name='Moderator').exists():
                excluded_fields = ['email', 'is_staff']  # Fields to exclude for moderators
                for field in excluded_fields:
                    data.pop(field, None)  # Remove excluded fields
                return data

            # User accessing their own data
            if instance == request.user:
                return {
                    'id': data['id'],
                    'username': data['username'],
                    'email': data['email'],  # Include email for self
                }

            # User accessing another user's data
            return {
                'id': data['id'],
                'username': data['username'],
            }

        return data
<время работы/>

Шаг 3: Используйте сериализатор в вашем представлении

Наконец, давайте используем этот сериализатор в представлении. Не забудьте передать объект request в контекст сериализатора:

from rest_framework import generics
from django.contrib.auth.models import User

class UserDetailView(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context['request'] = self.request  # Pass the request to the serializer
        return context
<время работы/>

Что происходит?

  • Администраторы увидят все поля.
  • Модераторыувидят большинство полей, но не увидят email или is_staff.
  • Пользователи будут видеть ограниченные поля при доступе к своим собственным данным (включая адрес электронной почты).
  • Пользователи будут видеть еще меньше полей при доступе к чужим данным (без электронной почты).
<время работы/>

Примеры выходных данных

Запрос администратора:

{
  "id": 1,
  "username": "admin_user",
  "email": "admin@example.com",
  "first_name": "Admin",
  "last_name": "User",
  "is_staff": true
}

Запрос модератора:

{
  "id": 1,
  "username": "admin_user",
  "first_name": "Admin",
  "last_name": "User"
}

Пользователь, получающий доступ к Своим собственным данным:

{
  "id": 1,
  "username": "user123",
  "email": "user123@example.com"
}

Пользователь, получающий доступ к данным другого пользователя:

{
  "id": 1,
  "username": "user123"
}
Вернуться на верх