Django REST Framework how to add context to a ViewSet
<p>The ViewSets do everything that I want, but I am finding that if I want to pass extra context to a template (with TemplateHTMLRenderer) then I will have to get at the functions that give responses.. (like list(), create(), etc)</p>

<p>The only way I can see to get into these is to completely redefine them in my ViewSet, but it seems that there should be an easy way to add a bit of context to the Template without having to redefine a whole set of methods...</p>

<pre><code>class LanguageViewSet(viewsets.ModelViewSet):
    """Viewset for Language objects, use the proper HTTP methods to modify them"""
    # TODO: add permissions for this view?
    queryset = Language.objects.all()
    serializer_class = LanguageSerializer
    filter_backends = (filters.DjangoFilterBackend, )
    filter_fields = ('name', 'active')
</code></pre>

<p>Right now my code is looking like this but I will be wanting to add different context to the responses and I am trying to avoid redefining an entire method for such a small change. like this...</p>

<pre><code>class LanguageViewSet(viewsets.ModelViewSet):
    """Viewset for Language objects, use the proper HTTP methods to modify them"""
    # TODO: add permissions for this view?
    queryset = Language.objects.all()
    serializer_class = LanguageSerializer
    filter_backends = (filters.DjangoFilterBackend, )
    filter_fields = ('name', 'active')

    def list(self, **kwargs):
        """Redefinition of list"""

        ..blah blah everything that list does
        return Response({"foo": "bar"}, template_name="index.html")
</code></pre>

<p>Although I disagree with 'pleasedontbelong' in principle, I agree with him on the fact that the extra contextual data ought to be emitted from the serializer. That seems to be the cleanest way since the serializer would be returning a native python data type which all renderers would know how to render.</p>

<p>Heres how it would look like:</p>

<p>ViewSet:</p>

<pre><code>class LanguageViewSet(viewsets.ModelViewSet):

    queryset = Language.objects.all()
    serializer_class = LanguageSerializer
    filter_backends = (filters.DjangoFilterBackend, )
    filter_fields = ('name', 'active')

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context['foo'] = 'bar'
        return context
</code></pre>

<p>Serializer:</p>

<pre><code>class YourSerializer(serializers.Serializer):
    field = serializers.CharField()

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        # Access self.context here to add contextual data into ret
        ret['foo'] = self.context['foo']
        return ret
</code></pre>

<p>Now, foo should be available inside your template.</p>

<p>Another way to achieve this, in case you don't wish to mess with your serializers, would be to create a custom TemplateHTMLRenderer.</p>

<pre><code>class TemplateHTMLRendererWithContext(TemplateHTMLRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        # We can't really call super in this case, since we need to modify the inner working a bit
        renderer_context = renderer_context or {}
        view = renderer_context.pop('view')
        request = renderer_context.pop('request')
        response = renderer_context.pop('response')
        view_kwargs = renderer_context.pop('kwargs')
        view_args = renderer_context.pop('args')

        if response.exception:
            template = self.get_exception_template(response)
        else:
            template_names = self.get_template_names(response, view)
            template = self.resolve_template(template_names)

        context = self.resolve_context(data, request, response, render_context)
        return template_render(template, context, request=request)

    def resolve_context(self, data, request, response, render_context):
        if response.exception:
            data['status_code'] = response.status_code
        data.update(render_context)
        return data
</code></pre>

<p>To add data into the context, ViewSets provide a <code>get_renderer_context</code> method.</p>

<pre><code>class LanguageViewSet(viewsets.ModelViewSet):

    queryset = Language.objects.all()
    serializer_class = LanguageSerializer
    filter_backends = (filters.DjangoFilterBackend, )
    filter_fields = ('name', 'active')

    def get_renderer_context(self):
        context = super().get_renderer_context()
        context['foo'] = 'bar'
        return context
</code></pre>

<p><code>{'foo': 'bar'}</code> should now be available in your template.</p>

python django django-rest-framework