Reputation: 321
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
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
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
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