Understanding many-to-many relationship and implementing it using models.ManyToManyField in Django / DRF

In our previous post “Understanding many-to-one relationship and implementing it using ForeignKey in Django / DRF” we showed how you can create a JSON object (useraddress) and add it in another main JSON object, so in our previous post we created a JSON structure 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
        }
    }
]

Now, what will happen if our users have more than one address, in this case we can’t use above JSON structure hence using “Many-To-One” implementation using ForeignKey will not work, so we need to extend this JSON to create an array of address’s .. this is called as “many-to-many” relationship implementation.

So, our plan is to design an JSON which should look 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
                },
                {
                    "id": 2,
                    "home_no": "01",
                    "street": "my 01 home at street",
                    "city": "my current city",
                    "pincode": 999999
                }
            ]
        }

So, as you can see we want the user to have more than one address i.e. array of address’s . This can be achieved by changing “models.ForeignKey” to “models.ManyToManyField” in our previous’s posts code. So, the code changes in models.py would required as,

Remove

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

and add

+    useraddress = models.ManyToManyField('UserAddress')

And also we need to change the serializers.py for our application as, i.e change UserAddressSerializer() to UserAddressSerializer(many=True)

-    useraddress = UserAddressSerializer()
+    useraddress = UserAddressSerializer(many=True)

So, the total changes required in our code would be as below,

$ git diff
diff --git a/helloproject/helloapp/models.py b/helloproject/helloapp/models.py
index 2d6cc34..500f229 100644
--- a/helloproject/helloapp/models.py
+++ b/helloproject/helloapp/models.py
@@ -15,7 +15,7 @@ class UserInfo (models.Model) :
     email = models.CharField(max_length=100)
     age = models.CharField(max_length=100)
 
-    useraddress = models.ForeignKey('UserAddress', on_delete=models.CASCADE)
+    useraddress = models.ManyToManyField('UserAddress')
 
     class Meta:
         ordering = ['id']
diff --git a/helloproject/helloapp/serializers.py b/helloproject/helloapp/serializers.py
index 6f92f25..29874cf 100644
--- a/helloproject/helloapp/serializers.py
+++ b/helloproject/helloapp/serializers.py
@@ -9,7 +9,7 @@ class UserAddressSerializer(serializers.ModelSerializer):
         fields = ['id', 'home_no', 'street', 'city', 'pincode']
 
 class UserInfoSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
-    useraddress = UserAddressSerializer()
+    useraddress = UserAddressSerializer(many=True)
 
     class Meta:
         model = UserInfo

So, our final code in actual models.py and serializers.py would be as below,

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.ManyToManyField('UserAddress')

    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(many=True)

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

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

second_home_no=01
second_street="my 01 home at street"
second_city="my current city"
second_pincode=999999

address="[{\"home_no\":\"$home_no\",\"street\":\"$street\",\"city\":\"$city\",\"pincode\":\"$pincode\"},{\"home_no\":\"$second_home_no\",\"street\":\"$second_street\",\"city\":\"$second_city\",\"pincode\":\"$second_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_class_based_views

Leave a Comment