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