Django Rest Framework的序列化器和视图

6

我不太清楚如何在DRF中实现序列化器和视图的方法:

我有一个扩展了AbstractBaseUser的账户模型。视图集如下所示:

class AccountViewSet(viewsets.ModelViewSet):
    lookup_field = 'username'
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

    def get_permissions(self):
        if self.request.method in permissions.SAFE_METHODS:
            return (permissions.AllowAny(), TokenHasReadWriteScope())

        if self.request.method == 'POST':
            return (permissions.AllowAny(), TokenHasReadWriteScope())

        return (permissions.IsAuthenticated(), IsAccountOwner(), TokenHasReadWriteScope())

    def create(self, request):
        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():
            Account.objects.create_user(**serializer.validated_data)

            return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
        return Response({
            'status': 'Bad request',
            'message': 'Account could not be created with received data.'
        }, status=status.HTTP_400_BAD_REQUEST)

序列化器就像这样:
class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'email', 'username', 'created_at', 'updated_at',
                  'first_name', 'last_name', 'tagline', 'password',
                  'confirm_password',)
        read_only_fields = ('created_at', 'updated_at',)

    def create(self, validated_data):
        return Account.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.username = validated_data.get('username', instance.username)

        instance.save()

        password = validated_data.get('password', None)
        confirm_password = validated_data.get('confirm_password', None)

        if password and confirm_password:
            instance.set_password(password)
            instance.save()

            update_session_auth_hash(self.context.get('request'), instance)

        return instance

def validate(self, data):
        if data['password'] and data['confirm_password'] and data['password'] == data['confirm_password']:
            try:
                validate_password(data['password'], user=data['username']):

                return data
            except ValidationError:
                raise serializers.ValidationError("Password is not valid.")
        raise serializers.ValidationError("Passwords do not match.")

在视图的create方法中,它会检查序列化器是否有效,然后保存它并根据结果返回响应。我的第一个问题是,序列化器的create()方法何时被调用?在我看来,该方法直接被调用视图的create()方法中调用的create_user(模型方法)绕过了。它是否被调用了?有什么意义吗?
第二个问题是,在update方法中返回状态码遇到麻烦,实例已在序列化器中保存。如果验证失败,序列化器update()内部的代码是否会起作用?
以下是目前的内容:
def update(self, request, pk=None):
        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():
            << what goes here??? >>

            return Response(serializer.validated_data, status=status.HTTP_200_OK)
        except serializers.ValidationError as e:
        return Response({
            'status': 'Bad request',
            'message': str(e)    
        }, status=status.HTTP_400_BAD_REQUEST)

        return Response({
            'status': 'Bad request',
            'message': 'Account could not be updated with received data.'
        }, status=status.HTTP_400_BAD_REQUEST)

我非常需要澄清一些事情。我不确定如何请求视图/序列化器方法的流,并且不确定如何在序列化器中保存实例并同时决定在视图中返回哪个响应。
编辑:
我删除了 AccountViewSet 的 create 和 update 方法,并修复了 get_permissions,按照您的建议,我还添加了用户名验证到 validate 中。我还更新了序列化器的 create 和 update 方法,以下是新版本:
def create(self, validated_data):
    instance = super(AccountSerializer, self).create(validated_data)
    instance.set_password(validated_data['password'])
    instance.save()
    return instance

def update(self, instance, validated_data):
    instance.username = validated_data.get('username', instance.username)

    password = validated_data.get('password', None)
    confirm_password = validated_data.get('confirm_password', None)

    if password and confirm_password:
        instance.set_password(password)
        instance.save()
        update_session_auth_hash(self.context.get('request'), instance)
    else:
        instance.save()

    return instance

我只有一个问题,创建用户后是否需要调用set_passwordcreate不是已经为新用户设置了密码吗?另外,在createupdate视图中没有代码会出现问题吗?如果没有视图代码,serializer.save()何时被调用?serializer.validate何时运行而不需要调用serializer.is_valid()

2个回答

6
在你的AccountViewSet类的create()方法中,当序列化程序验证通过时,你正在创建Account实例。相反,你应该调用serializer.save()
如果你查看BaseSerializer类中的save()方法,你会发现它调用create()update()方法,具体取决于是否正在创建或更新模型实例。由于你没有在AccountViewSet.create()方法中调用serializer.save(),因此AccountSerializer.create()方法不会被调用。希望这回答了你的第一个问题。
对于你的第二个问题,答案也是缺少serializer.save()。将<< what goes here??? >>替换为serializer.save()(如上所述),这将调用AccountSerializer.update()方法。

谢谢,我会尝试那样做。我没有在创建时调用保存,因为这会创建一个未经散列的帐户,导致它根本无法工作。 - ss7

5

AccountViewSet:

您不需要使用.create().update()方法,根据您的示例,现有方法应该足够。

get_permissions() - 首个"if"语句在我看来将您的系统开放得太宽泛,应该被删除 —— 您允许任何人进行POST——也就是创建新帐户,这没问题,但其他操作(比如 GETPUT)——只有帐户所有者或者已注册用户才能执行(如果需要的话!)

AccountSerializer:

  • API will return HTTP400 on failed validation
  • make sure that id field is readonly, you do not want someone to overwrite existing users
  • existing create() method can be removed, but I think your's should looks like:

    def create(self, validated_data):
        instance = super(AccountSerializer, self).create(validated_data)
        instance.set_password(validated_data['password'])
        instance.save()
        return instance
    
  • existing update() method... not sure what you wanted, but:

    • first line is permitting user to change it's username, without validation e.g. username is unique, even more, you do not check at all what is in the username field passed from the request, it may be even empty string or None - fallback on dictionary.get will be called only when key is missing in the dictionary,
    • if uniqueness of username will be validated on the db level (unique=True in the model's field definition) - you will get strange exception instead of nice error message for the API)
    • next is validation of the passwords (again) - that was just tested in validate method
    • after setting user password you save instance.. second time - maybe it is worth to optimize it and have only one save?
    • if you do not allowing to update all of the fields, maybe it is a good idea to pass update_fields to save() to limit which fields are updated?
  • validation - just add username validations ;)

简单了解DRF(非常简化)- 序列化器是API的表单ViewSets是通用的视图,渲染器是模板(决定数据如何显示)

--编辑--

关于viewsets的几个要点,ModelViewSet包括:

  • mixins.CreateModelMixin - 调用验证和创建 -> serializer.save -> serializer.create
  • mixins.RetrieveModelMixin - "获取"实例
  • mixins.UpdateModelMixin - 调用验证和更新/部分更新 -> serializer.save -> serializer.update
  • mixins.DestroyModelMixin - 调用实例删除
  • mixins.ListModelMixin - "获取"实例列表(浏览)

我编辑了问题以包含您建议的更改以及关于您提出的更改的一些小问题。 "set_password"是否被"create"调用?没有视图代码,"serializer.save()"和"serializer.is_valid()"是否仍然被调用,何时调用?如果不是那么何时调用"update/create/validate"? - ss7
最后一个问题,我应该在序列化器的元数据中将id字段设置为只读还是其他地方。 - ss7
我已经检查过Django源代码,set_password在创建或更新期间不会被调用,但可以在form.save()之前调用它,这样设置新密码将包括在整个form.save()中。 - Jerzyk
当使用表单数据调用您的序列化程序时 - 将调用验证,然后(根据将其绑定到实例 - 创建或更新)- 查看ModelViewsSet源代码。 - Jerzyk
这些mixin将添加或扩展现有方法以按描述的方式运行 :) - Jerzyk
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接