Serializer for writing Foreign Keys as integer id but reading them as object

I have two models - Users and UserAccessLogs:

class Users(AbstractUser):
    email = models.EmailField()
    name = models.Charfield()
    etc.

class UserAccessLogs(AbstractUser):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
    anotherField = models.Charfield()
    etc.

with serializers:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = Users
        fields = '__all__'

    def validate_email(self, value):
        # validations here

    def update(self, instance, validated_data):
        instance.email = validated_data['email'] 
        etc.
        instance.save()
        return instance

class UserAccessLogsSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserAccessLogs
        fields ='__all__'

In my views function I would like to retrieve the actual user class when fetching UserAccessLogs i.e. I would like:

UserAccessLogs = {
   user : {
           email: myEmail,
           name: myName, 
           etc.
          }
}

instead of

UserAccessLogs = {
   user : 1
}

in my views:

access_logs = UserAccessLogs.objects.all()
serializer = UserAccessLogsSerializer(access_logs, many = True)
return Response(serializer.data, status.HTTP_200_OK)

I tried adding user = UserSerializer() to UserAccessLogsSerializer class. It works, but then when I try and save a new instance of the UserAccessLogs I get errors related to User model from the validators. If I set read_only = True then it leaves user = null in the database. The below works in my views class but it doesn’t seem efficient and needs to be repeated every time I want to get full UserAccessLogs.

for data in serializer.data:
      data['user'] = UserSerializer(User.objects.get(id = data['user'])).data

Hey there! I think that what you want is to have a read_only and write_only fields.
The main reason that this works is how ForeignKey on models creates attributes on your model.

When you do this, django creates a Reverse lookup on user attribute and a implicit user_id that will be the column into the database.
So thinking on this, on your serializer you can do something like:

class UserAccessLogsSerializer(serializers.ModelSerializer):
    user_id = serializers.IntegerField(write_only=True)
    user = UserSerializer(read_only=True)

    class Meta:
        model = UserAccessLogs
        fields = (
            "user",
            "user_id",
            # Other fields goes here
        )

Also, this a not a good pratice.

It’s better to specify all fields on your serializers.

1 Like

Thanks, I ended up using the following as you suggested:

user = UserSerializer(read_only = True)
user_pk = serializers.PrimaryKeyRelatedField(queryset = User.objects.all(), source = 'user', write_only = True, allow_null = True)

and then used user_pk when inserting and user when reading.