Django-rest-framework permissions for create in vi

2019-02-04 14:22发布

问题:

I am trying to create a REST API and am stuck at user registration: basically I need to have the access token before I register.

This is the view:

class UserViewSet(viewsets.ModelViewSet):

    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def metadata(self, request):
        """
        Don't include the view description in OPTIONS responses.
        """
        data = super(UserViewSet, self).metadata(request)
        return data

    def create(self, request):
        serializer = self.get_serializer(data=request.DATA, files=request.FILES)

        if serializer.is_valid():
            self.pre_save(serializer.object)
            self.object = serializer.save(force_insert=True)
            self.post_save(self.object, created=True)
            self.object.set_password(self.object.password)
            self.object.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

This is the workaround:

@api_view(['POST'])
@permission_classes((AllowAny,))
@csrf_exempt
def create_auth(request, format=None):
    data = JSONParser().parse(request)
    serialized = UserSerializer(data=data)

    if serialized.is_valid():
        user = User.objects.create_user(
            serialized.init_data['email'],
            serialized.init_data['username'],
            serialized.init_data['password'],
        )
        user.groups = serialized.init_data['groups']

        user.save()

        serialized_user = UserSerializer(user)
        return Response(serialized_user.data, status=status.HTTP_201_CREATED, headers={"Access-Control-Allow-Origin": "http://127.0.0.1:8000/"})
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST, headers={"Access-Control-Allow-Origin": "http://127.0.0.1:8000/"})

My question is: How can I specify in the UserViewSet that for the create I don't require credentials? Or specify a custom authentication method? I don't want to change the authentication/permission classes for the whole viewset.

Thanks, Adi

EDIT to clarify: unregistered users should be allowed to POST registration data and should not be allowed anything else. Authenticated users can get the user list and update their own profile...this is the default behaviour. This is why AllowAny is not an option. In my view, the proper place for this is the create function, but I don't get what I am supposed to override.

回答1:

Customize the get_queryset method:

def get_queryset(self):
    if self.request.user.is_superuser:
        return User.objects.all()
    else:
        return User.objects.filter(id=self.request.user.id)

This way, an authenticated user can only retrieve, modify or delete its own object.

Specify the permission_classes = (AllowAny,) so an authenticated user can create a new one.

EDIT: further explanation from comments

Customizing the get_queryset method this way means the following:

  1. Yes, non-authenticated users can send the GET request to retrieve the user list but it will be empty because the return User.objects.filter(id=self.request.user.id) ensures that only information about the authenticated user is returned.

  2. The same applies for other methods, if an authenticated user tries to DELETE another user object, a detail: Not found will be returned (because the user it is trying to access is not in the queryset).

  3. Authenticated users can do whatever they want to their user objects.



回答2:

You could utilizing Django REST Framework's ability to define custom permissions. You can specify both a has_permission and has_object_permission within a custom class. This will give you the expected behavior of throwing 403s to anon users for everything except posting to the creation endpoint. It might look something like:

class IsAnonCreate(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method == "POST" and not request.user.is_authenticated():
            return True
        elif not request.user.is_authenticated() and request.method != "POST":
            return False
        elif request.method in permissions.SAFE_METHODS:
            return True

        return False

    def has_object_permission(self, request, view, obj):
        if not request.user.is_authenticated():
            return False
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.username == request.user.username

You could then add some custom handling for authenticated users if you wanted.

Then all you need to do is add the permission class to your ModelViewSet:

class UserViewSet(viewsets.ModelViewSet):

    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsAnonCreate, )


回答3:

This is based on @argaen answer and it worked for me:

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = (AllowAny,)
    authentication_classes = (NoAuthentication,)
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('id', 'email', 'name')

    def get_queryset(self):
        user = TokenAuthentication().authenticate(self.request)
        if user is not None:
            user = user[0]
            if user.is_superuser:
                return get_user_model().objects.all()
            else:
                return get_user_model().objects.filter(id=user.id)

        return get_user_model().objects.none()