22

I'm new in Django realm but see there is a lot of "magic" there. I'm using Django REST Framework and creating app that will allow free user registration. My user needs some additional fields that are not available in Django user. So I googled for extending User. There is an idea that this should be done by creating something like this

class MyUser(models.Model):
    user = models.ForeignKey(User, unique=True)
    city = models.CharField(max_length=50, blank=True, default='')

This is fine but I have this serializer

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyUser
        fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'city')

So, the problem is that this serializer does some "magic" here. It tries to figure out what field should model have ... I want to have user with fields listed here, and these are fields are in User and 'city' is new custom field. Serializer doesn't get that it should look inside User model.

What am I missing here? How to tell this serializer that I want some fields inside User? I need to be able to crete user too.

2
  • 7
    You should use OneToOneField for your UserModel relation, not ForeignKey since their can be only one MyUser for every User instance. Commented Nov 6, 2013 at 7:53
  • Use OneToOneField and follow this answer and you're good to go: link Commented Aug 31, 2016 at 10:06

6 Answers 6

19

So apparently I don't have enough reputation to post a comment under an answer. But to elaborate on what Kevin Stone described, if you model is something like the following:

class AppUser(models.Model):
    user = models.OneToOneField(User)
    ban_status = models.BooleanField(default=False)

You can do something like this to create both the custom user and django user:

class AppUserSerializer(serializers.ModelSerializer):
    username = serializers.CharField(source='user.username')
    email = serializers.CharField(source='user.email')
    password = serializers.CharField(source='user.password')
    ban_status = serializers.Field(source='ban_status')

    class Meta:
        model = AppUser
        fields = ('id', 'username', 'email', 'password', 'ban_status')

    def restore_object(self, attrs, instance=None):
        """
        Given a dictionary of deserialized field values, either update
        an existing model instance, or create a new model instance.
        """
        if instance is not None:
            instance.user.email = attrs.get('user.email', instance.user.email)
            instance.ban_status = attrs.get('ban_status', instance.ban_status)
            instance.user.password = attrs.get('user.password', instance.user.password)
            return instance

        user = User.objects.create_user(username=attrs.get('user.username'), email= attrs.get('user.email'), password=attrs.get('user.password'))
        return AppUser(user=user)
Sign up to request clarification or add additional context in comments.

1 Comment

Note that restore_object( ) is not compatible with DRF 3.x so you'd need to use create() and update() methods instead. Example here: django-rest-framework.org/topics/3.0-announcement/#serializers.
16

Okay, a couple of things. You want to create a OneToOneField for your user model extension.

class MyUser(models.Model):
    user = models.OneToOneField(User)
    city = models.CharField(max_length=50, blank=True, default='')

Now, the power of Django Rest Framework, is you can build your serializer, to take data from both of these models when serializing.

class UserSerializer(serializers.ModelSerializer):
    city = serializers.CharField(source='myuser.city')
    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'city')

Finally, where you're creating the user, since you're using custom fields, you need to implement your own restore_object() that builds both models from the input data.

Also, creating Users in Django is a bit different, you need to call create_user() and supply a password that is hashed, so its not as simple as storing fields from a serializer.

2 Comments

Could you expand on your answer to include an example implementation of restore_object() and the create_user()? I'm not sure how I should go about hashing the password... (the fact that you even suggest that we need to do that raises red flags in my mind - shouldn't Django or DRF be providing this kind of security out of the box?)
Thanks for this answer! Adding up to it, you could also go for the id-attribute user_profile=serializers.CharField(source='userprofile.id').
3

It would be nice if this use case was easier to find in the docs. As @jamod pointed out, in DRF 3, you can find it here:

class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()

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

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

1 Comment

how do i return a token instead of returning a user, it keeps giving me an attribute error.
2

When using Django Rest Framework you have to be careful. Any custom user model cannot utilize the built in token authentication. Until you can do that, I would suggest using a OneToOneField with user in your custom model. Your custom model will contain the extra fields you want to keep. One to One gives you access to the user from the custom user, and the custom user from the user.

1 Comment

It's been almost a year since you posted this - does DRF support Django's Custom User Models yet or is this answer still correct and OneToOneFields with users still the best idea?
1

If you're using django 1.5 or greater then use custom user model instead, this way user model will have it's own dedicated table and serializer will then pick up the fields correctly.

1 Comment

Think it's not really an option, probably I'll be using 1.4. I don't really understand is this really hard to do? Why is that? I seams like standard thing to do, why is this so complicated?
1

I prefer to use the django signals module, which sends signals to the app when something happens, and among other things will let you call a function of your own before/after other functions. My answer is similar to Stuart's answer but keeps all of the code relevant to your new extension class in one place (if you want to delete the profile later or change its name you don't have to look anywhere else).

The following code lays out your extended class model, in this case a user profile, then creates an empty instance when a user model is created, then saves the instance with new information (that you must add yourself) by saving the parent user instance i.e. - user.save()

models.py

from django.db.models.signals import post_save
from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model): #This extends the user class to add profile information
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    #add your extra traits here: is_nice, is_shwifty, address, etc.
    is_nice = models.BooleanField(default=True, blank=True) 

# a user model was just created! This now creates your extended user (a profile):
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        # instance is the user model being saved.
        Profile.objects.create(user=instance)

# a user model was just saved! This now saves your extended user (a profile):
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
        instance.profile.save()

If you don't have a ProfileSerializer: serializers.py

#use hyperlinkedmodelserializer for easy api browsing + clicking
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
    user = UserSerializer() 
    class Meta:
        model = Profile
        fields = ('url', 'user', 'is_nice')

After you create your user and save your user, you'll have an empty user.profile to add information to. For example, after running python manage.py shell try:

from backend.models import User, Profile
#create your user
user=User(username="GravyBoat")
user.save()
#now update your user's profile
user.profile.is_nice=False
#that's one mean gravy boat
user.save()
user.profile.is_nice
#False

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.