Understanding many-to-one relationship and implementing it using ForeignKey in Django / DRF

Lets say we are trying to create a database of users using Django / Django REST framework. In this every user has “userid”, “username”, “email” and “Age” and also every user will have his own address which can be represented using “home number”, “street”, “city” and “pincode”.

So, if we try to design the JSON for this, it may look something like as below.

[
    {
        "id": 1,
        "userid": "my_userid",
        "username": "lynxbee1",
        "email": "social(at)lynxbee.com",
        "age": "47",
        "useraddress": {
            "id": 1,
            "home_no": "123",
            "street": "my home street",
            "city": "my city",
            "pincode": 123456
        }
    }
]

If we take a look at above JSON, we can see that we are trying to build database of USERS, so obviously there are going to be many users ( Note: Many ) but every user will have his/her unique one address ( Note: One) .. so, this relationship of “many users with each having one address” is called as “Many-to-One” relationship in database.

Implementation of Many-to-One relationship in Django / Django REST Framework

The implementation of this “many-to-one” relation in Django is helped by “ForeignKey” class. The definition of this class is as below,

class ForeignKey(to, on_delete, **options)

A many-to-one relationship. Requires two positional arguments: the class to which the model is related and the on_delete option.


To create a recursive relationship – an object that has a many-to-one relationship with itself – use models.ForeignKey('self', on_delete=models.CASCADE).

This post is in continuation with our another post “Developing REST API using functions in DRF application views” which shows how you can use function based views to implement REST API. In this post, we will just modify same codebase,

In our post we had implemented code which used only one single JSON object like below,

[{
	"id": 1,
        "userid": "my_userid",
	"username": "lynxbee1",
	"email": "social(at) lynxbee.com",
	"age": "47"
}]

Now, in this we post, as we can see in initial JSON, we want one more JSON object “useraddress” in this outer Json object which represent single user., so lets see how this can be done

vim helloproject/helloapp/models.py
from django.db import models

class UserAddress(models.Model) :
    home_no = models.CharField(max_length=100)
    street = models.CharField(max_length=100)
    city = models.CharField(max_length=100)
    pincode = models.IntegerField()

    def __str__(self) :
        return self.home_no

class UserInfo (models.Model) :
    userid = models.CharField(max_length=100)
    username = models.CharField(max_length=100)
    email = models.CharField(max_length=100)
    age = models.CharField(max_length=100)

    useraddress = models.ForeignKey('UserAddress', on_delete=models.CASCADE)

    def __str__(self) :
        return self.username

As you can see in above code, we defined class “UserAddress” which will achieve the “useraddress” json object dependent tables and then we referenced this class from original “UserInfo” class, so the actual code address compared to previous code was as,

+class UserAddress(models.Model) :
+    home_no = models.CharField(max_length=100)
+    street = models.CharField(max_length=100)
+    city = models.CharField(max_length=100)
+    pincode = models.IntegerField()
+
+    def __str__(self) :
+        return self.home_no
+

 class UserInfo (models.Model) :
 
+    useraddress = models.ForeignKey('UserAddress', on_delete=models.CASCADE)
+
     def __str__(self) :
         return self.username

Next now, we need to change the serializers.py for our app, as below,

vim helloproject/helloapp/serializers.py
from rest_framework import serializers
from helloproject.helloapp.models import UserInfo, UserAddress

from drf_writable_nested import WritableNestedModelSerializer

class UserAddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserAddress
        fields = ['id', 'home_no', 'street', 'city', 'pincode']

class UserInfoSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
    useraddress = UserAddressSerializer()

    class Meta:
        model = UserInfo
        fields = ['id', 'userid', 'username', 'email', 'age', 'useraddress']

As you can see above, we created totally new “UserAddressSerializer” class and have referenced this “UserAddressSerializer” class as a variable “useraddress” inside UserInfoSerializer and then same variable we appended at last to fields.

Hence the actual code which we added / changed was like below,

+from helloproject.helloapp.models import UserInfo, UserAddress
+
+from drf_writable_nested import WritableNestedModelSerializer
+
+class UserAddressSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = UserAddress
+        fields = ['id', 'home_no', 'street', 'city', 'pincode']
+
+class UserInfoSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
+    useraddress = UserAddressSerializer()
 
-class UserInfoSerializer(serializers.ModelSerializer):
     class Meta:
         model = UserInfo
-        fields = ('id', 'userid', 'username', 'email', 'age')
+        fields = ['id', 'userid', 'username', 'email', 'age', 'useraddress']

Notice here, apart from just creating one more “UserAddressSerializer” and linking it with “UserInfoSerializer” we have also changed UserInfoSerializer as below, with additional argument WritableNestedModelSerializer,

-class UserInfoSerializer(serializers.ModelSerializer):
+class UserInfoSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):

The reason for this has been explained in our another post, “Solved : The .create() method does not support writable nested fields by default.”

Now start the server as,

$ vim run_server.sh
#!/bin/bash

SERVER_IP="192.168.0.106"
SERVER_PORT="8000"

python3 -m venv env
source env/bin/activate
pip install django
pip install djangorestframework
pip install drf_writable_nested
python manage.py makemigrations
python manage.py migrate --run-syncdb
python manage.py migrate
python manage.py runserver $SERVER_IP:$SERVER_PORT
$ bash run_server.sh

Once the server is running from another shell, lets try to push the nested JSON as payload,

$ vim http_post.sh
#!/bin/bash

API_URL="http://192.168.0.106:8000/users"

userid="my_userid"
username="lynxbee1"
email="social(at)lynxbee.com"
age="45"

home_no=123
street="my home street"
city="my city"
pincode=123456

address="{\"home_no\":\"$home_no\",\"street\":\"$street\",\"city\":\"$city\",\"pincode\":\"$pincode\"}"

data="{\"userid\":\"$userid\",\"username\":\"$username\",\"email\":\"$email\",\"age\":\"$age\", \"useraddress\":"$address"}"
echo $data

#exit
curl -v -k -X POST -H "\"Accept: application/json\"" -H "\"Content-Type:application/json\"" -d $data "$API_URL/"
$ bash http_post.sh

And now when you see the JSON in django dashboard at http://192.168.0.106:8000/users/ you should see the JSON with another object inside main object as required and mentioned at beginning of the post.

You can download complete source from github at https://github.com/lynxbee/drf_api_view_with_function.git

References –

Leave a Comment