Serializer context¶
One dict per request¶
Over HTTP the view builds one context dict per request, holding DRF's
request, view and format, the viewset's action, and every key the
view's get_serializer_context() adds. The input serializer, the output
serializer and the service's call_context are all handed that same dict, as
one serializer validating and rendering in a stock ModelViewSet sees one
context:
from rest_framework import serializers
class InvoiceInput(serializers.Serializer):
amount_cents = serializers.IntegerField(min_value=0)
def validate(self, attrs):
if attrs["amount_cents"] == 0:
self.context.setdefault("warnings", []).append("The amount is zero.")
return attrs
class InvoiceSerializer(serializers.ModelSerializer):
warnings = serializers.SerializerMethodField()
def get_warnings(self, invoice):
return list(self.context.get("warnings", []))
def create_invoice(*, data, call_context):
call_context.get("warnings") # the same list
The example project asserts the warning validate() writes is rendered in the
answer.
format is the route's: on a route with a format suffix, format is the
suffix even where an overriding get_serializer_context() answers another
value for it.
Keys a spec adds¶
SerializerValidator(serializer_context=...) and
SerializerPresenter(serializer_context=...) take a callable returning keys for
their serializer. Over HTTP the validator's keys are written into the request's
one dict, so the service and the output serializer see them too.
CREATE_SPEC = ServiceSpec(
service=create_invoice,
validator=SerializerValidator(InvoiceInput, serializer_context=lambda: {"currency": "EUR"}),
presenter=SerializerPresenter(InvoiceSerializer),
permissions=OPEN,
)
What a presenter's callable receives¶
A presenter's serializer_context callable declares what it reads by name:
request, view, user, call_context, a registered seed, and the value
being rendered.
| Rendering | Asked | The value, by name |
|---|---|---|
| one value: a service's output, a retrieve | once | under value_keyword: result by default, instance where the presenter names it so |
a list: a page's rows, a many service's return |
once for the list, not once per row | the rows under page, and under value_keyword too |
So a callable on a list view can batch one lookup over the page's rows and hand the answer to every row through the context:
def comment_counts(*, page):
ids = [post.pk for post in page]
counts = dict(
Comment.objects.filter(post_id__in=ids).values_list("post_id").annotate(n=Count("id"))
)
return {"comment_counts": counts}
class PostSerializer(serializers.ModelSerializer):
comments = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ("id", "title", "comments")
def get_comments(self, post):
return self.context["comment_counts"].get(post.pk, 0)
POSTS = SelectorSpec(
kind=SelectorKind.LIST,
selector=list_posts,
permissions=OPEN,
presenter=SerializerPresenter(PostSerializer, serializer_context=comment_counts),
)
The query runs once a page, whatever the page size. page holds the rows
being rendered, already evaluated, as a list: on a paginated view only the
page's rows, never DRF's Page object, and never a queryset to filter
further. A presenter with value_keyword=None offers the rows under page
alone. A many service's callable declaring result receives its whole
return, as the view's get_output_serializer_context(result) does.
The rows render as one many=True serializer, so a Meta.list_serializer_class
and its to_representation run on a list as behind a DRF view. Its answer
keeps one row per value, in order: affordances are added to each row by its
position, and a list of another length is refused.
Keys a view adds¶
A mutation view's hooks layer keys over DRF's context, view-wide then per action:
| Serializer | View-wide | Per action |
|---|---|---|
| input | get_input_serializer_context() |
get_<action>_input_serializer_context() |
| output | get_output_serializer_context() |
get_<action>_output_serializer_context() |
Both view-wide hooks default to get_serializer_context(). A selector spec's
presenter is not handed the per-action output hook: declare its context on its
SerializerPresenter.
Off HTTP¶
Dispatched with no request, a seat builds its serializer over an offline
request and view for the principal, so a serializer reading
self.context["request"].user while it validates reads the principal. Off
HTTP the kernel also describes the serializer's fields before the call, with
no request in its context: a get_fields() or __init__ reading
self.context["request"] raises there, so guard such a read.
base_serializer_context
and
build_offline_context
build that context for code of your own that renders a serializer outside a
view.