6

Django rest framework: How can I display a read-only field in the browsable api?

When I add result = serializers.CharField(read_only=True) to my model serializer, the form no longer renders the results field.

I understand the security concerns of a user removing the disabled attribute on a form input (though I am surprised django doesn't handle this natively), so how can I implement a read-only field in the api.html template from result?

serializers.py

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
result = serializers.CharField(read_only=True)

class Meta:
    model = Snippet
    fields = ('title', 'code', 'owner', 'url', 'result')

I am new to the django-rest framework, so any help would be appreciated!

Liz
  • 1,421
  • 5
  • 21
  • 39

2 Answers2

11

You have 2 options:

  1. either to calculate the result in the model

  2. or to add the field within the serialization

What you choose depends on whether you want to use that calculated result also somewhere else and whether you can touch models.

When you want to calculate the result in the model

Follow the example of Django's derived fullname, somewhere around: https://github.com/django/django/blob/master/django/contrib/auth/models.py#L348

Or explained here in the doc: https://docs.djangoproject.com/en/dev/topics/db/models/#model-methods

That will act as a read only field for DRF automatically.

You can see the usage in the code bellow (get_full_name).

When you want to add field within serialization

You have the answer in the DRF docs: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

SerializerMethodField This is a read-only field...It can be used to add any sort of data to the serialized representation of your object.

Example hours_since_joined in serializers.py:

from django.contrib.auth.models import User, Group
from rest_framework import serializers
from django.utils.timezone import now

class UserSerializer(serializers.HyperlinkedModelSerializer):
    hours_since_joined = serializers.SerializerMethodField()
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'groups', 'hours_since_joined', 'first_name', 'last_name', 'get_full_name' )

    def get_hours_since_joined(self, obj):
        return (now() - obj.date_joined).total_seconds() // 3600

class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        fields = ('url', 'name', 'user_set')

For your case:

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    result = serializers.SerializerMethodField()

    class Meta:
        model = Snippet
        fields = ('title', 'code', 'owner', 'url', 'result')

    def get_result(self, obj):
        # code here to calculate the result
        # or return obj.calc_result() if you have that calculation in the model
        return "some result"

To show the added fields in the DRF's browsable API

You need to list them in the Meta's fields - see example above. That will present that in the browsable output on requests. However it will not show them in the DRF's HTML form. The reason is that the HTML form is used for submitting information only, so the restframework template skips the read only field in rendering.

As you can see the full name and hours since joined is not rendered in the form, but is available for API:

enter image description here

If you want to show read only fields also on the form

you need to override the restframework templates.

  • make sure your templates are loaded before the restframework's (i.e. your app is above the restframework in the settings.py)
  • use the templates dir under your app
  • create subdir in your templates dir: restframework/horizontal
  • copy the form.html and input.html from Python's Lib\site-packages\rest_framework\templates\rest_framework\horizontal\

  • change the form.html

{% load rest_framework %}
{% for field in form %}
    {% render_field field style=style %}
{% endfor %}
  • change input line in the input.html (adding disabled attribute)

    <input name="{{ field.name }}"  {% if field.read_only %}disabled{% endif %} {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %}>
    

The result:

enter image description here

Radek
  • 1,530
  • 16
  • 20
  • Hmmm - I still don't see the result in the browsable api version. I can edit the code and title fields, but I want to also display the results (read-only) – Liz Dec 30 '15 at 11:03
  • Testing the api from the command line - I can see the result, so I assume this is a templating question? { "code": "sdfsdfsdfddd", "owner": "testuser", "result": "some result", "title": "dummy", "url": "http://localhost/data/snippets/26/" } – Liz Dec 30 '15 at 11:06
  • Do you mean, that you cannot see it in the browsable API output? Or in the HTML form? – Radek Dec 30 '15 at 23:15
  • both. I assume I need to get the value into the template but I'm unsure as to how to do that. – Liz Dec 30 '15 at 23:39
  • Updated the answer to see result in browsable API. (Added to fields). I do not think you would get it into the form as the form is for writable fields. – Radek Dec 31 '15 at 10:54
  • Hi Radek - that's my question. Given that the results field is read only (see original serializer class) and that forms display only writable fields, how can I show the results field in the rendered html (outside the form)? From the command line, I can see the results property - but my question is how to render that in the html template. I cannot figure out the templating here. – Liz Dec 31 '15 at 11:59
  • Hi, I just tested that to make sure. If you put result in the fields, you should be able to see it on the API browsable output (outside the PUT/UPDATE form). Example with read only days_since_joined here: http://imgur.com/geIyx8k – Radek Dec 31 '15 at 12:18
  • @liz_ophiuchus I updated the answer to make read only fields visible in DRF HTML form. – Radek Dec 31 '15 at 16:16
  • that's very thorough, I'll have a go - thanks. I take it that I'll need to also trash the post variable in the view? – Liz Jan 01 '16 at 05:29
  • I also don't see it in browsable API – Nikola Lukic May 16 '19 at 13:03
0

For the people that cannot make it visible in the Browsable API, the template folder should be named: rest_framework

Elletlar
  • 3,136
  • 7
  • 32
  • 38
staydin
  • 11
  • 2