Django: ModelViewSet router not working for update action - django

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.

Related

validate if instance exists 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)

Calling Class Based View (POST) method without going through url.py

We are developing an application backend API end points using Django Rest Framework with class based view approach.
have an end point like BASE_URL/api/v1/users/posts/ , when called with post method all it does is saves (posts data, ex: {"post_id" : "1", "post_content" : "some_text" ,"posted_username": "user_name"}) into back-end database.
The above end point will be processed by application urls.py file and will redirect to corresponding view to save the data.
I want co call the corresponding end point view class post method but not through regular end point [ requests.post(url,data,headers) ], I need to invoke this class post method from another python file (within the same application), without going through urls.py.
All I want to eliminate is network call. Please don't suggest saving directly to database by opening a connection to database, I want to save data through REST API end point only (class view post method only) but not actually calling end point.
Sample code:
class PosttView( mixins.CreateModelMixin, mixins.ListModelMixin, generics.GenericAPIView):
authentication_classes = []
permission_classes = []
serializer_class = PostSerializer
queryset = Posts.objects.all()
def post(self,request,*args,**kwargs):
return self.create(request,*args,**kwargs)

How does DRF know a simple POST request should call create()?

I think this is best explained with a code example.
Angular makes a POST request like this:
$http.post('/api/accounts/', {
username: username,
password: password,
email: email
}
This gets picked up by Django in urls.py:
router = routers.SimpleRouter()
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
]
Here's AccountViewSet:
class AccountViewSet(viewsets.ModelViewSet):
serializer_class = AccountSerializer
queryset = Account.objects.all()
lookup_field = 'username'
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(
serializer.validated_data,
status=status.HTTP_201_CREATED
)
And here's the serializer:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
def create(self, validated_data):
return Account.objects.create_user(**validated_data)
At no point in this code can I see something which translates to "if Angular sends a POST request to api/accounts/ which has a username, password and e-mail address, go ahead and create an account".
So my question is this:
How does DRF know to call AccountViewSet.create(), and where exactly is AccountSerializer.create() getting called?
There seems to be a lot of magic happening here.
Thanks for your help.
Generally, right REST API for each resource in most cases consists of 2 endpoints:
List/Create endpoint
Details (Retrieve/Update/Destroy) endpoint.
POST-request is used in the first one and is used for item creation.
More detailed on the endpoints, described above. URLs for the endpoints usually are almost the same, except of ID which is present in the second endpoint. For example:
/api/items/ — List/Create endpoint, with GET-request it will return list of items, with POST-request it will create a new item and return it serialized.
/api/items/<item_id>/ — Details endpoint, which allows us to deal with some item, identified by <item_id>. GET-request returns the item, PUT request updates the whole item (all required fields should be present in payload, if some non-required field is omitted — it's value will be set to None), PATCH-request allows to update item partially, only the provided in payload fields will be updated, all other fields will stay untouched) and DELETE request, obviously, deletes the item.
Such approach applies some limitations sometimes, but if you follow it — your code will stay slim and clear, because a lot of things will be done in generic way. Both on back-end and front-end parts of your project.

Sitemap and get_absolute_url

i make my sitemap and have an interrogation about to get only my objects that have a valid url, explain :
for exemple my events can have divers url, a dedicated page, a simple link to a pdf, a redirection to other page of my site or other site or simply no url.
In my sitemap i do this for only get event that have an url :
def items(self):
events = Event.objects.all()
event_array = []
for event in events:
if event.get_absolute_url():
event_array.append(event)
return event_array
That's work, but i have look at model managers and i think it can do this for me too, so my question is : it is better to have a model manager for this or my way to do this is good?
Thanks :)
Yes, your Model Manager is here to do jobs like this. Create a method that filter all event with an url.
Read this part of the documentation for more details : Django 1.8 - Model Manager
E.g :
from django.db import models
class EventManager(models.Manager):
def get_queryset(self):
return super(EventManager, self).get_queryset().all()
def with_url():
return self.get_query_set().exclude(url__isnull=True, url__exact='')
class Event(models.Model):
objects = EventManager()

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