Three ways to make Serializers update on the fly
Django Rest Framework is great. It provides all the batteries you need, and python lets you write powerful, clear, and easy-to-maintain code.
One challenge with building decoupled applications (generally, not just with DRF) is ensuring data is sent to the client with the correct permissions. Also, we may want to ensure that certain fields, that depend on expensive database queries or API calls, don't get loaded by default.
This is trivial to achieve at the object/class level (and particularly great to write with Dry-Rest-Permissions), but it's challenging to manage at the field level. Or how do you achieve allowing anonymous users to view only a subset of a given object's data?
Two standards in API design don't really help us: GraphQL and JSON-API's sparse fieldsets are all opt-in, making what I want to accomplish difficult.
Here are three approaches:
The Django Rest Serializer Field Permissions Package allows the API author to apply specific authorization classes to a specific field.
class PersonSerializer(FieldPermissionSerializerMixin, LookupModelSerializer): // Only allow authenticated users to retrieve family and given names family_names = serializers.CharField(permission_classes=(IsAuthenticated(), )) given_names = serializers.CharField(permission_classes=(IsAuthenticated(), )) // Allow all users to retrieve nick name nick_name = serializers.CharField(permission_classes=(AllowAll(), ))
If you want to provide a custom serializer — to reuse admin-specific fields — you can implement logic in the ViewSet's
get_serializer_class method to swap out serializers based on the
class GroupViewSet(OwnerBasedViewSet): def get_serializer_class(self): user = self.request.user return AdminSerializer if request.user.is_admin else NormalSerializer
This was is great for ensuring that external relationships and other sensitive fields that can be easy to forget are not exposed.
Following the convention of JSON:API, but not wanting to use the opt-in only approach of sparse-fieldsets, I wrote/copied a serializer mixin that parses the
included query param for a list of keys, then grabs them from a
DynamicIncluded class setup on the Serializer.
The mixin looks like:
class DynamicIncludedFieldsMixin(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.DynamicIncluded: raise NotImplementedError("Must provide a `DynamicIncluded` class") if kwargs.get('context', False): request = kwargs['context'].get('request', False) included = request.GET.get('include', False) if request and included: included_props = included.split(',') dynamic_fields = self.DynamicIncluded.__dict__ for prop in included_props: if dynamic_fields.get(prop, False): self.fields[prop] = dynamic_fields.get(prop)``` and the usage looks like ```py class MySerializer(serializer.Serializer): prop_one = serializer.CharField() def dynamic_method_field(): return fibonnaci_sequence_n(1000) DynamicIncluded: fibonnanci = serizlizer.SerializerMethodField(method_name="dynamic_method_field")