I'm using Django User model in my Django project. my user view is:
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from myapp.serializers.user import UserSerializer, UserListSerializer
class UserList(generics.ListCreateAPIView):
model = User
permission_classes = (permissions.IsAuthenticated, )
_ignore_model_permissions = True
serializer_class = UserListSerializer
queryset = User.objects.exclude(pk=-1)
def post(self, request, *args, **kwargs):
userName = request.DATA.get('username', None)
userPass = request.DATA.get('password', None)
user = User.objects.create_user(username=userName, password=userPass)
if not user:
return Response({'message': "error creating user"}, status=status.HTTP_200_OK)
return Response({'username': user.username}, status=status.HTTP_201_CREATED)
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
model = User
permission_classes = (permissions.IsAuthenticated, )
_ignore_model_permissions = True
serializer_class = UserSerializer
queryset = User.objects.exclude(pk=-1)
When I try to view users page logged in as a superuser, I can see the list of all the users. But when I try to access it with a non-superuser, I get an empty list.
I like every user to be able to view the user list but only its own user detail if it is non superuser. I tried using signals (such as post_migrate) but the problem is that for each user I need to give view permission to every other user every time I migrate.
Is there any easier way to do this?
I can see the list of all the users. But when I try to access it with a non-superuser, I get an empty list.
From your code you should be able to access UserList even if you are not superuser.
I like every user to be able to view the user list but only its own user detail if it is non superuser.
Try custom permission.
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
return obj == request.user
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
model = User
permission_classes = (permissions.IsOwner, )
_ignore_model_permissions = True
serializer_class = UserSerializer
queryset = User.objects.exclude(pk=-1)
Now only owner can see their details
Related
I am very new to django and currently trying to generate api calls to localhost:8000/stateapi/id where id is an id for a single "State" object in a json (like 1, 2, etc).
It is using token authentication by passing a username and password to the "get-token" endpoint and then using that token for calls to the stateapi endpoint.
I mostly followed this tutorial https://scotch.io/tutorials/build-a-rest-api-with-django-a-test-driven-approach-part-2
and keep getting a "detail": "You do not have permission to perform this action."
Here are the views where CreateView handles the "localhost:8000/stateapi" endpoint and DetailsView handles the localhost:8000/stateapi/id endpoint.
class CreateView(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
def perform_create(self, serializer):
"""Save the post data when creating a new State."""
serializer.save(owner=self.request.user)
class DetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
I can't seem to figure out why the authenticated user has permission to access information from CreateView but not DetailsView.
Here is the permissions code:
class IsOwner(BasePermission):
"""Custom permission class to allow only bucketlist owners to edit them."""
def has_object_permission(self, request, view, obj):
# Our problem is that we do not have a owner property for the object
"""Return True if permission is granted to the bucketlist owner."""
return obj.owner == request.user
Upon testing what happens when DetailsView is called, i've found that obj.owner is "None" when DetailsView is called and obj.owner is correctly equal to request.user whenever CreateView is called which would explain why the authenticated user can make get requests to the endpoint without an id while it cannot for the endpoint with an id.
Are there an suggestions as to how I could either:
a) make sure obj has the correct "owner" property (something that CreateView is doing but DetailsView is not)
b) change my permissions in some way
c) something else I cannot think of.
Thanks!
Can you share your State model and StateSerializer – Iain Shelvington Jun 18 at 3:26
State Model:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.dispatch import receiver
# Create your models here.
# 1 is /, 2 is -, 3 is (, 4 is ), 5 is .
class State(models.Model):
STATE = models.CharField(max_length=30,blank=True,null=True)
Team_Contact = models.CharField(max_length=100,blank=True,null=True)
CONTACT_INFORMATION = models.TextField(blank=True,null=True)
LEGISLATION1EXECUTIVE_ORDER = models.TextField(blank=True,null=True)
TESTING = models.TextField(blank=True,null=True)
TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4 = models.TextField(blank=True,null=True)
PRE2EMPTION = models.TextField(blank=True,null=True)
owner = models.ForeignKey('auth.User', related_name='statelists', on_delete=models.CASCADE,blank=True,null=True)
OVERSIGHT_DEPARTMENT = models.TextField(blank=True,null=True)
INFRASTRUCTURE_DEVELOPMENTS = models.TextField(blank=True,null=True)
CRASHES1SAFETY_INCIDENTS = models.TextField(blank=True,null=True)
DATA1PRIVACY_CONCERNS = models.TextField(blank=True,null=True)
PUBLIC_EDUCATION_FOR_AVS = models.TextField(blank=True,null=True)
LIABILITY1INSURANCE_REQUIREMENTS = models.TextField(blank=True,null=True)
HEALTH1EQUITY_CONCERNS = models.TextField(blank=True,null=True)
MISC5 = models.TextField(blank=True,null=True)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.STATE)
# This receiver handles token creation immediately a new user is created.
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Serializers:
from rest_framework import serializers
from .models import State
from django.contrib.auth.models import User
class StateSerializer(serializers.ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
# understand exactly what this line does
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
"""Meta class to map serializer's fields with the model fields."""
model = State
fields = ('id','STATE','Team_Contact','CONTACT_INFORMATION','LEGISLATION1EXECUTIVE_ORDER','TESTING',
'TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4','PRE2EMPTION','OVERSIGHT_DEPARTMENT','INFRASTRUCTURE_DEVELOPMENTS',
'CRASHES1SAFETY_INCIDENTS','DATA1PRIVACY_CONCERNS','PUBLIC_EDUCATION_FOR_AVS','LIABILITY1INSURANCE_REQUIREMENTS',
'HEALTH1EQUITY_CONCERNS','MISC5', 'owner')
read_only_fields = ('STATE', 'Team_Contact','CONTACT_INFORMATION')
class UserSerializer(serializers.ModelSerializer):
"""A user serializer to aid in authentication and authorization."""
states = serializers.PrimaryKeyRelatedField(
many=True, queryset=State.objects.all())
class Meta:
"""Map this serializer to the default django user model."""
model = User
fields = ('id', 'username', 'states')
All things here seem to be normal. I think the problem comes from other parts of your code? Or your checked object actually doesn't have any owner linked to it?
In my Django project, I have a statement app which just stores the statements of the registered users. I was experimenting with it to learn Django and at some point, I asked myself how to show only the statements of the current user. So, I filtered the queryset based on the current user and then I have got this error message (see title), when I wanted to see the statements.
My views.py:
from statements.models import Statement
from statements.serializers import StatementSerializer, UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
from rest_framework import permissions
from statements.permissions import IsOwnerOrReadOnly
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.views import APIView
from rest_framework.authtoken.models import Token
from rest_framework.authentication import TokenAuthentication
#api_view(['GET'])
def api_root(request, format=None):
return Response({'links':{
'users': reverse('user-list', request=request, format=format),
'statements': reverse('statement-list', request=request, format=format)
}})
class StatementList(generics.ListCreateAPIView):
#queryset = Statement.objects.all() <--- with this, everything was fine but I wanted to get only the statements of the current user
serializer_class = StatementSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
# I've overridden this method so that only the statements of the current user should be displayed (BUT this caused the error)
def get_queryset(self):
user = self.request.user
return Statement.objects.filter(owner=user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({'statements': serializer.data})
class StatementDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Statement.objects.all()
serializer_class = StatementSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
My models.py:
from django.db import models
# Create your models here.
class Statement(models.Model):
created = models.DateTimeField(auto_now_add=True)
text = models.CharField(max_length=100, blank=False)
owner = models.ForeignKey('auth.User', related_name='statements', on_delete=models.CASCADE)
class Meta:
ordering = ('created',)
My serializers.py:
class StatementSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Statement
fields = ('url','id', 'text', 'owner')
class UserSerializer(serializers.HyperlinkedModelSerializer):
statements = serializers.HyperlinkedRelatedField(many=True, view_name='statement-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'statements')
So, I also read the other stackoverflow posts regarding this type error, but they were not helpful.
The problem is in StatementList View.
If the request is unauthenticated the default value of request.user is an instance of django.contrib.auth.models.AnonymousUser.
So, you can check if the user is authenticated before doing Statement.objects.filter(owner=user) query.
# views.py
class StatementList(generics.ListCreateAPIView):
#queryset = Statement.objects.all() <--- with this, everything was fine but I wanted to get only the statements of the current user
serializer_class = StatementSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
# I've overridden this method so that only the statements of the current user should be displayed (BUT this caused the error)
def get_queryset(self):
user = self.request.user
# check if the user is authenticated
if user.is_authenticated():
return Statement.objects.filter(owner=user)
return Statement.objects.none()
# END
Or you can change the permission_classes to permissions.IsAuthenticated, it will make sure that only authenticated user can access the endpoint.
Make sure in settings.py
DEBUG = True
I'm creating a simple web app and I cannot find any way to forbid other users from changing your password. This is minimal code:
# serializers.py
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# have to use custom create method because default method calls `User.objects.create()` which doesn't take care of password hashing and other important stuff
return User.objects.create_user(**validated_data)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
read_only_fields = ('id', 'username')
# password is set to write_only because I don't want to send it to anybode (even though it's just a hash)
extra_kwargs = {'password': {'write_only': True}}
# views.py
from .serializers import UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
I could take care of this myself by using APIView.
# views.py
from .serializers import UserSerializer
from rest_framework.views import APIView
from django.contrib.auth.models import User
class UserDetails(APIView):
def put(self, request, format=None):
serialized = UserSerializer(data=request.DATA)
if not serialized.is_valid():
return # probably HTTP 400 Error code
if request.user.id != serialized.data['id']:
# this if is what I'm trying to achieve
return # probably HTTP 403 Error code
user = User.objects.update(
id=serialized.data['id'],
email=serialized.data['email']
)
if 'password' in request.DATA:
user.set_password(request.DATA['password'])
return # probably HTTP 200 Error code
Unfortunately, that would have caused that scheme generated by rest_framework.schemas.get_schema_view would be incomplete. And I use for communication CoreAPI (which cannot communicate with something that is not described in the scheme) so I cannot do that. I haven't found anything in the official documentation.
This seems to be a too basic problem that has a super easy solution that I missed. Thanks for any ideas or places where to look.
PS: I'm using django2.1 with python3.6
Edit: osobacho's solutions is clean and works like charm. Anyway I also need to allow modifications (let's say of TODO-list) only to creator of that todo list. Thought that solution for password problem would be applicable but it's not.
You can get the user in the request. That way each user will only be able to change their own password.
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
return self.request.user
For your second question take a look at django_restframework http://www.django-rest-framework.org/api-guide/permissions/ permissions here is an example:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Instance must have an attribute named `owner`.
return obj.owner == request.user
then you need to add to your view:
permission_classes = (IsOwnerOrReadOnly,)
hope it helps
As stated in DRF documentation http://www.django-rest-framework.org/api-guide/parsers/#multipartparser, in order to parse multipart/form-data, the MultiPart and form parser must be used. I have a supiscion this is a problem in the Django Rest Framework because I saw a solution on their github issue saying it work using APIView.
from django.contrib.auth.models import User
from rest_framework import viewsets
from api.serializers import UserSerializer,
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
parser_classes = (MultiPartParser, FormParser)
serializer_class = UserSerializer
Picture of me sending request to Postman with result:
Edit: Adding UserSerializer class
class UserSerializer(serializers.HyperlinkedModelSerializer):
snapcapsules = SnapCapsuleSerializer(
many=True,
read_only=True,
allow_null=True,
)
class Meta:
model = User
fields = ('snapcapsules', 'url', 'username', 'email', 'password', )
write_only_fields = ('password',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
capsules_data = validated_data.pop('snapcapsules')
for capsule in capsules_data:
SnapCapsule.objects.create(user=instance, **capsule)
return instance
This is might not the issue with the ContentType. The error response says that,the UserSerializer excpect a payloadordata which include username and password field
So, Please try to add those fields to the request body and try again
UPDATE
The problem with the Orginal Post was, he added extra headers in POSTMAN (see the pic below) .
Postman will add appropriate Headers aromatically for you. So, you don't have to explicitly mention it
I removed one line and then it worked!
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
Also, the serializer can be rewrite as follows:
class UserSerializer(serializers.HyperlinkedModelSerializer):
...
def create(self, validated_data):
return User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
...
I want to get the user's detail information:
class UserDetailAPIView(RetrieveAPIView):
"""
User detail information
"""
queryset = User.objects.filter(is_valid=True).exclude(status=4)
serializer_class = UserDetailSerializer
lookup_field = "username"
I want to limit other users to access this APIView, I want only admin user and the user it self to access that.
How to limit this?
you should define your own permission class.something like this:
from rest_framework import permissions
class OwnerProfilePermission(permissions.BasePermission):
"""object lvl permissions for owner """
def has_object_permission(self, request, view, obj):
return obj.user == request.user
and in your views include permission_classes .see DRF documention.
http://www.tomchristie.com/rest-framework-2-docs/api-guide/permissions
and the class base views you choose is important.
http://www.tomchristie.com/rest-framework-2-docs/api-guide/generic-views