How to update a many to many field in DRF

Question:

So in my project, I have a User model and a School model. My user model has a schools field that has an M2M relation with the School model. Now what I want to know is, how can I create a view that can take the email of a user and the school Id, and add the school or delete it from the schools that a user belongs to.

Here is my user model:

class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=255, unique=True, db_index=True)
    email = models.EmailField(max_length=255, unique=True, db_index=True)
    is_verified = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
    schools = models.ManyToManyField("School", related_name="members")

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    objects = UserManager()

    def __str__(self):
        return self.email

    def tokens(self):
        refresh = RefreshToken.for_user(self)
        return {
            'refresh': str(refresh),
            'access': str(refresh.access_token)
        }

School:

class School(models.Model):
    name = models.CharField(max_length=300, verbose_name='school name', )
    principal = models.ForeignKey("User", related_name="my_schools", on_delete=CASCADE)
    address = models.CharField(max_length=200)

    class Type(models.IntegerChoices):
        PUBLIC = 1, "Public"
        PRIVATE = 2, "Private"

    type = models.PositiveSmallIntegerField(choices=Type.choices, default=Type.PUBLIC)

    class Level(models.IntegerChoices):
        NATIONAL = 1, "National"
        EXTRACOUNTY = 2, "Extra County"
        COUNTY = 3, "County"
        SUBCOUNTY = 4, "Subcounty"

    level = models.PositiveSmallIntegerField(choices=Level.choices, default=Level.COUNTY)

    def __str__(self):
        return self.name

Here is the serializer to enroll the members:

class EnrollSchoolMembersSerializer():
    class Meta:
        model = User
        field = ('email')

Asked By: sajeyks mwangi

||

Answers:

I think you can implement that using the function API view. First, you can define the serializer.

from rest_framework import serializers as sz

class SchoolUpdateSerializer(sz.Serializer):
    school_id = sz.IntegerField()
    email = sz.EmailField()
    mode = sz.CharField()      # can be either 'add' or 'delete'

And in views.py, you can write the handler.

from rest_framework.response import Response
from rest_framework status
from rest_framework.decorators import api_view, permission_classes
from .models import School, User
from .serializers import SchoolUpdateSerializer

@api_view(['POST'])
@permission_classes([permissions.AllowAny])
def update_school_data(request):
    serializer = SchoolUpdateSerializer(data = request.data)
    if serializer.is_valid():
        input_data = serializer.validated_data
        email = input_data.get('email')
        mode = input_data.get('mode')
        school_id = input_data.get('school_id')
        try:
            user = User.objects.get(email = email)
            school = School.objects.get(pk = school_id)
            if mode == "add":
                user.schools.add(school)
            else:
                user.schools.remove(school)
            return Response(status = status.HTTP_200_OK)       
        except (User.DoesNotExist, School.DoesNotExist):
            return Response(status = status.HTTP_400_BAD_REQUEST)
    else:
        return Response(status = status.HTTP_400_BAD_REQUEST)
        

And finally, in the frontend, you can use POST API. The payload can be

{
    "email": "[email protected]",
    "school_id": 1,
    "mode": "add"
}
Answered By: Metalgear