self.request.user not returning in queryset - python

In my views, queryset is returning all the users when I want it to be only returning the user that is currently logged. I have a get self method which has the serializer set to the user but it is not being used. When I tried get_queryset, self.request.user still doesn't return the user.
views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from rsm_app.api.v1 import serializer as serializers
from rsm_app.users.models import User
class CurrentUserView(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = serializers.UserSerializer
#queryset = User.objects.filter(name=request.user.name)
def get_queryset(self):
return self.request.user
def put(self, request):
serializer = serializers.UserSerializer(
request.user, data=request.data)
if request.data and serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response({}, status=status.HTTP_400_BAD_REQUEST)
Url.py:
from rest_framework import routers
from django.urls import path, re_path, include
from graphene_django.views import GraphQLView
from rsm_app.api.v1 import views
app_name = "api.v1"
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r"user", views.CurrentUserView, basename="user")
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path("graphql", GraphQLView.as_view(graphiql=True)),
re_path(r"^", include(router.urls)),
re_path(r"user/", views.CurrentUserView, name='user'),
re_path(r"^api-auth/", include("rest_framework.urls",
namespace="rest_framework")),
]
Edit FIXED: It was a session token not being saved issue.

They mentioned in the docs that you could access the current user by user = self.get_object()
You could see the full example from the official docs.

Can you try with:
queryset = User.objects.filter(name=request.user.name).first()
or
queryset = User.objects.get(name=request.user.name)

Related

Authentication credentials weere not provided when signing up

Building Django APIs to help me handle both the signup and login process for my React app.
I am able to successfully login (get an access and refresh token from Django upon login), but having difficulties connecting my React signup process to Django to create a new user.
This is the error I am getting on my console:
POST http://127.0.0.1:8000/users/ 401 (Unauthorized)
onSubmit # Signup.js:32
and on my network:
Authentication credentials were not provided.
This is what I have in my serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email', 'password']
extra_kwargs = {
'password': {'write_only':True},
}
def create(self,validated_data):
user = User.objects.create_user(validated_data['username'],
password=validated_data['password'],
email=validated_data['email'])
return user
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class UserSerializerWithToken(serializers.ModelSerializer):
token = serializers.SerializerMethodField()
password = serializers.CharField(write_only=True)
class PasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
accounts.urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('signup.urls')),
]
signup.urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from . import views
from rest_framework_simplejwt.views import (TokenObtainPairView, TokenRefreshView, TokenVerifyView)
from .api import RegisterApi
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = [
path('', include(router.urls)),
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('api/register', RegisterApi.as_view()),
]
and finally signup.views.py:
from django.contrib.auth.models import User, Group
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import viewsets, status
from rest_framework import permissions
from .serializers import RegisterSerializer, PasswordSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
#changed name from UserSerializer to RegisterSerializer
serializer_class = RegisterSerializer
# permission_classes = [permissions.IsAuthenticated]
#action(detail=True, methods=['POST'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.validated_data['new_password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Here is my api.py
from rest_framework import generics, permissions, mixins
from rest_framework.response import Response
from .serializers import RegisterSerializer, UserSerializer
from django.contrib.auth.models import User
from rest_framework.permissions import AllowAny
#Register API
class RegisterApi(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"message": "User Created Successfully. Now perform Login to get your token",
})
def get_permissions(self):
if self == 'create':
return [AllowAny()]
else:
return super().get_permissions()
def get_authenticators(self):
if self == 'create':
return []
else:
return super().get_authenticators()
I am posting to this URL in the signup process:
'http://127.0.0.1:8000/api/register'
The reason for this is that you have a default authentication/permission scheme which applies to all your views. However, your signup process (perhaps understandably) is not authenticated, since there is not user account to authorize at this point.
One way to get around this would be to allow anonymous signup by not requiring permissions to create a user, assuming that is a desired behavior for your app.
You can do this by overriding get_permissions on the ViewSet, making the required permission classes to be AllowAny only when the viewset action is create.
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
...
def get_permissions(self):
"""
dont require permissions for create actions
otherwise, follow normal required permissions
"""
if self.action == 'create':
return [AllowAny()]
else:
return super().get_permissions()
Alternatively, you can develop an authentication/authorization mechanism for creating new users. For example, you may choose to explore email-based token authentication for your user signup process.
You may also need to override get_authenticators(self):
...
def get_authenticators(self):
if self.action == 'create':
return []
else:
return super().get_authenticators()

