Search code examples
pythondjangoserializationdjango-rest-framework

In a Django serializer, take default field value from context (or request data)


I'm using Django with the REST Framework. In a serializer, I would like to assign a field value based on a view or request (request.data['type']) parameter, so I need the view/request in the context.

I succeeded, but only in a cumbersome way, and I am looking into ways to simplify the code. Here's the successful approach (omitting irrelevant fields):

class TypeDefault(object):
    def set_context(self, serializer_field):
        view = serializer_field.context['view'] # or context['request']

        self.type = view.kwargs['type'].upper()

    def __call__(self):
        return self.type


class RRsetSerializer(serializers.ModelSerializer):
    type = serializers.CharField(read_only=True, default=serializers.CreateOnlyDefault(TypeDefault()))

    class Meta:
        model = RRset
        fields = ('type',)
        read_only_fields = ('type',)

To simplify things, I tried removing the TypeDefault class, and replacing the type serializer field by

    type = serializers.SerializerMethodField()

    def get_type(self, obj):
        return self.context.get('view').kwargs['type'].upper() # also tried self._context

However, context.get('view') returns None. I am unsure why the view context is not available here. My impression is that it should be possible to get the desired functionality without resorting to an extra class.

As a bonus, it would be nice to specify the default in the field declaration itself, like

    type = serializers.CharField(default=self.context.get('view').kwargs['type'].upper())

However, self is not defined here, and I'm not sure what the right approach would be.


Also, I am interested if there is any difference in retrieving information from the view or from the request data. While the context approach should work for both, maybe there's a simpler way to get the CreateOnlyDefault functionality when the value is obtained from request data, as the serializers deals with the request data anyways.


Edit: Per Geotob's request, here is the code of the view that calls the serializer:

class RRsetsDetail(generics.ListCreateAPIView):
    serializer_class = RRsetSerializer
    # permission_classes = ... # some permission constraints

    def get_queryset(self):
        name = self.kwargs['name']
        type = self.kwargs.get('type')

        # Note in the following that the RRset model has a `domain` foreign-key field which is referenced here. It is irrelevant for the current problem though.
        if type is not None:
            return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk, type=type)
        else:
            return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)

In urls.py, I have (among others):

url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets'),
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/(?P<type>[A-Z]+)/$', RRsetsDetail.as_view(), name='rrsets-type'),

Solution

  • SerializerMethodField is a read-only field so I do not think it will work unless you set a default value... and you are back to the same problem as with CharField.

    To simply things you could get rid of serializers.CreateOnlyDefault:

    class RRsetSerializer(serializers.ModelSerializer):
        type = serializers.CharField(read_only=True, default=TypeDefault())
    

    If you want something more dynamic, I can only think of something like this:

    class FromContext(object):
        def __init__(self, value_fn):
            self.value_fn = value_fn
    
        def set_context(self, serializer_field):
            self.value = self.value_fn(serializer_field.context)
    
        def __call__(self):
            return self.value
    
    
    class RRsetSerializer(serializers.ModelSerializer):
        type = serializers.CharField(read_only=True,
                default=FromContext(lambda context: context.get('view').kwargs['type'].upper()))
    

    FromContext takes a function during instantiation that will be used to retrieve the value you want from context.