Drf permissions' depending serialization

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

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)

So, you're working with Django REST Framework (DRF) and want to return different user data depending on who's asking—admins, moderators, or the users themselves. That's a pretty common use case, and it’s totally doable! Here’s how you can make it happen:


The Goal

You want:

  1. Admins to see all fields.
  2. Moderators to see most fields but exclude a few (like email or is_staff).
  3. Users to see limited fields when accessing their own data (including their email).
  4. Users to see even fewer fields when accessing someone else’s data (no email).

How to Do It

Step 1: Set Up Custom Permissions

First, let’s create some custom permissions to check who’s making the request. Here’s the code:

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

Step 2: Customize the Serializer

Next, we’ll tweak the serializer to adjust the data based on who’s asking. Here’s how:

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

Step 3: Use the Serializer in Your View

Finally, let’s use this serializer in a view. Make sure to pass the request object to the serializer context:

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

What Happens?

  • Admins will see all fields.
  • Moderators will see most fields but won’t see email or is_staff.
  • Users will see limited fields when accessing their own data (including their email).
  • Users will see even fewer fields when accessing someone else’s data (no email).

Example Outputs

Admin Request:

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

Moderator Request:

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

User Accessing Their Own Data:

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

User Accessing Another User’s Data:

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