Django rest framework : Prevent one user from deleting/Editing/Viewing other users in ModelViewSet

I was using Django users model for my Django rest framework. For this I used Django's ModelViewSet for my User class.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
extra_kwargs = {
'password' : {
'write_only':True,
'required': True
}
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user) # create token for the user
return user
But currently from postman when I make the request using the token of one user to view, delete, edit other users
http://127.0.0.1:8000/api/users/4/
Its able to edit/delete/view other users. I don't want that to happen and one user can make request on itself only is all I want.
This is my apps urls.py
urls.py
from django.urls import path, include
from .views import ArticleViewSet, UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('articles', ArticleViewSet, basename='articles')
router.register('users', UserViewSet, basename = 'users')
urlpatterns = [
path('api/', include(router.urls)),
]
How can I prevent one user from accessing other users when they make GET/POST/PUT/DELETE request.
EDIT 1: After adding the IsOwnerOfObject class as provided in he answers below, now when I am requesting the detail of the user himself, I am getting
Authentication credentials were not provided.
Building from Ene's answer, authentication and permission classes needs to be provided.
Create a file named permissions.py.
from rest_framework import permissions
class IsOwnerOfObject(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
next add the permission and authentication class to ModelViewSet:
from api.permissions import IsOwnerOfObject
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated, IsOwnerOfObject]
authentication_classes = (TokenAuthentication,)
Create a file named permissions.py.
from rest_framework import permissions
class IsOwnerOfObject(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
next add the permission class to you ModelViewSet:
from yourapp.permissions import IsOwnerOfObject
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsOwnerOfObject, <other permission classes you want to use>]
More info here:
https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
If you want to disable delete completely (Which is probably correct since if you want to "delete" a User you should deactivate it instead.) Then you can replace your view with this:
from rest_framework import viewsets
from rest_framework import generics
class UserViewSet(
generics.CreateModelMixin,
generics.ListModelMixin,
generics.RetrieveModelMixin,
generics.UpdateModelMixin,
generics.viewsets.GenericViewSet
):
queryset = User.objects.all()
serializer_class = UserSerializer
And then you can use Ene Paul's answer to limit who can edit.
Another all-in-one solution could be to use a queryset filter to directly narrow the queryset results. This will prevent an user to delete other users, but also prevent any unauthorized retrieving or listing as the only accessible data will be the user itself only.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
The get_queryset function is used to get the queryset of the user data corresponding to the logged in user.
It is called when the view is instantiated, and it returns a list containing this user only.
"""
# after get all products on DB it will be filtered by its owner and return the queryset
owner_queryset = self.queryset.filter(username=self.request.user.username)
return owner_queryset
And this can also be used with other objects to allow retrieving only data related to this user.

Django Rest Framework - URL Queries Not Working?

I am writting an application in Django that uses the Django Rest framework. A GET request to the API URL works, but when I add a query to it, such as '?id=1845' it does not actually perform a query, it still returns the full list of results.
In the below code, I am trying to query the 'Indicator List'
views.py
from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import IndicatorSerializer
from data_platform.models import Indicator
#api_view(['GET'])
def apiOverview(request):
api_urls = {
'Indicator List':'/indicator-list/',
'Indicator Detail':'/indicator-detail/<str:pk>/',
}
return Response(api_urls)
#api_view(['GET'])
def indicatorList(request):
indicators = Indicator.objects.all()
serializer = IndicatorSerializer(indicators, many=True)
#filterset_fields = ('id')
#filter_backends = [DjangoFilterBackend]
return Response(serializer.data)
#api_view(['GET'])
def indicatorDetail(request, pk):
indicators = Indicator.objects.get(id=pk)
serializer = IndicatorSerializer(indicators, many=False)
return Response(serializer.data)
urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.apiOverview, name="api-overview"),
path('indicator-list/', views.indicatorList, name="indicator-list"),
path('indicator-detail/<str:pk>/', views.indicatorDetail, name="indicator-detail"),
]
serializers.py
from rest_framework import serializers
from data_platform.models import Indicator
class IndicatorSerializer(serializers.ModelSerializer):
class Meta:
model = Indicator
fields = '__all__'
I figured it out! I had to use a class view rather then a function view
class indicatorList(generics.ListAPIView):
queryset = Indicator.objects.all()
serializer_class = IndicatorSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
filter_fields = ('id',)
and add 'django_filters', to INSTALLED_APPS

Django REST User Creation/Authentication

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.
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.
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/)
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

Django Rest Framework - adding JOIN endpoints

I'm making a REST API with Django Rest Framework (DRF) which has the following endpoints:
/users/
/users/<pk>/
/items/
/items/<pk>/
but I'd like to add the endpoint:
/users/<pk>/items/
which would of course return the items that belong (have a foreign key) to that user.
Currently my code is:
#########################
##### myapp/urls.py #####
#########################
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import response, schemas
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
from myapp.views import ItemViewSet, UserViewSet
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'items', ItemViewSet)
#api_view()
#renderer_classes([OpenAPIRenderer, SwaggerUIRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='My API')
return response.Response(generator.get_schema(request=request))
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
##########################
##### myapp/views.py #####
##########################
from django.contrib.auth import get_user_model
from rest_framework import viewsets, permissions
from myapp.serializers import MyUserSerializer, ItemSerializer
from myapp.models import Item
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = MyUserSerializer
permission_classes = (permissions.IsAuthenticated,)
class ItemViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
permission_classes = (permissions.IsAuthenticated,)
################################
##### myapp/serializers.py #####
################################
from rest_framework import serializers
from django.contrib.auth import get_user_model
from myapp.models import Item
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('pk', 'email',)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('pk', 'name',)
Is there a good way to add this endpoint in DRF, given how I'm using DRF?
I could just add a function view in urls.py like so:
from myapp.views import items_for_user
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^users/(?P<pk>[0-9]+)/items/$', items_for_user),
]
but I want to leverage DRF, get the browsable API, and make use of ViewSets instead of coding one-off function views like this.
Any ideas?
Took me a while to figure this out. I've been using view sets, so I'll give this answer within that setting.
First, URLConf and registered routes remain unchanged, i.e.,
router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'items', ItemViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
]
Your items will still be at /items/<pk>/, with permissions crafted for each of them depending on who requests them, by creating custom permissions, for example:
class IsItemOwnerPermissions(permissions.DjangoObjectPermissions):
"""
The current user is the owner of the item.
"""
def has_object_permission(self, request, view, obj):
# A superuser?
if request.user.is_superuser:
return True
# Owner
if obj.owner.pk == request.user.pk:
return True
return False
Next, for /user/<pk>/items/ you need to define #detail_route, like this:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
# Your view set properties and methods
#detail_route(
methods=['GET', 'POST'],
permission_classes=[IsItemOwnerPermissions],
)
def items(self, request, pk=None):
"""
Returns a list of all the items belonging to `/user/<pk>`.
"""
user = get_user_model().objects.get(pk=pk)
items = user.items.all()
page = self.paginate_queryset(items)
if page is None:
serializer = ItemSerializer(
objs, context={'request': request}, many=True
)
return Response(serializer.data)
else:
serializer = ItemSerializer(
page, context={'request': request}, many=True
)
return self.get_paginated_response(serializer.data)
A detailed route named xyz corresponds to the route user/<pk>/xyz. There are also list routes (#list_route); one named xyz would correspond to user/xyz (for example user/add_item).
The above structure will give you: /user, /user/<pk>, user/<pk>/items, /items, and /items/<pk>, but not (as I wrongly tried to achieve) user/<user_pk>/items/<items_pk>. Instead, user/<pk>/items will give you a list of user's items, but their individual properties will still be accessible only via /items/<pk>.
I just got this to work on my project, and the above code is a quick adaptation to your case. I hope it helps you, but there might still be problems there.
Update: What you wanted can be done using custom hyperlinked fields. I didn't try it (yet), so I cannot say how to use these, but there are nice examples on that link.

Categories