【问题标题】:django rest framework create user with passworddjango rest框架用密码创建用户
【发布时间】:2015-04-20 11:26:42
【问题描述】:

使用 django-rest-framework 3 和 django 1.8

我正在尝试使用 django-rest-framework ModelViewSerializer 创建用户。问题是 DRF 使用的默认 objects.create 方法将密码保留为纯文本。

问题是 DRF 序列化器创建方法使用 objects.create querysets/#create 方法而不是使用 objects.create_user 方法。

serializers.py 第 775 行的代码

instance = ModelClass.objects.create(**validated_data)

对此最好的解决方案是什么?我可以重写 serializer.create 方法以使用 objects.user_create 而不是 objects.create 但它感觉不是正确的解决方案。

其余代码:

from django.contrib.auth.models import User
from rest_framework import viewsets

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'email','password')
        write_only_fields = ('password',)


class UserViewSet(viewsets.ModelViewSet):   
    queryset = User.objects.all()
    serializer = UserSerializer()

【问题讨论】:

  • 为什么你觉得不合适?我想我会走那条路。搜索了一下,至少找到了这个 SO 帖子(不同的问题),但在接受的答案中覆盖 create() 用于此目的。 stackoverflow.com/a/27763502/870769

标签: python django django-rest-framework


【解决方案1】:

您可以在 UserSerializer 中覆盖 create

class UserSerializer(serializers.ModelSerializer):
    # ....

    def create(self, validated_data):
        user = User.objects.create_user(**validated_data)
        return user

其他解决方案可以在 ViewSet 类中覆盖 perform_create,或者您可以在视图集类中编写自己的 create 方法

class UserViewSet(viewsets.ModelViewSet): 
    def create(self, request, format=None):
        # create user here
        # do not call seriailzer.save()

更新:在@freethebees 评论后,覆盖perform_create 也可以,所以这里是代码sn-p:

class UserViewSet(viewsets.ModelViewSet, mixins.CreateModelMixin): 
    def perform_create(self, serializer):
        # use User.objects.create_user to create user
        pass

注意: 此答案提供 3 个解决方案,选择您认为更适合您的需求和项目生态系统的解决方案

注意 2 我个人更喜欢在UserViewSet(第二个代码sn-p)中覆盖create,因为在那里您可以简单地返回您的自定义Response(例如登录后返回用户配置文件)

【讨论】:

  • 为什么建议在序列化程序中覆盖 create 方法而不是在 ViewSet 中覆盖 perform_create
  • @freethebees 这是一个旧答案,我不记得为什么 :-),但你是对的 perform_create 似乎是更清洁的解决方案,我会更新我的答案。
  • 哦,我并不是说哪种方式更好。我只是感兴趣。我还没有看到哪一种方法是明确的选择。
  • 不使用User.objects.create_user,您可以使用django.contrib.auth.hashers 中的make_password 函数吗?所以,在perform_create 里面你会有两行:serializer.validated_data['password'] = make_password(serializer.validated_data['password'])serializer.save()
  • @Nick 为您的解决方案我认为您只需要在序列化程序中更新 validate_password 就可以了。 (但我个人不喜欢它,1. 在 django 文档中建议使用 create_user(考虑自定义用户模型,您应该在序列化程序中执行所有 create_user 内容,在 2 个位置更新相同的代码) 2. 我更喜欢显式解决方案(我认为您的解决方案使代码更难理解))
【解决方案2】:

除了@aliva 的回答你错过了serializers.Modelserializer.create() 中的功能(这可能会很好地保留,例如处理多对多关系),还有一种方法可以保留它。

使用user.set_password()方法,也可以正确设置密码,如:

class UserSerializer(serializers.ModelSerializer):

    def create(self, validated_data):
        user = super().create(validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

这样做的好处是保留了超类的功能,但缺点是额外写入数据库。决定哪种权衡对您更重要:-)。

请参阅set_password 的文档。

【讨论】:

  • 这对我有用!我不明白为什么这个问题的其他建议方法不起作用。
  • 没有其他人建议这样做的原因是,它将原始用户密码保存到数据库中,这是一种巨大的反模式。即使它在数据库中仅存在 1 毫秒,它也可以永远存在于日志中。
  • @jkatzer : set_password() 散列密码。还是有问题吗?
  • @Aurélien 是的,这是证实这一点的文档:docs.djangoproject.com/en/3.1/topics/auth/customizing/…
  • @Erikw 确实,我意识到了这一点,所以这不是一种“反模式”的做法,不是吗?
【解决方案3】:

在序列化程序中验证密码有更好的选择

from django.contrib.auth.hashers import make_password

class UserSerializer(serializers.ModelSerializer):
    def validate_password(self, value: str) -> str:
        return make_password(value)

【讨论】:

  • 如果您的视图支持,这还有一个额外的好处,那就是也适用于更新。
  • return validate_password(value) 在这里效果更好,Django 3.2.2
  • serializer.create 方法中validated_data['password'] 的值与旧值相同,你知道吗?
【解决方案4】:

支持POSTPUT/PATCH 的完整示例,无需其他SQL UPDATE 语句。

class MyUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = '__all__'

    def create(self, validated_data):
        if "password" in validated_data:
            from django.contrib.auth.hashers import make_password
            validated_data["password"] = make_password(validated_data["password"])
        return super().create(validated_data)

    def update(self, instance, validated_data):
        if "password" in validated_data:
            from django.contrib.auth.hashers import make_password
            validated_data["password"] = make_password(validated_data["password"])
        return super().update(instance, validated_data)

【讨论】:

  • 太好了,谢谢。在搜索了很长时间后,我没有意识到 Django 正在对密码进行哈希处理,导致我所有的登录尝试都失败了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-21
  • 2020-04-25
  • 2020-05-15
  • 2016-07-17
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
相关资源
最近更新 更多