【问题标题】:django rest framework how to define required fields per ViewSet actiondjango rest框架如何为每个ViewSet操作定义必填字段
【发布时间】:2016-12-04 23:20:53
【问题描述】:

我正在尝试 DRF 的教程,但我发现有些困惑。 我有一个 User 模型,它只是像这样扩展 auth.User

class User(DefaultUser):
"""
Represents a registered User
"""
EMAIL_VALIDATOR_LENGTH = 6

email_validated = models.BooleanField(default=False)
# using a 6-digit numbers for email validation
email_validator = models.CharField(
    max_length=6,
    default=_get_random_email_validator(EMAIL_VALIDATOR_LENGTH),
    editable=False
)
phone_number = models.CharField(validators=[phone_regex],
                                blank=True, null=True, max_length=64)
# country is required
country = models.ForeignKey('Country', null=False, blank=False)
# subdivision is optional
subdivision = models.ForeignKey('Subdivision', null=True, blank=True)

然后我有了基本的 UserSerializer:

class UserSerializer(serializers.ModelSerializer):

class Meta:
    model = User
    fields = ('id', 'email', 'password', 'email_validated',
              'email_validator', 'country', 'subdivision', 'phone_number',
              'last_login', 'is_superuser', 'username', 'first_name',
              'last_name', 'is_staff', 'is_active', 'date_joined')

在我的views.py中,我有UserViewSet:

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

@detail_route(methods=['get', 'post'], url_path='validate-email')
def validate_email(self, request, pk):
    user = self.get_object()
    serializer = UserSerializer(data=request.data)
    if serializer.is_valid():
        user.is_active = True
        user.save()
        return Response({'status': 'email validated'})
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@detail_route(methods=['post'], url_path='set-password')
def set_password(self, request, pk):
    pass

@list_route()
def test_list_route(self, request):
    pass

问题是,在 validate_email 中,我实际上只需要 pk 但是当我测试 API 时,它告诉我还需要用户名和电子邮件。

然后我将以下代码添加到我的 UserSerializer

        extra_kwargs = {'country': {'required': False},
                    'password': {'required': False},
                    'username': {'required': False},
                    }

现在上面的问题已经解决了,但是当我尝试创建用户时,我确实想要求用户名和电子邮件。

有没有一种方法可以指定每个操作需要哪些字段? 例如,对于我的 set_password(),我想要求密码字段。

谢谢,

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    结果我自己实现了它,所以我基本上将所有字段都设置为选项,但在操作中,我添加了一个装饰器以确保请求正文中包含那些指定的键。

    装饰者:

    class AssertInRequest(object):
    """
    A decorator to decorate ViewSet actions, this decorator assumes the first
    positional argument is request, so you can apply this decorator any methods
    that the first positional argument is request.
    
    This decorator itself takes a list of strings as argument, and will check
    that the request.data.dict() actually contains these keys
    
    For example, if you have a action to set user password which you expect that
    in the request body should have 'password' provided, use this decorator for
    the method like
    
    @detail_route()
    @AssertInRequest(['password'])
    def set_password(self, request, pk):
        pass
    """
    
    def __init__(self, keys):
        self.keys = []
        for key in keys:
            if hasattr(key, 'upper'):
                self.keys.append(key.lower())
    
    def __call__(self, func):
        def wrapped(*args, **kwargs):
            if self.keys:
                try:
                    request = args[1]
                except IndexError:
                    request = kwargs['request']
                if request:
                    json_data = get_json_data(request)
                    for key in self.keys:
                        if key not in json_data or not json_data[key]:
                            return DefaultResponse(
                                'Invalid request body, missing required data [%s]' % key,
                                status.HTTP_400_BAD_REQUEST)
            return func(*args, **kwargs)
    
        return wrapped
    

    使用方法:

        @detail_route(methods=['post'], url_path='set-password', permission_classes=(IsAuthenticated,))
    @AssertInRequest(['password'])
    def set_password(self, request, pk):
        user = self.get_object()
        json_data = get_json_data(request)
        user.set_password(json_data['password'])
        user.save()
        return DefaultResponse(_('Successfully set password for user %s'
                                 % user.email), status.HTTP_200_OK)
    

    我想这并不优雅,但对我来说可能已经足够了。

    【讨论】:

    • 你从哪里导入get_json_data?我在谷歌上只能找到一些具有这种方法的第三方库。
    【解决方案2】:

    尝试重写序列化器构造函数以根据额外参数修改字段。没有测试,但它应该可以工作:

    class UserSerializer(ModelSerializer):
    
        def __init__(self, *args, **kwargs):
            super(UserSerializer, self).__init__(*args, **kwargs)
            require_password = kwargs.get('require_password', False)
            require_email = kwargs.get('require_email', False)
    
            if require_password:
                self.fields['password'].required = True
    
            if require_email:
                self.fields['email'].required = True
    

    然后在需要时传递require_password 或/和require_email 参数:

    serializer = UserSerializer(data=request.data, require_password=True, require_email=True)
    

    【讨论】:

    • 感谢您的回复,但我认为它不起作用。我将以下代码添加到我的 UserSerializer 中,它仍然需要用户名: self.fields['username'].require=False
    猜你喜欢
    • 2018-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 2016-06-28
    • 1970-01-01
    • 2013-06-21
    相关资源
    最近更新 更多