I want create view which can deactivation user's account. when i create view and send delete request i have error - > "detail": "You do not have permission to perform this action.". i have authenticated permissionn but i am login in my account. i also use APIview
this is code ->
class DeleteAccount(generics.RetrieveUpdateDestroyAPIView):
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
def delete(self, request, *args, **kwargs):
user=self.request.user
user.delete()
return Response({"result":"user delete"})
Since you seem to be using a purely view-based approach, APIView might do the trick instead of the generic class. Additionally, setting the serializer class isn't necessary as well.
from rest_framework.views import APIView
class DeleteAccount(APIView):
permission_classes = [permissions.IsAuthenticated]
def delete(self, request, *args, **kwargs):
user=self.request.user
user.delete()
return Response({"result":"user delete"})
Additionally as a general practice, it's better/safer to reserve user deletion capabilities only for admin/staff users.
I am trying to create a class based view with different permissions per function. I have already created my permissions in a file that I import :
utils.py
from rest_framework.permissions import BasePermission
from rest_framework.authentication import TokenAuthentication
from rest_framework.views import APIView
class IsOwner(BasePermission):
"""
Check if the user who made the request is owner.
Use like that : permission_classes = [IsOwner]
"""
def has_object_permission(self, request, view, obj):
# if request.method in permissions.SAFE_METHODS:
# return True
return obj.user == request.user
class IsAdmin(BasePermission):
"""
Check if the user who made the request is admin.
Use like that : permission_classes = [IsAdmin]
"""
def has_permission(self, request, view):
return request.user.is_admin
class BaseView(APIView):
"""
Check if a user is authenticated
"""
authentication_classes = [
TokenAuthentication,
]
class AdminOrOwnerView(APIView):
"""
Check if a user is admin or owner
"""
authentication_classes = ( IsOwner | IsAdmin,)
I want to create a class with different method. The GET method would allow admins to have a view of all users. The POST method would allow any user to connect.
I have already created the serializer for each of these methods but I cannot assign different permissions per method.
This is my class :
class Users(APIView):
def get(self, request):
"""Only for admin"""
try:
user = Users.objects.all()
except User.DoesNotExist():
return HttpResponse(status=404)
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
"""For everyone"""
serializer = RegistrationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.error)
How I can apply my permissions on each of the methods separately ?
Thank you in advance for your help
You just need to create a permission class like this:
class CustomPermissionClass(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET':
# logic for GET method
elif request.method == 'POST'
# logic for POST metod
# default logic
And add it to your view:
class Users(APIView):
permission_classes = [CustomPermissionClass]
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 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.
I'm working in a project which relies in a Django User model and TokenAuthentication under DjangoRestFramework
I was requested to get last login datetime for each user and I've realized that this field is not getting updated when I call the authentication REST endpoint.
Is this a known fact? Am I missing something I must do in order to get that field updated each time the token authentication is called?
Thanks
Well, at the end I inherited from the REST Framework TokenAuthentication, pointing to it in the urls file
url(r'^api-token-auth/', back_views.TokenAuthenticationView.as_view()),
and the View handles the request and manually calls the update_last_login like this:
from django.contrib.auth.models import update_last_login
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
try:
request_user, data = requests.get_parameters(request)
user = requests.get_user_by_username(data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
#F.D.F. answer is great. Here is another way of doing it.
We send user_logged_in signals that will call update_last_login:
user_logged_in.send(sender=user.__class__, request=request, user=user)
Here is a working view (based on a custom User model that uses email as USERNAME_FIELD) :
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth.signals import user_logged_in
from emailauth.serializers import AuthTokenSerializer, UserSerializer
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
user_logged_in.send(sender=user.__class__, request=request, user=user)
return Response({'token': token.key, 'user': UserSerializer(user).data})
obtain_auth_token = ObtainAuthToken.as_view()
You can find the full source code here : Api View with last_login updated
Hope this helps.
A cleaner way to do it:
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
class LoginToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
result = super().post(request, *args, **kwargs)
token = Token.objects.get(key=result.data['token'])
update_last_login(None, token.user)
return result
And then setup urls.py:
url(r'^api-token-auth/', views.LoginToken.as_view()),
My answer for Django==2.0.5, django-rest-framework-social-oauth2==1.1.0
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
from rest_framework import status
from rest_framework_social_oauth2.views import TokenView
class MyTokenView(TokenView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
token = AccessToken.objects.get(token=response.data['access_token'])
user = token.user
user_logged_in.send(sender=type(user), request=request, user=user)
return response
urls.py:
from django.urls import path
urlpatterns = [
path('token', MyTokenView.as_view(), name='token'),
]
Here is my solution using a ViewSet:
views.py:
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import viewsets
from django.contrib.auth.models import update_last_login
class LoginViewSet(viewsets.ViewSet):
"""Checks email and password and returns an auth token."""
serializer_class = AuthTokenSerializer
def create(self, request):
"""Use the ObtainAuthToken APIView to validate and create a token."""
##update last_login
try:
user = models.User.objects.get(email = request.data['username'])
update_last_login(None, user)
except:
pass
return ObtainAuthToken().post(request)
Now just add this viewset to the urls.py:
router.register('login', views.LoginViewSet, base_name="login")
This is the newest code for django 3.0.8.
:)
thx F.D.F.!
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.views import ObtainAuthToken
from django.contrib.auth import get_user_model
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
currentUserModel = get_user_model()
try:
user = currentUserModel.objects.get(username=request.data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
impl via signals
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
#receiver(post_save, sender=AccessToken)
def post_save_access_token(instance, created, raw, **kwargs):
if not created or raw:
return
user_logged_in.send(sender=instance.user.__class__, user=instance.user)
for anyone using knox to authenticate the user then need to edit api.py file and import user_logged_in like below
from django.contrib.auth.signals import user_logged_in
after that in LoginAPI class in the same api.py file add the below line after _, token = AuthToken.objects.create(user) like below
user_logged_in.send(sender=user.__class__, request=request, user=user)
If you're using Simple JWT for Django, you can do it pretty much easily by following this link. You just have to add
SIMPLE_JWT = {
...,
'UPDATE_LAST_LOGIN': True,
...,
}
Sometimes you don't want exactly the login time, since front-end stores the token and use it without logging in again.
You can save the current time in every request that user is authenticated and authorized by subclassing APIView from rest_framework
from django.contrib.auth.models import update_last_login
from rest_framework.views import APIView
class CustomAPIView(APIView):
def check_permissions(self, request):
super().check_permissions(request)
# here user is authorized (otherwise an exception would have been raised)
update_last_login(None, request.user)
class my_endpoint(CustomAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, company_id=None):
...