title: Customize DjangoRestFramework Browsable API
body: <p>I'm using Django Rest Framework 3.11.0 and I want to use the BrowsableAPIRenderer with a customized template for rendering the details of an instance. I only want to override the rendering of the dict/json, marked red in the image below, and I want to keep all the rest.</p>
  
  <p>By overwriting <code>restframework/api.html</code> I only managed to change the title, heading and some fields, but I did not find a way to render the details of an instance e.g. in a table. Is there a way to do this?</p>
  
  <p><strong>Clarification</strong>: I have models with large dictionaries that I want to display prettier than just as inline strings. I thin that when I find out how to customize the (already beautiful) Django RestFramework BrowsableAPI, I will also be able to solve my problem.</p>
  
  <p>(Look at my Update 2 in case you want to solve a similar problem.)</p>
  
  <p><a href="https://i.stack.imgur.com/jW3TY.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/jW3TY.png" alt="Screenshot"></a></p>
  
  <hr>
  
  <h2>Update 1</h2>
  
  <p>This is where I got with <a href="https://stackoverflow.com/a/60599661">Bedilbeks answer</a> (up to the first update). </p>
  
  <p>I don't want to change all views, so I'm not registering the renderer globally.</p>
  
  <pre><code>REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES': [
          'rest_framework.renderers.JSONRenderer',
          'rest_framework.renderers.BrowsableAPIRenderer',
           # 'users.renderers.CustomBrowsableAPIRenderer',
      ]
  }
  </code></pre>
  
  <p>Instead I am setting the <code>renderer_classes</code> for my <code>UserViewSet</code> and use my <code>CustomBrowsableAPIRenderer</code> here.</p>
  
  <pre><code>class UserViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
      queryset = UserModel.objects.all()
      serializer_class = UserSerializer
      renderer_classes = [renderers.JSONRenderer, CustomBrowsableAPIRenderer]
  </code></pre>
  
  <p>I need to override the <code>api.html</code> template, but I do not want this change to apply everywhere, so I am dynamically choosing the the template in the renderer. By default the <code>BrowsableAPIRenderer</code> has a <code>template = "rest_framework/api.html"</code> property, but I need logic so I'm using the <code>@property</code> decorator to do the following:</p>
  
  <ul>
  <li>check if we are in <code>detail</code> view </li>
  <li>check GET parameters</li>
  </ul>
  
  <p>If we are in detail view and a <code>"table"</code> parameter is present, return my template, else return the default.</p>
  
  <pre><code>class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
      @property
      def template(self):
          view = self.renderer_context.get("view", {})
          table = "table" in view.request.query_params
          if view and hasattr(view, "detail") and view.detail and table:
              return "users/api.html"  # custom template
          else:
              return "rest_framework/api.html"  # default
  
      def get_default_renderer(self, view):
          table = "table" in view.request.query_params
          if hasattr(view, "detail") and view.detail and table:
              return TableHtmlRenderer()
  
          return super().get_default_renderer(view)
  </code></pre>
  
  <p>The crucial section of <code>api.html</code> looks like this (around line 123).</p>
  
  <pre><code>...
  {% block style %}
      {{ block.super }}
      &lt;link rel="stylesheet" type="text/css" href="{% static "css/api.css" %}"/&gt;
  {% endblock %}
  
  &lt;!-- HERE IS THE ACTUAL CONTENT --&gt;
  &lt;/span&gt;&lt;/pre&gt;&lt;div class="prettyprint" style="overflow: auto;"&gt;{{ content|urlize_quoted_links }}&lt;/div&gt;
              &lt;/div&gt;
  ...
  </code></pre>
  
  <p>I'm actually not doing this for the <code>User</code> model and ViewSet, but I'm sticking to it for the sake of the example. In my model, I have larger JSON elements that I want to render, so I am doing some preprocessing in my <code>TableHTMLRenderer</code> to return JSON in indented form.</p>
  
  <pre><code>class TableHtmlRenderer(TemplateHTMLRenderer):
      media_type = "text/html"
      format = "api"
      template_name = "table_template.html"
  
      def get_template_context(self, data, renderer_context):
          for key in data.keys():
              try:
                  data[key] = json.dumps(json.loads(data[key]), indent=4)
              except (JSONDecodeError, TypeError):
                  pass
  
          context = {
              "data": data
          }
  
          response = renderer_context["response"]
          if response.exception:
              context["status_code"] = response.status_code
  
          return context
  </code></pre>
  
  <p>So controlled by the URL, I can switch between the default renderer and the Custom/Table renderer.</p>
  
  <ul>
  <li>localhost.me:8000/api/users/1/<strong>?table</strong></li>
  </ul>
  
  <p><a href="https://i.stack.imgur.com/T7BzJ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/T7BzJ.png" alt="http://localhost.me:8000/api/users/1/?table"></a></p>
  
  <ul>
  <li>localhost.me:8000/api/users/1/</li>
  </ul>
  
  <p><a href="https://i.stack.imgur.com/v8Zog.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/v8Zog.png" alt="http://localhost.me:8000/api/users/1/"></a></p>
  
  <p>So far so good, I now have my own Renderer classes and I can modify how the API view for my User instance looks. I'm still struggling with the table, because line-breaks on long lines don't work and it won't stay inside of the div's boundaries.</p>
  
  <p>Here is the <code>app.css</code> which is loaded in the <code>api.html</code> template.</p>
  
  <pre><code>pre.inline {
      padding: 0;
      border: none;
      word-break: break-all;
      word-wrap: break-word;
      display: contents;
  }
  
  table, th, td {
      vertical-align: top;
      padding: 2px;
      text-align: left;}
  
  table {
      //table-layout: fixed;
      width: 100% !important;
      word-wrap:break-word;
  }
  
  th, td {
      border-bottom: 1px solid #ddd;
      overflow: auto;
      width: 100%;
  }
  
  tr:hover {
      background-color: #f2f2f2;
  }
  
  tr:nth-child(even) {
      background-color: #f5f5f5;
  }
  </code></pre>
  
  <hr>
  
  <h2>Update 2</h2>
  
  <p>Since I can now display some views with a customized BrowsableAPIRenderer and templates with quite a few hacks, I came back to the problem that lead me to this question. I wanted to get to know how DRF renders my models, to make changes in order to display large nested dictionaries.</p>
  
  <p>I found out that the <code>BrowsableAPIRenderer</code> inserts the model contents as a single string int he <code>api.html</code> template like this <code>{{ content|urlize_quoted_links }}</code> inside of <code>&lt;pre&gt;</code> tags.
  The insertion takes place in the <code>BrowsableAPIRenderer.get_content</code> method.</p>
  
  <pre><code># original code
  renderer_context['indent'] = 4
  content = renderer.render(data, accepted_media_type, renderer_context)
  </code></pre>
  
  <p>I now see that all I had to do is subclass <code>BrowsableAPIRenderer</code> and override the <code>get_content</code> method. I'm doing it like this.</p>
  
  <pre><code>class LogBrowsableAPIRenderer(BrowsableAPIRenderer):    
      def get_content(self, renderer, data, accepted_media_type, renderer_context):
          """
          Extends BrowsableAPIRenderer.get_content.
          """
          if not renderer:
              return '[No renderers were found]'
  
          renderer_context['indent'] = 4
          # content = renderer.render(data, accepted_media_type, renderer_context)
  
          # try to convert all string-values into dictionaries
          data_dict = dict(data.items())
          for k in data_dict.keys():
              try:
                  data_dict[k] = json.loads(data_dict[k], strict=False)
              except JSONDecodeError:
                  # ignore errors and move on for now
                  pass
  
          # dump into indented string again
          content = json.dumps(data_dict, indent=4, sort_keys=True).encode(encoding="utf-8")
  
          render_style = getattr(renderer, 'render_style', 'text')
          assert render_style in ['text', 'binary'], 'Expected .render_style "text" or "binary", but got "%s"' % render_style
          if render_style == 'binary':
              return '[%d bytes of binary content]' % len(content)
  
          return content
  </code></pre>
  
  <p>I also realize that I could have worded my question differently to maybe come to this closure more quickly.</p>