[Fixed]-SerializerClass field on Serializer save from primary key

3πŸ‘

The issue here is that with nested serializers, Django REST framework is expecting both the input and the output to be a nested representation. DRF will automatically validate the input to make sure it matches the nested serializer, allowing you to create the main object and any relations in a single request.

You are looking to have a nested output with a PrimaryKeyRelatedField input. This is very common for those who don’t need to create relations in the same request, but instead will always be using existing objects in their relations. The way you are going to have to do it is basically take in a primary key (just like a PrimaryKeyRelatedField) in to_internal_value, but output a serializer in to_representation. Something like this (untested) should work

class PrimaryKeyNestedMixin(serializers.PrimaryKeyRelatedField, serializers.ModelSerializer):

    def to_internal_value(self, data):
        return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)

    def to_representation(self, data):
        return serializers.ModelSerializer.to_representation(self, data)

You would need to use this as a mixin on the nested serializer, AccountSerializer in your case, and it should do what you are looking for.

3πŸ‘

I ran into a similar problem (wanting to POST id / FK of the object, but expecting the serialized object in a GET). I implemented Kevin Brown’s solution successfully for my case. Adapting that to your problem (too late, but hope someone else, including future me, stumbles on this and finds it useful).

def get_primary_key_related_model(model_class, **kwargs):
    """
    Nested serializers are a mess. https://stackoverflow.com/a/28016439/2689986
    This lets us accept ids when saving / updating instead of nested objects.
    Representation would be into an object (depending on model_class).
    """
    class PrimaryKeyNestedMixin(model_class):

        def to_internal_value(self, data):
            try:
                return model_class.Meta.model.objects.get(pk=data)
            except model_class.Meta.model.DoesNotExist:
                self.fail('does_not_exist', pk_value=data)
            except (TypeError, ValueError):
                self.fail('incorrect_type', data_type=type(data).__name__)

        def to_representation(self, data):
            return model_class.to_representation(self, data)

    return PrimaryKeyNestedMixin(**kwargs)


class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        # ...


class PhysicianSerializer(serializers.ModelSerializer):
    user = get_primary_key_related_model(AccountSerializer)

    class Meta:
        model = Physician
        # ...

The class generator comes very handy when you have custom serializer fields (restricting access based on request.user).

πŸ‘€shad0w_wa1k3r

1πŸ‘

I followed this answer from SO. Disable creating nested objects in django rest framework Its a little bit messy, but works. Either way, that’s something it lacks DRF.

πŸ‘€Cheluis

0πŸ‘

I worked around this issue by having different views to handle get single item and post, and get nested list. The get single item and get list used a nested serializer and the post method used a non-nested serializer. When posting to create a new job alert you can use the primary keys for the job and the user which are the related objects.

class JobAlertList(APIView):
    """
    List all job alerts or create a new job alert
    """
    def get(self, request, format=None):
        job_alerts = JobAlert.objects.all()
        serializer = JobAlertNestedSerializer(job_alerts, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = JobAlertSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class JobAlertDetail(APIView):
    """
    Retrieve or delete a job alert instance.
    """
    def get_object(self, pk):
        try:
            return JobAlert.objects.get(pk=pk)
        except JobAlert.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        job_alert = self.get_object(pk)
        serializer = JobAlertNestedSerializer(job_alert)
        return Response(serializer.data)

    def delete(self, request, pk, format=None):
        job_alert = self.get_object(pk)
        job_alert.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

class JobAlertSerializer(serializers.ModelSerializer):

    class Meta:
        model = JobAlert
        fields = ('job', 'user')
        depth = 0

    def create(self, validated_data):
        user = validated_data.pop('user')
        job = validated_data.pop('job')
        job_alert = JobAlert.objects.create(user=user, job=job)
        return job_alert


class JobAlertNestedSerializer(serializers.ModelSerializer):

    class Meta:
        model = JobAlert
        fields = ('id', 'job', 'user')
        depth = 1

url(r'^job_alerts/$', views.JobAlertList.as_view(), name='job-alerts-list'),
url(r'^job_alerts/(?P<pk>[0-9]+)/$', views.JobAlertDetail.as_view(), name='job-alerts-detail'),

0πŸ‘

I have created a Field type that tries to solve the problem of the Data Save requests with its ForeignKey in Integer, and the requests to read data with nested data

This is the class:

class NestedRelatedField(serializers.PrimaryKeyRelatedField):
"""
    Model identical to PrimaryKeyRelatedField but its
    representation will be nested and its input will
    be a primary key.
"""

def __init__(self, **kwargs):
    self.pk_field = kwargs.pop('pk_field', None)
    self.model = kwargs.pop('model', None)
    self.serializer_class = kwargs.pop('serializer_class', None)
    super().__init__(**kwargs)

def to_representation(self, data):
    pk = super(NestedRelatedField, self).to_representation(data)
    try:
        return self.serializer_class(self.model.objects.get(pk=pk)).data
    except self.model.DoesNotExist:
        return None

def to_internal_value(self, data):
    return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)

And so it would be used:

class PostModelSerializer(serializers.ModelSerializer):

    message = NestedRelatedField(
         queryset=MessagePrefix.objects.all(),
         model=MessagePrefix,
         serializer_class=MessagePrefixModelSerializer
   )

I hope this helps you.

πŸ‘€Wezen42

Leave a comment