Search code examples
pythondjangodjango-modelsdjango-rest-frameworkdjango-serializer

How to update a many to many field in DRF


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')


Solution

  • 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": "test@example.com",
        "school_id": 1,
        "mode": "add"
    }