3

I needed to extend User model to add things like address, score, more user_types, etc. There are 2 possible ways to achieve that, extend the User model or create a new model that will be connected with the target User with OneToOneField. I decided to go with a new model because It seemed easier and It is recommended in this stack overflow question. But now I cannot create Serializer without nested profile field which is moreover undocumented because default rest_framwork documentation generator cannot generate documentation for nested serializers.

My UserSerializer looks like this:

class UserSerializer(serializers.ModelSerializer):
    # This creates a nested profile field
    profile = ProfileSerializer(required=True)

    def create(self, validated_data):
        profile_data = validated_data.pop('profile')
        user = User.objects.create_user(**validate_data)
        profile, created = Profile.objects.upodate_or_creeate(user=user, defaults=profile_data)
        return user

    class Meta:
        model = User
        fields = ('id', 'username', 'email', 'password', 'buckelists', 'profile')
        read_only_fields = ('id',)
        extra_kwargs = {'password':{'write_only': True}}

This Serializer takes following JSON format:

{
    'name': ...,
    'email': ...,
    'password': ...,
    'profile': {
        'address': ...,
        'score': ...,
        'user_type': ...,
        'achievements': ...,
        'country': ...,
        'trusted': ...,
    }

This looks weird and documentation generated with rest_framework.documentation.include_docs_urls shows just following:

{
    'username': ...,
    'email': ...,
    'password': ...,
    'field': ...,
}

So it's not clear what should be included in the profile field. I'd like to create Serializer that would accepted following format:

{
    'name': ...,
    'email': ...,
    'password': ...,
    'address': ...,
    'score': ...,
    'user_type': ...,
    'achievements': ...,
    'country': ...,
    'trusted': ...,
}

Is it possible without creating custom Serializer from scratch? Or at least is it possible to generate documentation for nested serializers.

PS: I use python3.6 and Django 2.1

EDIT: Here is a relevant part of my models.py:

class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)

trusted = models.BooleanField(default=False)
address = models.CharField(max_length=100, default="")

COUNTRIES = (
    ('CZ', 'Czech Republic'),
    ('EN', 'England'),
)
country = models.CharField(max_length=2, choices=COUNTRIES, default="CZ")

score = models.BigIntegerField(default=0)

achievements = models.ManyToManyField(Achievement, blank=True)

USER_TYPES = (
    ('N', 'Normal'),
    ('C', 'Contributor'),
    ('A', 'Admin'),
)
user_type = models.CharField(max_length=1, choices=USER_TYPES, default='N')


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
    Token.objects.create(user=instance)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created=False, **kwargs):
if created:
    profile, created = Profile.objects.get_or_create(user=instance)
profile.save()

EDIT:

Mohammad Ali's answers solves this for GET, but I'd also like to use POST, UPDATE and PATCH methods. I have found that I have to use source parameter but this is relative to serializer I don't know how to reference profile wihtout having profile field.

Jan Kaifer
  • 664
  • 4
  • 18

2 Answers2

1

Take it easy, You can create Profile obj just in the create function.

class UserSerializer(serializers.ModelSerializer):

    trusted = serializers.BooleanField()
    address = serializers.CharField()

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

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

        profile = Profile(user=user, trusted=validated_data['trusted'], address=validated_data['address']
        profile.save()
        return validated_data

It is just a brief implementation of your scenario. you can fill the story.

Ali
  • 2,541
  • 2
  • 17
  • 31
  • Thanks. And are you sure I don't need to tell where he should look for information in trusted and address field in case of GET request? – Jan Kaifer Sep 01 '18 at 12:46
  • Yeah, I'm sure. You can try it. – Ali Sep 01 '18 at 15:20
  • Well, It doesn't work. It says that `source` needs to be specified. (When I'm trying to list all users) – Jan Kaifer Sep 01 '18 at 15:52
  • @Jan Kaifer this strategy is just for creating new user, not for displaying list of user. Your question was about creating a user with address – Ali Sep 01 '18 at 16:12
  • I'm sorry for an unclear question I ment to ask for Serializer that can do both. – Jan Kaifer Sep 02 '18 at 13:48
-1

Plase read documentation for Serializers: Django REST FRAMEWORK -- user related_name

user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="user_profile") # models

class ProfileSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)

     class Meta:
        model = Profile
        fields = '__all__'


class UserSerializer(serializers.ModelSerializer):
    user_profile = ProfileSerializer(required=True)

    class Meta:
        model = User
        fields = '__all__'
Marin
  • 1,098
  • 14
  • 33