validate if instance exists django - django

I am building a REST Api on DRF. I have a ModelViewSet endpoint from rest_framework.viewsets.
I have a Post and a Comment model. Each comment belongs to a post. So I have defined 2 endpoints, sort of:
1) router.register(r"posts", views.PostView
2) router.register(r"(?P<pk>[^/.]+)/comments", views.CommentView
Both of them inherit from ModelViewSet so I can perform CRUD operations on them.
I have a question regarding the second endpoint. Since we create comments to posts, I am getting a post pk from posts/int:pk/comments. But the problem is when I do a GET request on this endpoint it'll return the list of all comments, but I need those to belong to a post (id in url). When I try make a POST request on this endpoint if the post does not exist it raises DoesNotExist error which is logical.
What I have done so far is:
redefined a get_queryset() to retrieve only objects belonging to a particular post. If the post does not exist it returns an empty list (though I think it should raise 404)
redefined validate() in my serializer to check if the post exists. Returns 404 if it does not
But when I check PUT, DELETE, PATCH methods on posts/int:pk/comments/int:pk it won't take the post instance into consideration either i.e. it'll allow these operation even if the post does not exist which wrong I believe.
Is there a method in ModelViewSet class that gets triggered on every request and checks if the post instance exists? So that I don't have to redefine every CRUD method just to check it.
EDIT
CommentView
class CommentView(ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
lookup_url_kwarg = 'pk2'
def get_queryset(self):
queryset = Comment.objects.filter(post=self.kwargs.get('pk'))
return queryset
def perform_create(self, serializer, **kwargs):
post = get_object_or_404(Post, pk=self.kwargs.get('pk'))
serializer.save(author=self.request.user, post=post)

Maybe take a look at drf-nested-routers package.
If it doesn't solve your problem, maybe what you want is standard router registration:
router.register(r"posts", views.PostView)
router.register(r"comments", views.CommentView)
and then use a package like django-filter to filter via query params when listing the comments: /comments/?post=<id>. When creating a comment just send the post id in the body of the request (don't forget to include the 'post' field in CommentSerializer)

Related

Django: ModelViewSet router not working for update action

I am using ModelViewSet and Modelserializer for a blog like project.
It could be my difficulty in understanding the implementation; I can't get the update action to work via calling it through router, only the list action is working with the route I have defined.
When I put the url : 127.0.0.1:8000/api/blogs/1, to return the blog with ID 1 to edit, it returns {"Detail": "Not Found."}.
This is my view:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
I have also overridden the save and update methods in the serializer class, don't know whether it was needed for ModelViewSet in ModelSerializer.
class ArticleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
article = Article.objects.create(
article_title = self.validated_data['article_title'],
article_content = self.validated_data['article_content'],
...
)
return article
def update(self, instance, validated_data):
instance.article_title = validated_data.get('article_title', instance.article_title)
instance.article_content = validated_data.get('article_content', instance.article_content)
...
instance.save()
return instance
class Meta:
model = Article
fields = ...
And the urls.py file:
router = DefaultRouter()
router.register(r'blogs', ArticleViewSet, basename='articles-list')
urlpatterns = router.urls
My question is:
1. How do I specify urls for the ModelViewSet actions (in my case the update action)?
2. Will defining only one url suffice all my needs with every ModelViewSet actions? if so how?
What am I doing wrong? I'm new to DRF.
Regarding your questions:
1) Upon registering ModelViewSet in api router, it will create all required urls for the following actions. In your case it would be following:
list (GET request to /api/blogs/)
retrieve (GET request to
/api/blogs/{pk}/)
create (POST request to /api/blogs/)
update (PUT request to /api/blogs/{pk}/) (it will validate all fields of the model)
partial update (PATCH request to /api/blogs/{pk}/) (it will run no validation - you can send only
fields you've decided to change)
delete (DELETE request to /api/blogs/{pk}/)
So, basically router does most of the job for you about registering viewset actions.
2) I don't completely get it, but if my guess is correct - answer is the same as to first question.
About what you are doing wrong - I am not sure, but did you try appending slash at the end of your request (i.e not /api/blogs/1 but /api/blogs/1/)
Found the issue. I was trying the url localhost/api/blogs/1. It was returning this: "Detail": "Not Found".
It was because there were no instance saved with the id 1. All my saved intance had different ids which i didn't notice before. After putting available ids, it returned desired result.

What is the best way to get different output for different request methods in django REST API?

So I have a model having, say, 'field1' and 'field2' fields.
And I need to have next results depending on the request method was used:
http POST http://127.0.0.1:8000/app 'someinput'
> {'field1': 'content1'}
http GET http://127.0.0.1:8000/app/1
> {'field1' : 'content1', 'field2': 'content2'}
I guess it has something to do with serializer's to_representation() method? but I can't figure out how can I check if the method that triggered to_representation() was 'POST' or 'GET'.
It is not very clera if you want to have different output for POST and GET for the same endpoint.
Because as it is written, you have 2 different endpoints for GET and POST in your example - so in that case, you just create 2 different serializers and use it accordingly.
If you have the same endpoint, you can override the following method in your view :
def get_serializer_class(self):
if self.request.method == 'GET':
return <your get serializer class>
return < your post serializer class >

How to safely access request object in Django models

What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})

