Django REST Framework 创建自定义用户

Posted

技术标签:

【中文标题】Django REST Framework 创建自定义用户【英文标题】:Django REST Framework Creating custom user 【发布时间】:2013-11-15 08:25:39 【问题描述】:

我是 Django 领域的新手,但看到那里有很多“魔法”。我正在使用 Django REST Framework 并创建允许免费用户注册的应用程序。我的用户需要一些在 Django 用户中不可用的附加字段。所以我用谷歌搜索扩展用户。有一个想法是应该通过创建类似这样的东西来完成

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

这很好,但我有这个序列化程序

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

所以,问题是这个序列化器在这里做了一些“魔法”。它试图找出模型应该具有什么字段... 我想让用户在这里列出字段,这些字段在用户中,“城市”是新的自定义字段。序列化程序不知道它应该在用户模型内部查看。

我在这里缺少什么?如何告诉这个序列化程序我想要用户内部的一些字段?我也需要能够创建用户。

【问题讨论】:

您应该使用OneToOneField 作为您的UserModel 关系,而不是ForeignKey,因为对于每个User 实例,它们只能是一个MyUser 使用OneToOneField并按照这个答案,你很高兴:link 【参考方案1】:

所以显然我没有足够的声誉在答案下发表评论。但要详细说明 Kevin Stone 所描述的内容,如果您的模型类似于以下内容:

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

您可以这样做来创建自定义用户和 django 用户:

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)

【讨论】:

请注意,restore_object( ) 与 DRF 3.x 不兼容,因此您需要改用 create() 和 update() 方法。此处示例:django-rest-framework.org/topics/3.0-announcement/#serializers.【参考方案2】:

好的,有几件事。你想为你的用户模型扩展创建一个OneToOneField

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

现在,Django Rest Framework 的强大之处在于,您可以构建您的序列化程序,以便在序列化时从这两个模型中获取数据。

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

最后,在您创建用户的地方,由于您使用的是自定义字段,因此您需要实现自己的 restore_object(),它会根据输入数据构建两个模型。

另外,在 Django 中创建用户有点不同,您需要调用 create_user() 并提供经过哈希处理的密码,因此它不像从序列化程序中存储字段那么简​​单。

【讨论】:

您能否扩展您的答案以包括restore_object()create_user() 的示例实现?我不确定我应该如何对密码进行哈希处理......(你甚至建议我们需要这样做的事实在我脑海中引发了危险信号 - Django 或 DRF 不应该提供这种安全性吗?盒子?) 感谢您的回答!加上它,您还可以选择 id-attribute user_profile=serializers.CharField(source='userprofile.id')【参考方案3】:

如果这个用例更容易在文档中找到,那就太好了。正如@jamod 指出的那样,在 DRF 3 中,您可以找到它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

【讨论】:

我如何返回令牌而不是返回用户,它一直给我一个属性错误。【参考方案4】:

使用 Django Rest Framework 时必须小心。任何自定义用户模型都不能使用内置的令牌身份验证。在您能做到这一点之前,我建议您在自定义模型中使用 OneToOneField 和用户。您的自定义模型将包含您要保留的额外字段。一对一使您可以从自定义用户访问用户,并从用户访问自定义用户。

【讨论】:

自您发布此消息以来已经快一年了 - DRF 是否支持 Django 的自定义用户模型,或者这个答案仍然正确并且 OneToOneFields 与用户仍然是最好的主意?【参考方案5】:

如果您使用 django 1.5 或更高版本,请改用 custom user model,这样用户模型将拥有自己的专用表,然后序列化程序将正确提取字段。

【讨论】:

认为这不是一个真正的选择,可能我会使用 1.4。我真的不明白这真的很难做到吗?这是为什么?我的接缝看起来像是标准的事情,为什么这么复杂?【参考方案6】:

我更喜欢使用 django signals 模块,它会在发生某些事情时向应用程序发送信号,除此之外,您还可以在其他函数之前/之后调用自己的函数。我的回答与 Stuart 的回答类似,但将与您的新扩展类相关的所有代码保存在一个位置(如果您想稍后删除配置文件或更改其名称,则不必查看其他任何地方)。

以下代码列出了您的扩展类模型,在本例中为用户配置文件,然后在创建用户模型时创建一个空实例,然后通过保存父级来保存带有新信息(您必须自己添加)的实例用户实例即 - 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()

如果您没有 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')

创建用户并保存用户后,您将拥有一个空的 user.profile 来添加信息。比如运行python manage.py shell之后 试试:

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

【讨论】:

以上是关于Django REST Framework 创建自定义用户的主要内容,如果未能解决你的问题,请参考以下文章

Django Rest Framework 自定义身份验证

Django REST Framework 自定义字段验证

Django Rest Framework:如何将数据传递给嵌套的序列化器并仅在自定义验证后创建对象

Django Rest Framework - 如何在 ModelSerializer 中添加自定义字段

如何访问 Django Rest Framework 上的自定义 HTTP 请求标头?

DRF(Django REST Framework)框架