I've recently stumbled into a simliar pattern twice where I want to configure a list such that one can pass in partial objects that only contain a primary key, but have fleshed out objects in the response. In the example below we're creating (or PUT-ing) an object that has relations to already existing entities, and we don't want to allow the client to alter the related entities in this request, but we want the client to conveniently be able to get the full objects in the response.
For lack of a better name, I'm calling this an "augmented list" for now. Suggestions are welcome.
class AugmentedList(ListField):
def __init__(self, child: Serializer, method_name: str | None = None, writable_field: str = "id") -> None:
super().__init__(source="*", child=child, default=list)
self.method_name = method_name
self.writable_field = writable_field
def to_internal_value( # type: ignore[override]
self, data: Iterable[dict[str, int]]
) -> dict[str, Iterable[int]]:
assert self.field_name is not None
return {self.field_name: [item[self.writable_field] for item in data]}
# Stolen from SerializerMethodField
def bind(self, field_name, parent):
# The method name defaults to `get_{field_name}`.
if self.method_name is None:
self.method_name = 'get_{field_name}'.format(field_name=field_name)
super().bind(field_name, parent)
# Stolen from SerializerMethodField and modified to use self.child for serialization and to not pass
# any value to the method.
def to_representation(self, value):
method = getattr(self.parent, self.method_name)
return [
type(self.child)(instance=child_instance).data
for child_instance in method()
]
The above specialized list field allows us to achieve the behavior we want, and would be used like this:
class TagSerializer(Serializer):
id = IntegerField()
name = CharField()
class HasTags(Serializer):
tags = AugmentedList(child=TagSerializer())
def get_tags(self):
return Tag.objects.filter(...=self.instance.pk)
(The above code is untested as-is, but all the concepts are fully working in separate places at the moment).
I've recently stumbled into a simliar pattern twice where I want to configure a list such that one can pass in partial objects that only contain a primary key, but have fleshed out objects in the response. In the example below we're creating (or PUT-ing) an object that has relations to already existing entities, and we don't want to allow the client to alter the related entities in this request, but we want the client to conveniently be able to get the full objects in the response.
Example request
Example response
Solution
For lack of a better name, I'm calling this an "augmented list" for now. Suggestions are welcome.
The above specialized list field allows us to achieve the behavior we want, and would be used like this:
(The above code is untested as-is, but all the concepts are fully working in separate places at the moment).