I'm trying to limit the access to objects for users. Only creaters should modify objects. For that purpose like they say in tutorial I've written
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return False
and added it to permission_classes. But still any user can modify any object.
If I add method
def has_permission(self, request, view):
return False
nobody can do anything. So all behaviour is controlled by the only has_permission method that doesn't provide any way to handle per object permissions.
So am I doing something wrong? Here's the code of request handler
class ProblemsHandler(APIView):
permission_classes = (
IsOwnerOrReadOnly,
permissions.IsAuthenticatedOrReadOnly,
)
def pre_save(self, request, problem):
problem.author = request.user
def get_object(self, request, pk, format):
try:
problem = ProblemsModel.objects.get(pk=pk)
serializer = ProblemsSerializer(problem)
return Response(serializer.data, status=HTTP_200_OK)
except ProblemsModel.DoesNotExist:
raise Http404
def get_list(self, request, format):
problems = ProblemsModel.objects.all()
serializer = ProblemsSerializer(problems, many=True)
return Response(serializer.data, status=HTTP_200_OK)
def get(self, request, pk=None, format=None):
if pk:
return self.get_object(request, pk, format)
else:
return self.get_list(request, format)
def post(self, request, format=None):
serializer = ProblemsSerializer(data=request.DATA)
if serializer.is_valid():
self.pre_save(request, serializer.object)
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
def put(self, request, pk, format=None):
try:
problem = ProblemsModel.objects.get(pk=pk)
serializer = ProblemsSerializer(problem, data=request.DATA)
if serializer.is_valid():
self.pre_save(request, serializer.object)
serializer.save()
return Response(serializer.data, status=HTTP_200_OK)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
except ProblemsModel.DoesNotExist:
raise Http404
def delete(self, request, pk, format=None):
try:
problem = ProblemsModel.objects.get(pk=pk)
problem.delete()
return Response(status=HTTP_204_NO_CONTENT)
except ProblemsModel.DoesNotExist:
raise Http404
the permission-checks for objects are done by DRF in the method APIView.check_object_permissions.
Since you don't use the GenericAPIView, you define your own get_object method and you have to call check_object_permissions yourself. Since you are mis-using get_object a bit, you have to check for GET (single), PUT and DELETE
self.check_object_permissions(self.request, obj)
Perhaps have a better look at the DRF Generic Views, since your use-case looks much like them. Normally get_object should only return an object and check the permissions.
Related
I have this code of 2 views in Django. You will notice that each REST API call has a verify_login() function call that ensures that the request contains a verified JWT token. I'm wondering if there's a better way to implement this so that I don't have to have these lines specifically in every REST endpoint
verify_response = verify_login(request)
if verify_response not None:
return verify_response
I'm trying to follow the D.R.Y. (Do Not Repeat Yourself) principle of coding. It'd be nice if there was a cleaner way to represent this. I thought about maybe creating a module extending APIView that automatically has this and then all my Views extend that, but runs into the issue of having to call super().API_REQUEST() and then having to do the same if-statement check to see if it's None or not.
class PostView(APIView):
"""
View for Post object
* requires token authentication
"""
# Create post
#swagger_auto_schema(
request_body=PostSerializer,
operation_description="Create a post object"
)
def post(self, request):
verify_response = verify_login(request)
if verify_response not None:
return verify_response
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# get all posts
#swagger_auto_schema(
operation_description="Get all posts from the DB"
)
def get(self, request):
verify_response = verify_login(request)
if verify_response not None:
return verify_response
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
You can use authentication classes alongside permission classes. If you want the authentication check to happen for all the APIs of your application, put your classes in settings.REST_FRAMEWORK. If you want it for specific APIView's, put them in permission_classes & authentication_classes class variables. Check out the doc for more details.
Example,
class JWTAuthenticataion(BaseAuthentication):
def authenticate(self, request):
... # write your JWT implementation here
# settings.py:
REST_FRAMEWORK = {
...
"DEFAULT_AUTHENTICATION_CLASSES": (
"path.to.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticated",
)
}
# or
# api.py
class YourAPIView(APIView):
permission_classes = (IsAuthenticated, )
authentication_classes = (JWTAuthentication, )
...
How to send the json data using post request in django python.
I have a code for get the data and I don't use any html file.
class employeeList(APIView):
def get(self, request):
employee1 = employees.objects.all()
serializer = employeeSerializer(employee1, many=True)
return Response(serializer.data)
def post(self,request):
pass
can you please help for post request.Now I want to create post function to send the data
You can use this basically,
def post(self, request):
serializer = employeeSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({'message':'Data davet succesfully'})
You can simply dump query set into Json format like:
def post(self, request):
data = list(employees.objects.values())
return JsonResponse(data, safe=False)
# or
return JsonResponse({'data': data})
Today I dig into django-rest-auth package a bit. And I have no idea what context={'request': self.request} for in get_response and post function.
They say context parameter is for including extra context to serializer. But below the codes, it seems not necessary to put context parameter. Is there something I have missed?
context: http://www.django-rest-framework.org/api-guide/serializers/
django-rest-auth : https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/views.py
def get_response(self):
serializer_class = self.get_response_serializer()
if getattr(settings, 'REST_USE_JWT', False):
data = {
'user': self.user,
'token': self.token
}
serializer = serializer_class(instance=data,
context={'request': self.request})
else:
serializer = serializer_class(instance=self.token,
context={'request': self.request})
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
self.request = request
self.serializer = self.get_serializer(data=self.request.data,
context={'request': request})
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
Sometimes you need request's data inside serializer method. For this case you can provide request to serializer's context. For example if you look into PasswordResetSerializer you'll see in save method use_https option which calculated based on the request passed with context argument:
def save(self):
request = self.context.get('request')
# Set some values to trigger the send_email method.
opts = {
'use_https': request.is_secure(),
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
'request': request,
}
Also you can check if user is authenticated or not and depends on it return one data or another on serializer level.
I'm using django.rest_framework. I have a get_or_create method for a particular view,
class LocationView(views.APIView):
def get_or_create(self, request):
try:
location = Location.objects.get(country=request.data.get("country"), city=request.data.get("city"))
Response(location, status=status.HTTP_200_OK)
except Location.DoesNotExist:
serializer = LocationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This is the location Model,
class Location(models.Model):
country = models.CharField(max_length=255)
city = models.CharField(max_length=255, unique=True)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
class Meta:
unique_together = ('country', 'city')
This is my url,
url(r'^location/$', LocationView.as_view(), name='location'),
When I call this endpoint in the following way,
http://127.0.0.1:8000/api/v1/bouncer/location/?country=USA&&city=Sunnyvale&&latitude=122.0363&&longitude=37.3688
This is what I get,
{
"detail": "Method \"GET\" not allowed."
}
What am I missing here.
The Method not allowed error is because, it searches for a get() method inside your API class, and it couldn't find a one.
The general format of the API class is as below
class LocationView(views.APIView):
def get(self, request):
#do something with 'GET' method
return Response("some data")
def post(self, request):
#do something with 'POST' method
return Response("some data")
If you want to invoke the get_or_create() method at some point, you can do it as any other methods,
class LocationView(views.APIView):
def get_or_create(self, request):
# do some "get or create" stuff
return "some data"
def get(self, request):
if condition:
self.get_or_create(request)
# do some stuff
return Response(" some special data related to get or create")
return Response("some data")
You need to provide separate methods for get and post. If your get method also creates an instance, then you can just call your current get_or_create inside both get and post methods.
class LocationView(views.APIView):
def get_or_create(self, request):
# your current definition here
def get(self, request):
return self.get_or_create(request)
def post(self, request):
return self.get_or_create(request)
For my situation, I was sending a request properly as my view would expect, a POST. But the issue was on http/s. I had set a permanent redirect, from http to https on my app, and so sending a POST to the http version resulted in a GET somehow when the server was redirecting.
TL;DR
HTTP instead of HTTPS
I am trying to write an RESTful API for my event planning app using Django Rest Framework but I am having some trouble when using views that do not expect the GET HTTP method. I have read through the tutorial on the DRF site. From what I understand after reading through the tutorial and the class based view documentation on the Django site is that if there is a class based view like this (taken from the DRF tutorial)
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, 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):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
The different methods in the view correspond to the different HTTP Request methods. So if I have www.foo.com/bar it would do two different things based on what request method is sent to that address. So that means that I wouldn't have to specify anything else because the function that is executed is determined based on the method the URL is sent with. Is this correct?
I have this view which I tried to model after the example on the DRF site
class EventDetail(APIView):
"""
Retrieve, update or delete a event instance.
"""
def get_object(self, pk):
try:
return Event.objects.get(pk=pk)
except Event.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
event = self.get_object(pk)
serializer = EventSerializer(event)
return Response(serializer.data)
def post(self, request, format=None):
serializer = EventSerializer(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)
# def put(self, request, pk, format=None):
# event = self.get_object(pk)
# serializer = EventSerializer(event, 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):
event = self.get_object(pk)
event.delete()
return Response(status=status.HTTP_204_NO_CONTENT
which maps to these URLs
urlpatterns = patterns('',
# Get event
url(r'^(?P<pk>\d+)/$', views.EventDetail.as_view(),
name='create_events'),
# list all events
url(r'^list/$', views.EventList.as_view(),
name='list_events'),
# url(r'^update$/(?P<pk>\d+)', #update event),
url(r'^create/$', views.EventDetail.as_view(),
name='create_events'),
# delete event
url(r'^delete$/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),
)
which I am trying to test using CURL with this command (like suggested here DELETE using CURL with encoded URL)
curl -X DELETE "http://127.0.0.1:8000/events/delete/1"
This will seem to do what it should:
[18/Oct/2014 22:41:27] "DELETE /events/delete/1 HTTP/1.1" 404 2707
But the actual record is not deleted from my database
Is there something here that I am forgetting to do to get these to get this to work properly?
You're being redundant. The HTTP method is already DELETE, so there's no /events/delete in the url. Try this:
curl -X DELETE "http://127.0.0.1:8000/events/1/"
By default, DRF's router creates detailed urls at /event/<pk> and you GET, PUT, POST and DELETE them to retrieve, update, create and delete respectively.
As mentioned by Kevin Stone, the pattern you're using isn't advisable, but if you want to use it, you'll need to fix the typo in your urls for the events/delete/ mapping.
# delete event
url(r'^delete$/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),
should be:
# delete event
url(r'^delete/(?P<pk>\d+)',
views.EventDetail.as_view(), name='delete_event'),