最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - drf permissions' depending serialization - Stack Overflow

programmeradmin0浏览0评论

I'm using Django REST framework and want to have a standard users, admins and moderators. All of them have different permissions obviously.

So, the question is, can we return the data about some user, depending on who's accessing to it: if any of admins sending the request to the api, then they should get all of the available information on the user, if a moderator is accessing the endpoint, then they should get all data on the user excluding two fields, if user is trying to get information about THEMSELVES, then they should get some fields (e.g. username, id) and also email should be included, but if user's trying to get information about the OTHER person, then email should NOT be provided for them

I'm using Django REST framework and want to have a standard users, admins and moderators. All of them have different permissions obviously.

So, the question is, can we return the data about some user, depending on who's accessing to it: if any of admins sending the request to the api, then they should get all of the available information on the user, if a moderator is accessing the endpoint, then they should get all data on the user excluding two fields, if user is trying to get information about THEMSELVES, then they should get some fields (e.g. username, id) and also email should be included, but if user's trying to get information about the OTHER person, then email should NOT be provided for them

Share Improve this question asked Mar 2 at 10:22 Doule_Doule_ 694 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

In django framework book the author Julia Solórzano did a good work explaining the same concept you are asking for

1 : Define Custom Searlizers for all of them importing from same base Abstract User

Some of the following code is copied from the book Lightweight Django by Julia Solórzano and Mark Lavin

Create a custom Searlizers for all of them separately and define the fields they can access



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

Now you can define to all of them

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)

To successfully work the above script you must define is_staff , group in User base class

Note : You can replace the following code by directly creating a manual subclass like _User_IsAuthenticated

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

# : Define user roles using Django's groups/permissions.

The UserViewSet simply uses BaseUserSerializer, letting the serializer automatically adjust fields based on the request.

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)

Now use hybrid approach searlizers.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)  

Now instead of four separate serializers, we use one base serializer that dynamically modifies its fields As you demanded

Admins: Get all fields. Moderators: Excludes phone_number and is_active. Self: Gets all fields (including email). Others: Cannot see email. You can test it with 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)),
]

Set a required views.py as you wanted you can definitely use this as template

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

Edit : Only differences between these two methods are :

  • First Method: Manually picks a serializer , Hard to modify
  • Second Method : Uses one serializer for all , Easier to update and scale (Don't look but is)
发布评论

评论列表(0)

  1. 暂无评论