django rest框架如何根据ViewSet动作定义必需的字段

时间:2016-07-30 09:28:35

标签: python django django-rest-framework

我正在尝试DRF的教程,我发现了一些困惑。 我有一个用户模型,它只是像这样扩展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(),我想要输入密码字段。

谢谢,

2 个答案:

答案 0 :(得分:0)

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

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)

答案 1 :(得分:0)

我自己实现了它,所以我基本上都选择了所有字段,但是在动作中,我添加了一个装饰器来确保请求体中有指定的键。

装饰:

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)

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