I am using the basic UserViewSet derived from ModelViewSet.
Retrieving a user with a primary-key via api/users/<pk> works fine.
But I also want to be able to retrieve a User by Username.
I have added a new detail route but I always get 404 on my server when I try to get the user with the url /api/users/retrieve_by_username/altoyr.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#detail_route(methods=['get'])
def retrieve_by_username(self, request, username=None):
try:
user = User.objects.get(userName=username)
return Response(user)
except User.DoesNotExist:
return Response("No user with username found!", status=status.HTTP_400_BAD_REQUEST)
The Urls are registered via a router:
router = DefaultRouter()
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
I think I am missing an important part of building rest urls.
You can do this by adding a list route like:
#list_route(methods=['get'], url_path='retrieve_by_username/(?P<username>\w+)')
def getByUsername(self, request, username ):
user = get_object_or_404(User, username=username)
return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
and the url will be like:
/api/users/retrieve_by_username/altoyr
You can override the retrieve() ViewSet action. You'll find more detail here: https://www.django-rest-framework.org/api-guide/viewsets/
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def retrieve(self, request, pk=None):
queryset = User.objects.filter(username=pk)
contact = get_object_or_404(queryset, pk=1)
serializer = ContactSerializer(contact)
return Response(serializer.data)
The answer from Anush Devendra is right but need a little update due to deprecations on v3.9.
action decorator replaces list_route and detail_route
Both list_route and detail_route are now deprecated in favour of the single action decorator. They will be removed entirely in 3.10.
The action decorator takes a boolean detail argument.
Replace detail_route uses with #action(detail=True).
Replace list_route uses with #action(detail=False).
...
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from rest_framework import status
class UserViewSet(viewsets.ModelViewSet):
...
#action(methods=['get'], detail=False,
url_path='username/(?P<username>\w+)')
def getByUsername(self, request, username):
user = get_object_or_404(User, username=username)
data = UserSerializer(user, context={'request': request}).data
return Response(data, status=status.HTTP_200_OK)
I add context={'request': request} because I have url as HyperlinkedIdentityField in my serializer. If you don't have it, you probably don't need it.
Related
I need to prepare an endpoint in DRF which will allow to change a Users password. So I read this post:
How to update user password in Django Rest Framework?
But, to be honest even after read that article and implemented code from that article in my App It still doesn't work. Im a newbie in Django and Python and I dont understant what I do wrong. Can you help me in understanding what I do wrong ?
Here below is the code I implemented:
serializers.py
from rest_framework import serializers
class ChangePasswordSerializer(serializers.Serializer):
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
api.py
# Password Change API
class ChangePasswordAPI(generics.UpdateAPIView):
"""
An endpoint for changing password.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
return obj
def update(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
return Response("Success.", status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
from django.urls import path, include
from .api import ChangePasswordAPI
from django.conf.urls import url
urlpatterns = [
url(r'^auth/password-change/(?P<pk>[0-9]+)$', ChangePasswordAPI.as_view()),
]
So now I send the PUT on http://localhost:8000/auth/change-password/
with following body:
{
"old_password": "password1",
"new_password": "password2"
}
and I get this message:
<h1>Not Found</h1>
<p>The requested resource was not found on this server.</p>
Your view is tied to the URL pattern auth/password-change/(?P<pk>[0-9]+) yet you are requesting auth/change-password. The request should match the URL pattern.
I know this question maybe a duplicate, but I have tried many solutions and could not understand any. I have followed this tutorial exactly and yet I get this error on the 'userlist' page. Everything else works just fine. Can somebody point out what the error is ?
class UserList(APIView):
"""
Create a new user. It's called 'UserList' because normally we'd have a get
method here too, for retrieving a list of all User objects.
"""
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head']
def post (self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
EDIT:
urls.py
from django.urls import include, path
from classroom.views.classroom import current_user, UserList
from .views import classroom, suppliers, teachers
urlpatterns = [path('', classroom.home, name='home'),
path('current_user/', current_user),
path('users/', UserList.as_view()),
Edit:
Still getting this error,
You need to add GET endpoint url to your urls.py in order to use GET requests. GET url is missing in your urls.py, simply edit your urls.py like:
# urls.py
from django.urls import include, path
from classroom.views.classroom import current_user, UserList
from .views import classroom, suppliers, teachers
urlpatterns = [
path('', classroom.home, name='home'),
path('current_user/', current_user),
path('users/', UserList.as_view()),
path('users/<int:pk>/', UserList.as_view()),
]
And you need to implement get method in yourUserList view such as:
# views.py
class UserList(APIView):
"""
Create a new user. It's called 'UserList' because normally we'd have a get
method here too, for retrieving a list of all User objects.
"""
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head']
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializerWithToken(users, many=True)
return Response(serializer.data)
def post(self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Basically the problem is that, there is not functionality defined for GET requests in your view. So either you can add it, like this:
class UserList(APIView):
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head', 'post']
def get(self, request, *args, **kwargs):
serializer = UserSerializerWithToken(User.objects.all(), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post (self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Or you can subclass UserList View from ListAPIView.
FYI, permission_classes won't work with APIView. You need to use GenericAPIView or any other generic views to have those functionalities.
I had the same issue but I noticed that in my views.py, I had
renderer_class = api_settings.DEFAULT_RENDERER_CLASSES
I corrected it to :
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
and it worked for me.
maybe you are calling 'post' from where ever you are calling, example as 'postman'
but your function is
def get:
so call as 'get' or change your function to
def post:
I want to have an API where a user can update his own listings. Currently any authenticated user can update any listing which I found using Postman. I want to validate the user so that the API returns an error as a response if the user is not trying to update his own listing. Here is my code:
# serializers.py
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = Listing
fields = '__all__'
# api.py
class ListingViewSet(ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
serializer_class = ListingSerializer
def get_queryset(self):
return Listing.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# urls.py
router = routers.DefaultRouter()
router.register('api/listings', ListingViewSet, 'listings')
urlpatterns = router.urls
You just need to overwrite the perform_update function:
def perform_update(self, serializer):
obj = self.get_object()
if self.request.user != obj.created_by: # Or how ever you validate
raise PermissionDenied('User is not allowed to modify listing')
serializer.save()
You will need:
from django.core.exceptions import PermissionDenied
You can limit the object for all method change the main queryset. In this case if an inapropiate user try to access an invalid object the api return 404.
def get_queryset(self):
return Listing.objects.filter(owner=self.request.user)
So I have an model serializer which consists of
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'description')
This is my ViewSet
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
This is my URLs.py file:
from django.conf.urls import include, url
from rest_framework import routers
import views
router = DefaultRouter()
router.register('user', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^login/', include('rest_framework.urls', namespace='rest_framework'))
]
Using the serializer, I can make it print out the objects inside my database. If I have the object PK/ID, I want to be able to update the field id or name of the object. Is there a way I can do that with a patch/post request using the serializer? I'm new to this so I'd love it if someone can help me out with this.
I'm thinking of just doing a POST request, then have it do this:
user = User.objects.get(id=id)
user.name = "XXXXX"
user.save()
But I want to do this using the serializer, using a PATCH request.
the below code will help to you,
**filename : views.py**
from user.models import User
from users.serializers import UserSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class UserList(APIView):
"""
List all users, or create a new user.
"""
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UserDetail(APIView):
"""
Retrieve, update or delete a user instance.
"""
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
**filename : urls.py**
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from users import views
urlpatterns = [
url(r'^users/$', views.UserList.as_view()),
url(r'^user/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Reference : http://www.django-rest-framework.org/tutorial/3-class-based-views/
Django rest framework comes with some pre-defined concrete generic views such as UpdateAPIView, RetrieveUpdateAPIView.
First you need to create a view for user which uses one of the views which can update. The update views provides handlers for patch method on the view.
RetrieveUpdateAPIView
Used for read or update endpoints to represent a single model instance.
Provides get, put and patch method handlers.
Now, use this to create a view:
class UserDetail(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
To access this view you need to have a url which uses user's primary key to access the user:
url(r'users/(?P<pk>\d+)/$', UserDetail.as_view(), name='api-user-detail'),
Then using PATCH call you can update the user's name.
Since you're using a ModelViewset, this capability should be built in. If you use the browsable API to navigate to /user/<pk>/, you'll see the operations you can perform on that object. By default, a ModelViewset provides list(), retrieve(), create(), update(), and destroy() capability.
http://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
You can also override any or all of the provided methods, however an update of a single object is built in to DRF ModelViewsets. Use curl to try a PATCH to /user/<pk>/ with the information you'd like to update.
I have the following code:
The problem is when I try to access user-login/ I get an error:
"CSRF Failed: CSRF cookie not set."
What can I do?
I am using the django rest framework.
urls.py:
url(r'^user-login/$',
csrf_exempt(LoginView.as_view()),
name='user-login'),
views.py:
class LoginView(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
startups = Startup.objects.all()
serializer = StartupSerializer(startups, many=True)
return Response(serializer.data)
def post(self, request, format=None):
profile = request.POST
if ('user_name' not in profile or 'email_address' not in profile or 'oauth_secret' not in profile):
return Response(
{'error': 'No data'},
status=status.HTTP_400_BAD_REQUEST)
username = 'l' + profile['user_name']
email_address = profile['email_address']
oauth_secret = profile['oauth_secret']
password = oauth_secret
I assume you use the django rest framework SessionBackend. This backend does a implicit CSRF check
You can avoid this by:
from rest_framework.authentication import SessionAuthentication
class UnsafeSessionAuthentication(SessionAuthentication):
def authenticate(self, request):
http_request = request._request
user = getattr(http_request, 'user', None)
if not user or not user.is_active:
return None
return (user, None)
And set this as authentication_classes in your View
class UnsafeLogin(APIView):
permission_classes = (AllowAny,) #maybe not needed in your case
authentication_classes = (UnsafeSessionAuthentication,)
def post(self, request, *args, **kwargs):
username = request.DATA.get("u");
password = request.DATA.get("p");
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return redirect("/")
Actually, better way to disable csrf check inside SessionAuthentication is:
from rest_framework.authentication import SessionAuthentication as OriginalSessionAuthentication
class SessionAuthentication(OriginalSessionAuthentication):
def enforce_csrf(self, request):
return
The easiest way to solve this problem:
For that there are two ways of authentication in drf see drf auth
BasicAuthentication
SessionAuthentication (default)
SessionAuthentication has a forced csrf check, but BasicAuthentication doesn't.
So my way is using BasicAuthentication in my view instead of SessionAuthentication.
from rest_framework.authentication import BasicAuthentication
class UserLogin(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = UserSerializer
authentication_classes = (BasicAuthentication,)
def post(self, request, *args, **kwargs):
return Response({})
Probably better to just make the enforce_csrf check do nothing:
from rest_framework.authentication import SessionAuthentication
class UnsafeSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, *args, **kwargs):
'''
Bypass the CSRF checks altogether
'''
pass
Otherwise you'll possibly end up with issues in the future if the upstream authenticate() method changes. Also, it's MUCH simpler to just make the check not do anything :-)