0

I have a User model and a Group model, which are presented below:

class User(models.Model):
    username = models.CharField(primary_key=True, max_length=32, unique=True)
    user_email = models.EmailField(max_length=32, unique=False)
    user_password = models.CharField(max_length=32)
    user_avatar_path = models.CharField(max_length=64)

class Group(models.Model):
    group_id = models.AutoField(primary_key=True)
    group_name = models.CharField(max_length=32, unique=False)
    group_admin = models.ForeignKey(User, on_delete=models.CASCADE, related_name='my_groups')
    members = models.ManyToManyField(User, related_name='groups', through='UserGroup')

Every group can have multiple users associated with it and every user can be associated to multiple groups, which is modeled with a ManyToManyField and a through model between the models. When I create a group, the user which creates the group is automatically assigned as the group admin and therefore added as a member of the group:

class MemberSerializer(serializers.ModelSerializer): # The nested serializer used within the GroupSerializer

    username = serializers.ReadOnlyField(source='user.username')

    class Meta:
        model = User
        fields = ['username']

class GroupSerializer(serializers.ModelSerializer):
    members = MemberSerializer(source='user_groups', many=True, required=False)
    group_admin = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all()) # A Group object is related to a User object by username

    class Meta:
        model = Group
        fields = ['group_id', 'group_name', 'group_admin', 'members']

    def create(self, validated_data): # Overriden so that when a group is created, the group admin is automatically declared as a member.
        group = Group.objects.create(**validated_data)
        group_admin_data = validated_data.pop('group_admin')
        group.members.add(group_admin_data)

        return group

    def update(self, instance, validated_data):
        members_data = validated_data.pop('members') # Comes from the request body
        group_id = self.kwargs['group_id'] # Comes from the URL
        add_remove = self.kwargs['add_remove'] # Comes from the URL 
        group = Group.objects.filter(group_id=group_id).first()
        if members_data is not None:
            if add_remove == 'add':
                for member in members_data:
                    group.members.add(member['username'])
            elif add_remove == 'remove':
                for member in members_data:
                    group.members.remove(member['username'])   
            
        return super().update(instance, validated_data)

Adding the user which created the group (the group admin) as a member is done successfully by overriding the create method of the GroupSerializer and then making a POST request as shown below:

{
    "group_name": "StackOverFlow",
    "group_admin": "bigboy"  
}

# which then results in the response when the group has been successfully created:

{
    "group_id": 1,
    "group_name": "StackOverFlow",
    "group_admin": "bigboy",
    "members": [
        {
            "username": "bigboy"
        }
    ]
}

I want to be able to update the members of the group through a PATCH request by adding or removing users associated with a group. I have attempted to do this by overriding the update method of GroupSerializer as shown above. The PATCH request is made as shown below with the URL of http://127.0.0.1:8000/group/1/add/update to add a user to a group:

{
    "members": [
        {
            "username": "small_man"
        }
    ]
}

The error that is received:

line 47, in update
    members_data = validated_data.pop('members') # Comes from the request body
KeyError: 'members'

I'm unsure if the method I'm attempting to add a user to a group, is a simple or even valid method but the KeyError is confusing because I assume that the validated data does include a members list.

EDIT

The error above has been solved.

I overrode the perform_update method of the GroupUpdate View as follows in order to see why my request data would not be validated:

    def perform_update(self, serializer):
        serializer=GroupSerializer(data=self.request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        return super().perform_update(serializer)

The more detailed error that appeared was: TypeError: Direct assignment to the reverse side of a related set is prohibited. Use user_groups.set() instead. For reference, user_groups is the related_name of the UserGroup through model:

class UserGroup(models.Model): # Manually specified Junction table for User and Group
    user = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        related_name='user_groups'
        )
    group = models.ForeignKey(
        Group, 
        on_delete=models.CASCADE, 
        related_name='user_groups'
        )
9
  • Can you check self.request.data and see if data exist there or not, if data exists in request and not in validated_data that means you are not sending data that is required. Commented May 5, 2022 at 17:08
  • what if your members_data is [ ], your this members_data is not None: can fail. Commented May 5, 2022 at 17:59
  • @DeepakTripathi self.request.data returns the following: {'members': [{'username': 'small_man'}]}. The data exists in the request, but not in validated_data. Commented May 5, 2022 at 18:21
  • @RanuVijay I checked members_data[0].items() and it returned: odict_items([]) which I interpret as an empty dictionary. So as you mentioned, members_data is []. Commented May 5, 2022 at 18:23
  • So here you got the error if data is coming in request.data but not in validated_data that means your data is not validated and you can check the error by using custom perform_update() method in your views and in that method pass request.data to serializer where you will get the error . Commented May 5, 2022 at 18:27

1 Answer 1

1

When you use source argument you can access you data using source value as a key, try this:

def update(self, instance, validated_data):
    members_data = validated_data.pop('user_groups')
Sign up to request clarification or add additional context in comments.

1 Comment

Using the source value as a key worked to fix the KeyError I was receiving! Unfortunately I ran into another KeyError when trying to access 'username' within members_data but I suspect this is due to members_data being empty (as mentioned in the comments on the post).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.