Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DjangoRestFramework - How to access other fields of a OneToOneField reverse relationship using a model serializer?

I am using the default User model and am also extending it with a UserExtended model:

class Country(models.Model):
    countryName = models.CharField(max_length=50, unique=True)
    countryCode = models.CharField(max_length=10, unique=True)

class UserExtended(models.Model):
    user = models.OneToOneField(User, related_name="userextended")
    country = models.ForeignKey(Country)

I am trying to follow what is documented here: http://www.django-rest-framework.org/api-guide/relations/#reverse-relations

This is my UserSerializer:

class UserSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
            super(UserSerializer, self).__init__(*args, **kwargs) # call the super() 
            for field in self.fields: # iterate over the serializer fields
                self.fields[field].error_messages['required'] = 'Enter a valid %s.'%field # set the custom error message

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'userextended')

        extra_kwargs = {
                    'password': {
                        'write_only': True,
                    }
                }

    def create(self, validated_data):
        user = User.objects.create_user(
            email = validated_data['email'],
            username = validated_data['username'],
            password = validated_data['password'],
        )
        return user

My issue is, if the end user does not fill out the "Country" section of the form before submitting it, Django sends an error message to the front end saying "Enter a valid userextended". "userextended" is the name of the reverse relationship, and every user object is linked to a userextended object and vise-versa. "userextended" is mandatory, but the end user does not have to specify a "userextended" field when creating a user object because that is already a given (the moment the end user creates a user object, the user object will have a reverse relationship with a UserExtended object by default).

The end user has to fill out a "Country" section in the form because Country is what is required from the end user. With that said, how do I get Django to say "Enter a valid Country" rather than "Enter a valid userextended"?

If the end user successfully saves a user object, and submits "Canada" in the "Country" section in the form, how does DRF know to save "Canada" as the country? Because currently, it looks like DRF thinks the "Country" section in the form refers to the "userextended" reverse relationship field.

What I'd expect is, in the "fields" array for the user serializer, I use dot notation? Something like this?:

fields = ('username', 'password', 'email', 'userextended.country')

Edit: I just created a UserExtended serializer as well, like so:

class UserExtendedSerializer(serializers.ModelSerializer):

    class Meta:
        model = UserExtended
        fields = ('country')

but I don't know how to "merge" this serializer with the original UserSerializer.

like image 963
SilentDev Avatar asked Sep 29 '15 20:09

SilentDev


People also ask

How do you pass extra context data to Serializers in Django REST framework?

In function-based views, we can pass extra context to serializer with “context” parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with “self. context”. From example, to get “exclude_email_list” we just used code 'exclude_email_list = self.

What is the difference between serializer and model serializer?

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 .

What is a hyperlinked model serializer?

HyperlinkedModelSerializer is a layer of abstraction over the default serializer that allows to quickly create a serializer for a model in Django. Django REST Framework is a wrapper over default Django Framework, basically used to create APIs of various kinds.


2 Answers

Solution given by Igor works only when you want to fetch the details of users because of read_only field. Serializer doesn't support nested write by default. To write a nested object you should override the create method of UserSerializer.

class UserSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
            super(UserSerializer, self).__init__(*args, **kwargs) # call the super()
            for field in self.fields: # iterate over the serializer fields
                self.fields[field].error_messages['required'] = 'Enter a valid %s.'%field # set the custom error message

    country = serializers.RelatedField(source='userextended.country')

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'country')

        extra_kwargs = {
                    'password': {
                        'write_only': True,
                    }
                }

    def create(self, validated_data):
        user = User.objects.create_user(
            email = validated_data['email'],
            username = validated_data['username'],
            password = validated_data['password'],
        )
        country=Country.objects.get(pk=validated_data['country'])
        UserExtended.objects.create(user=user, country=country)

        return user

Data to send when creating:

{'username':'sachin', 'email': '[email protected]', 'password': 'abc', 'country':2}
like image 184
Sachin Gupta Avatar answered Oct 02 '22 13:10

Sachin Gupta


You should do the next:

  1. Write serializer for UserExtended model instances.
  2. Use this serializer to serialize userextended field.

Something like this:

class UserExtendedSerializer(serializers.ModelSerializer):
# define serializer here...

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'userextended')

    userextended = UserExtendedSerializer(read_only=True)
like image 37
Igor Pomaranskiy Avatar answered Oct 02 '22 13:10

Igor Pomaranskiy