Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SerializerClass field on Serializer save from primary key

I am working developing an API with Django-rest-framework and consuming it from a web app. It has a Physician Model with a Fk from the django.auth User model. I want to post from a form to the Physician Model but the serializer returns this message:

{"user":{"non_field_errors":["Invalid data. Expected a dictionary, but got unicode."]}}

I am sending the primary key of the user object. Which is the right (or just one way) to store a foreign key on DRF. I have tried overriding get_validation_exclusions on the serializer and overriding perform_create method on the viewset.

The api and the web app are decouple. The API is developed with django and the web app with angularjs.

My model

class Physician(models.Model):
    medical_office_number = models.CharField(max_length = 15)
    fiscal_id_number = models.CharField(max_length = 20)
    user = models.OneToOneField(User)

    def __unicode__(self):
        return self.user.first_name +' '+ self.user.last_name

Serializer:

class PhysicianSerializer(serializers.ModelSerializer):
    user = AccountSerializer()
    class Meta:
        model = Physician
        fields = ('id', 'user', 'medical_office_number', 'fiscal_id_number')
        read_only_fields = ('id')
        depth = 1
    def get_validation_exclusions(self, *args, **kwargs):
        exclusions = super(PhysicianSerializer, self).get_validation_exclusions()
        return exclusions + ['user']

*Edit This is my account serializer, which is based on this implementation and with the @Kevin Brown suggestion

class PrimaryKeyNestedMixin(serializers.RelatedField, 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)

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

    class Meta:
        model = Account
        fields = ('id', 'email', 'username', 'created_at', 'updated_at',
                  'first_name', 'last_name', 'password',
                  'confirm_password', 'is_admin',)
        read_only_fields = ('created_at', 'updated_at',)

Viewset

class AccountViewSet(viewsets.ModelViewSet):
    lookup_field = 'username'
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

When I try to serializer this object, it triggers an error.

So I can post any user from the <select> element. But I can't verify the solution. Something I am missing?

Error Stacktrace

TypeError at /api/v1/accounts/

__init__() takes exactly 1 argument (5 given)

Exception Location:     /home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py in many_init, line 68
Python Executable:  /home/jlromeroc/workspace/asclepios/venv/bin/python
Python Version:     2.7.3

File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response 111. response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view 57. return view_func(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/viewsets.py" in view 85. return self.dispatch(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 407. response = self.handle_exception(exc) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 404. response = handler(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/mixins.py" in list 45. serializer = self.get_serializer(instance, many=True)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/generics.py" in get_serializer 90. instance, data=data, many=many, partial=partial, context=context File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in __new__ 48. return cls.many_init(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in many_init 68. list_kwargs = {'child_relation': cls(*args, **kwargs)}

Exception Type: TypeError at /api/v1/accounts/
Exception Value: __init__() takes exactly 1 argument (5 given)

Edit** I have opted to override the create function on the viewset and include the object in the request, so it can be validated, but then, the serializer tries to insert a new object for the Account model. How can I prevent this behaviour? I tried to set the serializer on the PhysicianSerializer class as read_only but then, django tries to store the model with a null user_id. How can I save a model without try to insert an related object too?

like image 712
Cheluis Avatar asked Jan 18 '15 14:01

Cheluis


People also ask

What does serializer save do?

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON , XML or other content types.

How do I pass Queryset to serializer?

To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.

What does serializer data return?

Serializer. data returns a copy of the validated data · Issue #5998 · encode/django-rest-framework · GitHub.

What is difference between serializer and ModelSerializer?

The ModelSerializer class is the same as a regular Serializer class, except that: It will automatically generate a set of fields for you, based on the model. It will automatically generate validators for the serializer, such as unique_together validators. It includes simple default implementations of .


3 Answers

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.

like image 184
Kevin Brown-Silva Avatar answered Oct 10 '22 16:10

Kevin Brown-Silva


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

like image 32
shad0w_wa1k3r Avatar answered Oct 10 '22 14:10

shad0w_wa1k3r


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.

like image 29
Cheluis Avatar answered Oct 10 '22 15:10

Cheluis