简体   繁体   English

Django Rest Framework如何为每个ViewSet操作定义必填字段

[英]django rest framework how to define required fields per ViewSet action

I am trying the tutorial from DRF and I found something confused. 我正在尝试DRF的教程,发现有些困惑。 I have a User model which simply extends auth.User like this 我有一个简单地扩展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)

Then I have my basic UserSerializer: 然后,我有了基本的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')

In my views.py, I have UserViewSet: 在我的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

The issue is, in validate_email, I actually only need pk but when I test the API, it told me that username and email are also required. 问题是,在validate_email中,我实际上只需要pk,但是当我测试API时,它告诉我也需要用户名和电子邮件。

I then added following code to my UserSerializer 然后,我向UserSerializer添加了以下代码

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

Now the above issue is gone, but when I tried to create an User, I actually do want to require username and email. 现在,上述问题已经解决,但是当我尝试创建用户时,我实际上确实需要输入用户名和电子邮件。

Is there a way that I can specify which fields are required per action? 有没有一种方法可以指定每个操作需要哪些字段? For example, for my set_password(), I want to require the password field. 例如,对于我的set_password(),我想要输入密码字段。

Thanks, 谢谢,

Try to override serializer constructor to modify fields based on extra arguments. 尝试重写序列化程序的构造函数,以基于其他参数修改字段。 Didn't test that but it should work: 没有测试,但它应该工作:

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

Then pass require_password or/and require_email arguments when you need: 然后在需要时传递require_password或/和require_email参数:

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

I turned out to implement it myself, so I basically make all fields option but in actions, I added a decorator to ensure the request body has those specified keys in it. 我原来是自己实现的,所以我基本上使所有字段成为选项,但在操作中,我添加了一个装饰器以确保请求主体中具有那些指定的键。

decorator: 装饰者:

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

How to use it: 如何使用它:

    @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)

I guess it is not elegant but probably enough for me for now. 我想这并不优雅,但对我来说可能足够了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM