Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Posting a foreign key relationship in Django Rest Framework

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?

like image 944
darkhorse Avatar asked Aug 17 '16 16:08

darkhorse


2 Answers

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.

like image 161
Vinit Kumar Avatar answered Oct 05 '22 12:10

Vinit Kumar


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

like image 28
Ryan H. Avatar answered Oct 05 '22 12:10

Ryan H.