Pass request context to serializer from Viewset in Django Rest Framework
Asked Answered
U

8

116

I have a case where the values for a serializer field depend on the identity of the currently logged in user. I have seen how to add the user to the context when initializing a serializer, but I am not sure how to do this when using a ViewSet, as you only supply the serializer class and not the actual serializer instance.

Basically I would like to know how to go from:

class myModelViewSet(ModelViewSet):
   queryset = myModel.objects.all()
   permission_classes = [DjangoModelPermissions]
   serializer_class = myModelSerializer

to:

class myModelSerializer(serializers.ModelSerializer):
    uploaded_by = serializers.SerializerMethodField()
    special_field = serializers.SerializerMethodField()

    class Meta:
        model = myModel

    def get_special_field(self, obj):
        if self.context['request'].user.has_perm('something.add_something'):
           return something

Sorry if it wasn't clear, from the DOCs: Adding Extra Context Which says to do

serializer = AccountSerializer(account, context={'request': request})
serializer.data

But I am not sure how to do that automatically from the viewset, as I only can change the serializer class, and not the serializer instance itself.

Uncommon answered 24/6, 2015 at 23:19 Comment(5)
Have you tried self.context.get('request').user.has_perm("some.permission") ?Row
Do you want something to be returned with other fields of serializer in the output?Juliajulian
Sorry if it wasn't clear, the problem is that self.context is empty in the serializer.Uncommon
AccountSerializer(account, context={'request': request}) was all I needed for my purposeScratch
citynorman Where do u define request? I try to define request as: context={'request': { 'user': self.admins[0].user }} and it doesn't work!Liberalize
G
175

GenericViewSet has the get_serializer_context method which will let you update context:

class MyModelViewSet(ModelViewSet):
    queryset = MyModel.objects.all()
    permission_classes = [DjangoModelPermissions]
    serializer_class = MyModelSerializer

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context.update({"request": self.request})
        return context

For Python 2.7, use context = super(MyModelViewSet, self).get_serializer_context()

Grind answered 30/7, 2015 at 13:7 Comment(8)
Selecting this as the best answer. I believe this would have solved my problem (if this is what my problem actually was). Turns out I missed the fact that I was manually instantiating the serializer in this case, and it wasn't being routed by the viewset. D'oh.Uncommon
for me it says : NameError: name 'request' is not definedEllenaellender
@Ellenaellender did you fix that? That's what I get :/ context is not passed to serializerPneumothorax
@Bradley Neil Reed Do you have self.request?Gorey
The latest DRF includes self.request in the default context sent to serializers; you don't need to manually add it in.Scrimshaw
For everyone who's getting the request KeyError: You can solve this is by initializing the serializer from the view with the request object as follows: serializer = serializers.RandomSerializer(data=request.data, context={'request':request}) Source: django-rest-framework.org/api-guide/serializers/…Thyroid
@Syslo, please could you help me look into this, it is a similar question but the solution here doesn't work for me. #62039805Kbp
Use self.context.request in 3.12.4.Imperative
C
21

For Function based views you can pass request or user as follows:

serializer = ProductSerializer(context = {"request": request}, data=request.data)

Your Serializer may look like:

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ["id"]

    def create(self, validated_data):
        user = self.context["request"].user
        print(f"User is: {user}")

Feel free to inform if there is any better way to do this.

Cloison answered 30/7, 2020 at 14:45 Comment(0)
L
3

just use get_serializer() in your viewsets

def get_serializer(self, *args, **kwargs):
    """
    Return the serializer instance that should be used for validating and
    deserializing input, and for serializing output.
    """
    serializer_class = self.get_serializer_class()
    kwargs['context'] = self.get_serializer_context()
    return serializer_class(*args, **kwargs)
Lafollette answered 12/10, 2016 at 13:12 Comment(1)
your ViewSet should be subclass of GenericViewSetLafollette
D
2

Return parent context in overrided function get_serializer_context will make it easy to access request and its data.

 class myModelViewSet(ModelViewSet):
       queryset = myModel.objects.all()
       permission_classes = [DjangoModelPermissions]
       serializer_class = myModelSerializer

       def get_serializer_context(self):
       """
       pass request attribute to serializer
       """
           context = super(myModelViewSet, self).get_serializer_context()
           return context

This is very stable as every time we request viewset, it returns context as well.

Diapositive answered 8/1, 2016 at 5:50 Comment(0)
S
1

the values for a serializer field depend on the identity of the currently logged in user

This is how I handle such cases in my ModelViewSet:

def perform_create(self, serializer):

    user = self.request.user
    if user.username == 'myuser':
        serializer.data['myfield'] = 'something'

    serializer.save()
Septi answered 2/10, 2016 at 17:57 Comment(0)
E
1

Simply add this 2 line method in your class and you are good to go.

def get_serializer_context(self):
    return {'request': self.request}
Excruciating answered 29/1, 2022 at 19:44 Comment(0)
F
1

since the posted answers had partial correctness, summarizing here in the interest of completeness.

  1. override get_serializer_context..AND
  2. use get_serializer in your views instead of manually calling the serializer
Floatplane answered 10/5, 2022 at 21:13 Comment(0)
D
0

In my case, I could fix it by replacing the serializer definition

class MyListView(APIView):
    serializer_class = YourSerializerClass

    def get(self, request):
        queryset = self.get_queryset()
        serializer = self.serializer_class(queryset, many=True) #replace this
        return Response(serializer.data)

replace the serializer with

serializer = self.get_serializer(queryset, many=True)
Doi answered 31/8, 2023 at 7:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.