In my models, I have the following classes:
class Topic(models.Model):
name = models.CharField(max_length=25, unique=True)
class Content(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
topic = models.ForeignKey(Topic, blank=True, null=True)
My serializers is like this:
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = Topic
fields = ('name')
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=True)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
Alright, so in my urls file, I have the following pattern:
urlpatterns = [
...
url(r'^api/topic_detail/(?P<name>[a-zA-Z0-9-]+)/content_list/$', views.topic_content_list, name='topic_content_list'),
...
]
Therefore, when the user goes to say /api/topic_detail/sports/content_list/
, we get a list of all the contents that has the topic of sports. Now what I want is if we POST the following data to the above URL, then a Content object is created with the topic field related automatically to sports.
I am trying to do this the following way in my views:
@api_view(['GET', 'POST'])
def topic_content_list(request, name):
try:
topic = Topic.objects.get(name=name)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
contents = Content.objects.filter(topic=topic)
serializer = ContentSerializer(contents, many=True)
return Response(serializer.data)
elif request.method == 'POST':
request.data["topic"] = topic
serializer = ContentSerializer(data=request.data)
print request.data
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now lets say I go the URL /api/topic_detail/sports/content_list/
and POST this:
{
"title": "My blog post",
"body" : ".....",
}
The content object gets created and the title and body field is set properly. However, the topic field is set to null. How can I fix this? Any help is appreciated.
Also, please don't suggest using generic viewsets, as I am uncomfortable with so many things happening automatically.
EDIT
Alright, so I fixed my dumb mistake :
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=False)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
That is, I set the read_only argument to False. However, now the post creates a new error:
{
"topic": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Topic."
]
}
}
This I am pretty sure refers to the fact that the data.website I'm sending in is not JSON, but instead just a Django model instance. How can I JSONify the single instance?
This is from your serializer.
topic = TopicSerializer(read_only=True)
It means your topic is read only so when you are trying to save your serializer, topic is not getting saved. Remove that and problem would be fixed.
EDIT:
Now as per the second error, it is because it is expecting a dict and you are passing the model instance, so you have two options. Either create the dict by hand.
topic_dict = { "name": topic.name }
and pass that as 'topic' in request.data and then save or give the topic_id, as there is a foreign key relationship, it should work.
So it would be something like:
request.data["topic_id"] = topic.id
Now what you choose to do is totally upto you.
Resurrecting this old thread since it seems to be a common issue people are running into. I just got this working on Django 3.1.6.
Since the Topic
and Content
models are already linked, the serializer is simply
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
No need for topic = TopicSerializer(read_only=False)
, which will require you to create a new topic with the POST. Now, the body of the POST can be
{
"title": "My blog post",
"body" : ".....",
"topic": 3
}
If you want your GET output to look like
{
"title": "My blog post",
"body" : ".....",
"topic": {
"id": 3
"name": "announcements"
}
}
override to_representation
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
def to_representation(self, instance):
response = super().to_representation(instance)
response['topic'] = TopicSerializer(instance.topic).data
return response
Credit for proper usage of to_representation
goes to this answer by @PdotNJ
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With