Сериализация в зависимости от разрешений 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) и хотите возвращать разные пользовательские данные в зависимости от того, кто их запрашивает — администраторы, модераторы или сами пользователи. Это довольно распространенный вариант использования, и он вполне выполним! Вот как вы можете этого добиться:
<время работы/>Цель
Вам нужно:
- Администраторы, чтобы просмотреть все поля.
- Модераторы могут просматривать большинство полей, но исключать несколько (например,
email
илиis_staff
). - Пользователи могут видеть ограниченные поля при доступе к своим собственным данным (включая адрес электронной почты).
- Пользователи могут видеть еще меньше полей при доступе к чужим данным (без электронной почты).
Как это сделать
Шаг 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"
}