DRF tries to create User object when used in nested serializer

DRF docs says that "By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be save."<< In general this is true except for one case. When User class is a nested object in serializer. Use case is to add existing user to newly created organization.

Consider my simple example where I noticed that issue:

views.py

class UserOrganizationViewSet(viewsets.ModelViewSet):
    # authentication_classes = (JwtAuthentication, SessionAuthentication)
    # permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
    serializer_class = UserOrganizationSerializer
    queryset = UserOrganization.objects.all()

models.py:

class UserOrganization(models.Model):
    name = models.CharField(max_length=256)
    users = models.ManyToManyField(User, blank=True, related_name="user_organizations", through='OrganizationMember')

    def __str__(self):
        return self.name

class OrganizationMember(models.Model):
    organization = models.ForeignKey(UserOrganization, on_delete=CASCADE)
    user = models.ForeignKey(User, on_delete=CASCADE)
    joined = AutoCreatedField(_('joined'))

serializers.py:

  1. First version - User class has its own simple serializer and it is used as nested serializer:
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = ['username']


class UserOrganizationSerializer(serializers.ModelSerializer):
     users = UserSerializer(many=True, required=False)
     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'users']
         read_only_fields = ['id']

Is this case I am able to fetch data via GET method (Organization created through admin panel):

enter image description here

But when I am trying to create organization I am getting error which says that user already exist: enter image description here

So, I checked what will happen when I try to paste not existed username in JSON data send. According to expectation DRF says that I need to override create() method:

{
  "name": "Test2",
  "users": [
    {
      "username": "admins"
    }
  ]
}

AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `organization.serializers.UserOrganizationSerializer`, or set `read_only=True` on nested serializer fields.

And there is a first weird behavior. DRF still tries to created User object: enter image description here

My create method looks like this:

def create(self, validated_data):
     print("validated_data", validated_data)
     users = validated_data.pop('users', [])
     user_organization = UserOrganization.objects.create(**validated_data)
     print("users", users)

     for user in users:
          userSerializer = UserSerializer(user)
          print("userSerializer.data", userSerializer.data)
          username = userSerializer.data['username']
          print("username", username)
          user_organization.users.add(get_user_model().objects.get(username=username))
     return user_organization

In this case my method isn't called at all.

In case when I passed user which does not exist, my create() method is called and exeption is thrown as it should be. (I know I am not handling errors, this is just sandbox):

{
  "name": "Test2",
  "users": [
    {
      "username": "admin1"
    }
  ]
}

django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
  1. New model class UserProfile and serializer for this class used as nested:

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['name']


class UserOrganizationSerializer(serializers.ModelSerializer):
     profiles = UserProfileSerializer(many=True)
     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'profiles']
         read_only_fields = ['id']


     def create(self, validated_data):
          print("validated_data ", validated_data)
          users = validated_data.pop('profiles', [])
          user_organization = UserOrganization.objects.create(**validated_data)
          print("create", users)

          for user in users:
               print("username = ", user['name'])
               user_organization.profiles.add(UserProfile.objects.get(name=user['name']))
          user_organization.save()
          return user_organization

Small change was needed to change field users to profiles inside UserOrganization.

Creating organization with existed user profile (created by admin) via POST:

enter image description here

In this scenario there is no such problem as mentioned in first case.

  1. Final solution using SlugReleatedField works as expected. No serializer used for User.
class UserOrganizationSerializer(serializers.ModelSerializer):
     users = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='username')

     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'users']
         read_only_fields = ['id']

Above code does what I exactly need and is very simple. Here is small concern regarding performance when fetched all users from DB in this line: queryset=get_user_model().objects.all()

enter image description here

Can someone explain why this happens what we could observe in scenario 1?

Sorry for this post being so long. I tried to shorten it as much I as can. I can put whole project in github if it will be needed for readability.

Back to Top