2

This question is based on the one here. I am setting up a Django REST Framework for my web app and am trying to set up User accounts. Based on the REST documentation, they put all of their account code in their example in the main project directory and a separate application so did that as well. Here is what I have:

urls.py

from django.contrib import admin
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from . import views

router = routers.DefaultRouter()
router.register('users', views.UserViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    url('', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

serializers.py

from django.contrib.auth.models import User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()

        return user

    class Meta:
        model = User
        # Tuple of serialized model fields (see link [2])
        fields = ( "id", "username", "password", )

views.py

from rest_framework import viewsets, permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth.models import User
from .serializers import UserSerializer


# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]


class CreateUserView(CreateAPIView):
    model = User
    permission_classes = [
        permissions.AllowAny
    ]
    serializer_class = UserSerializer

I have tried using the Boomerang REST Client in Chrome to POST data to this API, but it always returns a 403 Error saying "Invalid username/password." Specifically I am POSTing to http://127.0.0.1:8000/users/create/ with a Query String and 2 parameters: username and password. I also tried sending it as JSON and it returned the same. Any help would be appreciated.

arc-menace
  • 435
  • 6
  • 19

3 Answers3

2

It doesn't look like CreateUserView was registered in your urls.py. You should be able to register it and access it normally. I think this should work for you:

urlpatterns = [
    ...
    url(r'^users/create/', views.CreateUserView.as_view()),
]

That said, I'd like to suggest adding an extra action for your UserViewSet instead:

# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated

    @action(methods=['post'], detail=False, permission_classes=[permissions.AllowAny])
    def register(self, request, *args, **kwargs):
        # This logic was taken from the `create` on `ModelViewSet`. Alter as needed.
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Then you should be able to post via /users/register/. You can also specify your own url name and path on the decorator.

aredzko
  • 1,690
  • 14
  • 14
  • This somewhat worked for me. Adding the CreateUserView to urls.py did not work, but adding the action to UserViewSet did so I am fine with using that. However, I still cant seem to post data from Boomerang, I can only add it manually by a basic HTML form. – arc-menace May 10 '20 at 01:38
1

Maybe you are posting in the wrong url, try POST the same on http://127.0.0.1:8000/users/, because ModelViewSet adds POST, PATCH, PUT, DELETE and GET methods automatically.

Also because you are asking for authentication (permission_classes = [permissions.IsAuthenticated]), you should send the headers for this in the request. There is a tutorial for this in the DRF site (https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/)

Jorge Luis
  • 902
  • 12
  • 25
  • Doesn't seem to help. POSTing to /users/ still results in an incorrect username/password response – arc-menace May 10 '20 at 02:21
  • @arc-menace this `Boomerang REST Client` allow headers for authentication? – Jorge Luis May 10 '20 at 02:24
  • yes it does. (and apparently my comment has to have enough characters) – arc-menace May 10 '20 at 02:33
  • I actually am not asking for authentication as I need anon users to be able to access it. In CreateUserView i set the permissions to AllowAny to accomodate this – arc-menace May 10 '20 at 02:38
  • @arc-menace comment this line `permission_classes = [permissions.IsAuthenticated]` in your `UserViewSet` and repeat your `POST`, if it works, you'll need to figure out a url for your other ViewSet for creating users. My opinion is to user the same url for everything and as Ehsan says, implement a method in the same class, like `def create(self, request):` – Jorge Luis May 10 '20 at 02:44
1

based on django-rest-framework documents it's better to use viewset for create user api. therefor you need to send a POST request to http://127.0.0.1:8000/api-auth/users and no need to CreateUserView function.

But if you want to have a custom user create api do you need something like below:

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'], permission_classes=[permissions.AllowAny])
    def create_user(self, request, pk=None):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

To have custom serializers in your ViewSet you can specify them in get_serializer_class function in your ViewSet like this:

class UserViewSet(viewsets.ModelViewSet):
# example viewset
    def get_serializer_class(self):
        if self.action == 'list':
            return ListUserSerializer
        elif self.action == 'create':
            return CreateUserSerializer
        elif self.action == 'update':
            return UpdateUserSerializer
        return DetailUserSerializer
Ehsan
  • 3,711
  • 27
  • 30