Django REST Framework Creating custom user

2019-01-17 21:29发布

问题:

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.

回答1:

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)


回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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


回答6:

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