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

django, django-rest-framework, python, serialization

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.

Problem

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'), ```

Original source