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.
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as . get() or . post() , and instead provides actions such as . list() and . create() .
Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; GET , HEAD or OPTIONS . This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users.
APIView allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Viewsets allow us to define functions that match to common API object actions like : LIST, CREATE, RETRIEVE, UPDATE, etc.
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:
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.
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).
Authenticated users can do whatever they want to their user objects.
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, )
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With