Dhruv
Dhruv

Reputation: 321

DRF How to change the field key names in JSON response

I want to display all objects of a particular model, and it's related objects ( object-wise ). Is there a way to change/customize the JSON output? (In terms of the key names, values, or the nesting?)

views.py

@api_view(['GET'])
def api(request):
    q = Question.objects.all()
    s = QuestionSerializer(q, many=True)
    print(ChoiceSerializer())
    return Response(s.data)

serializers.py

class QuestionSerializer(serializers.ModelSerializer):
    choices = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Question
        fields = '__all__'

models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField(auto_now_add=True, editable=False, name='pub_date')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices')
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return self.choice_text

    def related_question(self):
        q = self.question.question_text
        return q

Output

HTTP 200 OK
Allow: OPTIONS, GET
Content-Type: application/json
Vary: Accept

[
    {
        "id": 1,
        "choices": [
            1,
            2,
            3
        ],
        "question_text": "Question 1",
        "pub_date": "2020-12-19T23:07:25.071171+05:30"
    },
    {
        "id": 2,
        "choices": [
            4,
            5
        ],
        "question_text": "Question 2",
        "pub_date": "2020-12-21T13:58:37.595672+05:30"
    }
]

In the output, choices shows the pk of the objects. How can I change that to show, say, the choice_text or a custom string? Also, can the way the objects are nested be customized?

EDIT: When I asked if there is a way to exercise more control, I meant control over the depth of JSON response.

Upvotes: 0

Views: 1652

Answers (3)

Roham
Roham

Reputation: 2110

You can use serializer's method field to replace choices key with any related model fields that you want. For instance

class QuestionSerializer(serializers.ModelSerializer):
    choices = serializers.SerializerMethodField('get_choices')

    class Meta:
        model = Question
        fields = '__all__'
    
    def get_choices(self, obj):
        return obj.choices.values('choice_text').distinct() # or values('id', 'choice_text') based on your requirements

Note that values are a better way than iterating through all objects as a performance point of view (like [choice.choice_text for choice in question.choices.all()], etc).

EDIT: SerializerMethodField is used when we want to run some custom logic to populate a particular field. When using ModelSerializer, the source keyword can be used to access fields (related, non related both) or even rename fields in the case where the person interacting with the endpoint requires a different name.

Example for renaming(using models mentioned in the question)

class QuestionSerializer(serializers.ModelSerializer):
    poll_question = serializers.CharField(source='question_text')
    class Meta:
        model = Question
        fields = '__all__'

Example for getting a FK related field:

class ChoiceSerializer(serializers.ModelSerializer):
    poll_question = serializers.CharField(source='question.question_text')
    class Meta:
        model = Choice
        fields = '__all__'

This is a very good foundational read for understanding how and when to use serializers in DRF.

Upvotes: 1

ruddra
ruddra

Reputation: 51938

Instead of PrimaryKeyRelatedField, consider using SlugRelatedField. For example:

class QuestionSerializer(serializers.ModelSerializer):
    choices = SlugRelatedField(many=True, read_only=True, slug_field='choice_text')

    class Meta:
        model = Question
        fields = '__all__'

Also, you need to make choice_text field unique, ie add unique=True for this to work.

choice_text = models.CharField(max_length=200, unique=True)

FYI, you can remove __unicode__ method from your model definations. Explanation can be found in this Django documentation link.

Upvotes: 1

JPG
JPG

Reputation: 88429

You can use serializers.SerializerMethodField(...) if you want more control over what you wish to return

class QuestionSerializer(serializers.ModelSerializer):
    choices = serializers.SerializerMethodField()

    def get_choices(self, question):
        return [choice.choice_text for choice in question.choices.all()]

    class Meta:
        model = Question
        fields = '__all__'

Upvotes: 1

Related Questions