django rest post return 405 error code

I am using django with rest framework and I try to test POST on existing object but I keep getting 405. my ViewSet looks like this:
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
....
and in the urls:
router = routers.SimpleRouter()
router.register(r'rest/agents', api_views.Agents, "Agent")
...
urlpatterns += router.urls
I call the post request from within APITestCase class (rest testing), my post request looks like this:
response = self.client.post(url, {'available': True, 'online':True}, format='json')
and printing url shows "/chat/rest/agents/1910_1567/", while 1910_1567 is the valid id of an existing agent (I create the agent during the setup and use its id).
I've seen other questions about rest post getting 405, but all the solutions there were url-related and in my case the url is correct. I even ran the setup outside the test and accessed the url via browser to get the object and the object indeed exist. but when I try to post to it - 405.
any ideas?
thanks!
Most probably your url is somehow matching with some different url's regex, and the request is being dispatched to some other view which disallows post request. Could you mention others urls in your urls.py? Infact you can verify this by adding pdb(debugger) in dispatch method of your view. If you made it to the pdb, then you can assume I was wrong.
If that is not the case, then you can evaluate the issue from dispatch method with debugger. Just in case you have any doubt about how to do that -
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
def dispatch(self, *args, **kwargs):
import ipdb; ipdb.set_trace()
return super(Agents, self).dispatch(*args, **kwargs)
Solution Found:-
You are passing id in url, so this request would be routed to detail view and detail view only allow GET, PUT, DELETE operations on resource since resource already exists. So to create resource, don't provide the id. Otherwise use PUT request and provide support for creation in PUT.
POST are supposed to be for creation purpose. If you want to update with a ViewSet you'll need to PUT or PATCH (partial update) instead.
Edit:
For more about this, here's some explanation on the HTTP methods used for REST API:
http://restful-api-design.readthedocs.org/en/latest/methods.html
This is also described in the DRF documentation at:
http://www.django-rest-framework.org/api-guide/routers/#simplerouter

Tastypie Adder without models

I would like to make a Tastypie based API adder. Here is how it works... the user would post to the two numbers they would like added and using Tastypie + Django I would like to include the added number on the return to the user.
I have no interest in putting it into the mySQL database.
class Adder(resource):
class Meta:
authorization = Authorization()
authentication = Authentication()
def hydrate(self,bundle):
_a = bundle.data['first_number']
_b = bundle.data['second_number']
self.create_response(request, return_dict)
return bundle
The documentation for Tastypie really seems to revolve around the models (for obvious reasons).
But I was curious if the create_response can be called from within the hydrate method and if calling the hydrate method is the right way of handling the post data.
I would probably skip the finer-grained things like hydrate, apply_sorting, build_filters, etc.
I'm assuming that without objects behind the api you're using a list-looking url like /api/v1/add_stuff/, and assuming you're accepting POST requests. If these assumptions are wrong you can adjust by changing to post_detail, get_list, etc.
def post_list(self, request, **kwargs):
_a = request.POST.get('first_number', None)
_b = request.POST.get('second_number', None)
if None in (_a, _b):
raise HttpBadRequest()
return self.create_response(request, {'result': _a + _b})
Note that I think this code would work but I haven't tested it. It's meant to provide a starting point.
This section of the Tastypie docs describes the order in which the various methods are called, and toward the bottom of the page there is a full API reference so you can see what parameters things expect and what they are supposed to return.
Edit:
The flow for this situation will look something like this:
In dispatch, the request uri is inspected. Depending on whether a
detail or a list uri was requested (/api/v1/add_stuff/<pk>/ or
/api/v1/add_stuff/), handling is delegated to dispatch_detail or
dispatch_list. This is also where authentication, authorization,
and throttling checks happen.
In dispatch_list, the request method is inspected and the call is
delegated to a method named '%s_list' % request.METHOD.lower().
To answer your comment, these are magical method names. If the
request method is POST, dispatch_list looks for a method named
post_list and an error is thrown if the appropriate handler
is not defined.