Django REST framework 405 Error - django-views

I have a Serializer coded this way:
class InternationalSerializer(serializers.Serializer):
""" Serializer for International
Serializes version, which is displayed on the
International page
"""
overall_version = serializers.SerializerMethodField('get_overall_version',
read_only=True)
def get_overall_version(self):
# sum up all the individual country versions
# to keep a unique value of the overall
# International version
sum_of_versions = 0
for key in constants.country_versions:
sum_of_versions+=key.value()
return sum_of_versions
~
Now, I wish to display the 'overall_version' of the InternationalSerializer class through the views.py file. Here's my code:
class International(generics.GenericAPIView):
serializer_class = InternationalSerializer()
Whenever I try to load /domain/international/, I get 405 Method not allowed error.
Here's what my urls.py contains:
urlpatterns = patterns('',
url(r'^international/$', views.International.as_view()), ...
What could be the issue here?
Thanks!

Seems like in your case you don't really need serializer, since you don't operate on any object (either it's python or django model object)
So instead of using serializer you can return response directly:
from rest_framework import generics
from rest_framework.response import Response
class International(generics.GenericAPIView):
def get(self, request, *args, **kwargs):
sum_of_versions = 0
for key in constants.country_versions:
sum_of_versions+=key.value()
return Response({'sum_of_versions': sum_of_versions})
The reason you get 405 was that you have not specified get method on your generic api view class.

Related

Got a `TypeError` when calling `Article.objects.create()`

I am working on Django React Project using the Django REST FRAMEWORK,I am trying to post some data tied to my model.
The list view and the detail view of the project works pretty fine,The only problem is when I try to make a POST request.
Whenever I Try post the data in the CreateAPIView I get an error :
Got a `TypeError` when calling `Article.objects.create()`. This may be because
you have a writable field on the serializer class that is not a valid argument to
`Article.objects.create()`. You may need to make the field read-only, or override
the ArticleSerializer.create() method to handle this correctly.
I have searched through various past problems but non of them seem to fix my problem.
Here is my serializers file:
from rest_framework import serializers
from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('id','title','content','star_count','like_count','comment_count','avatar')
Here is my views file
from rest_framework.generics import ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView
from .serializers import ArticleSerializer
from articles.models import Article
from rest_framework import viewsets
class ArticleViewSets(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
models file
content = models.TextField()
comment_count = models.IntegerField(default=0,null=True,blank=True)
like_count = models.IntegerField(default=0,null=True,blank=True)
star_count = models.IntegerField(default=0,null=True,blank=True)
avatar = models.ImageField(null=True,blank=True)
def __str__(self):
return self.title
def save(self):
if not self.slug:
self.slug = slugify(self.title)
super(Article,self).save()
Here is the error generated when I try to make a POST request based on the django rest framework createAPIVew
Got a `TypeError` when calling `Article.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Article.objects.create()`. You may need to make the field read-only, or override the ArticleSerializer.create() method to handle this correctly.

Django-REST-framework Concrete-View-Classes not returning json?

I'm working on a new app using React and DRF in which React must get the data from DRF in JSON and parse the data. But I think some of my Generic View Classes do not return JSON correctly.
For Example, a "ListCreateAPIView" class returns this:
[{"id":5,"name":"5 Storey I=1.3","Value":1399511075,"NSt":5},{"id":6,"name":"5 Storey I=0.7","Value":1344981250,"NSt":5},{"id":7,"name":"5 Storey I=1","Value":1363157800,"NSt":5}]
While a "RetrieveUpdateDestroyAPIView" class returns this:
{"id":6,"name":"5 Storey I=0.7","Value":1344981250,"NSt":5,"jRatio":"[0.2,0.4,0.4]","jEDR":"[0.02,0.1,0.5,1]","jStrDrSL":"[0.3826667,0.6046667,0.78,0.8666667]","StrDrB":0.8,"jNStrDrSL":"[0.4,0.8,2.5,5]","NStrDrB":0.75,"jNStrAccSL":"[0.25,0.5,1,2]","NStrAccB":0.5,"jPGA":"[0.141,0.374,0.550,0.822]","jRePr":"[75,475,975,2475]","NNTH":7,"jP_CL":"[ 0 , 5 , 6 , 7 ]","jMIDR":"[[[0.014488,0.021893,0.010635,0.029521,0.009106,0.013556,0.034016], [0.019524,0.022306,0.013733,0.041172,0.012122,0.019027,0.027467], \r\n[0.018057,0.019549,0.01485,0.034628,0.010172,0.022447,0.02065], \r\n[0.018057,0.015954,0.009193,0.024401,0.006838,0.022809,0.017592], \r\n[0.017192,0.012215,0.009257,0.016268,0.005854,0.019945,0.012777]], \r\n\r\n[[0.016604,0.025492], \r\n[0.026047,0.03113], \r\n[0.02794,0.02432], \r\n[0.024571,0.01748], \r\n[0.023962,0.014474]], \r\n\r\n[[0.040325], \r\n[0.044064], \r\n[0.035164], \r\n[0.024971], \r\n[0.020532]], \r\n\r\n[[1], \r\n[1], \r\n[1], \r\n[1], \r\n[1]]]","jMAcc":"[[[0.081014271,0.126595311,0.094557594,0.094250765,0.068444444,0.088719674,0.118411825], \r\n[0.06911213,0.091793068,0.097146789,0.11106422,0.056927625,0.118172273,0.103258919], \r\n[0.076614679,0.077261978,0.120961264,0.144769623,0.055780836,0.08214475,0.123833843], \r\n[0.082191641,0.08675739,0.129832824,0.13788685,0.04724159,0.071845056,0.091466871], \r\n[0.18904791,0.111108053,0.098691131,0.13933945,0.052319062,0.198410805,0.117994903]], \r\n\r\n[[0.184002039,0.156264016], \r\n[0.18011213,0.122554536], \r\n[0.24753211,0.136911315], \r\n[0.292653415,0.120941896], \r\n[0.255610601,0.127876656]], \r\n\r\n[[0.201146789], \r\n[0.173687054], \r\n[0.17719368], \r\n[0.172267074], \r\n[0.187479103]], \r\n\r\n[[1], \r\n[1], \r\n[1], \r\n[1], \r\n[1]]]"}
Notice that the data doesn't start with a bracket "[" and because of this I'm not able to parse it in React. I need to know where the problem is. Should I not use GenericViewClasses?
views.py:
class BuildingsList(ListCreateAPIView):
queryset=Building.objects.all()
serializer_class=BuildingSerializerList
class BuildingDetails(RetrieveUpdateDestroyAPIView):
queryset=Building.objects.all()
serializer_class=BuildingSerializerDetails
urls.py:
urlpatterns=[
path('', BuildingsList.as_view()),
path('<int:pk>/', BuildingDetails.as_view()),]
serializers.py:
class BuildingSerializerList(serializers.ModelSerializer):
class Meta:
model=Building
fields=['id','name','Value','NSt']
class BuildingSerializerDetails(serializers.ModelSerializer):
class Meta:
model=Building
fields='__all__'
The Detail-View means, it only returns One item/details of particular look-up. and moreover, the response behavior will purely depend on your Serializer classes (BuildingSerializerList and BuildingSerializerDetails).
The ListCreateAPIView is meant for listing all your Building instances.
In terms of react, the Detail-view returns a JSON object whereas in List-view it returns a JSON-Array
I would recommend you to use DRF's ModelViewset class for views, which is very handy
UPDATE-1
You can do simple CRUD operations on Sample model through REST-API by using following view class
from rest_framework.viewsets import ModelViewSet
class SampleViewset(ModelViewSet):
serializer_class = SampleSerializer
queryset = SampleModel.objects.all()
If you are trying to do CRUD operations by using GenericViewSet, you need to write views like this,
from rest_framework.viewsets import GenericViewSet
class SampleNew(GenericViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def list(self, request, *args, **kwargs):
# your list logic
return response
def create(self, request, *args, **kwargs):
# your list logic
return response
def destroy(self, request, *args, **kwargs):
# your list logic
return response
def retrieve(self, request, *args, **kwargs):
# your list logic
return response
Did you see the difference !!
Get ListCreateAPIView will return a list(jsonarray),get RetrieveUpdateDestroyAPIView will return a single object info(jsonobject) is right,that's ListCreateAPIView and RetrieveUpdateDestroyAPIView designed to be.You would better change the parse code in fontend in React.
In your case is GenericAPIView not suit your requirement,I think you want to CRUD the model Building by DRF,then the ModelViewSet is something you actually needed.
views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
class BuildingViewSet(ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializerList
permission_classes = (IsAuthenticated,)
def get_serializer_class(self):
if self.action in ['update', 'partial_update', 'retrieve']:
return BuildingSerializerDetails
else:
return self.serializer_class
urls.py:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'building', BuildingViewSet)
urlpatterns = [
path('', include(router.urls)),
path('admin/', admin.site.urls),
]
then
create with post http://localhost/building/
list with get http://localhost/building/ # return jsonarray
detail with get http://localhost/building/id/ # return jsonobject
update with put/patch http://localhost/building/id/
delete with delete http://localhost/building/id/
If you really want detail return a list,simple change to:
class BuildingViewSet(ModelViewSet):
....
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response([serializer.data])
but it's really meaningless.

ListView and Form on the same view, using the form to filter the results

I'm new to Django and I've been trying to make so small app after reading the tutorial but I can't figure out what's wrong with my code.
What I'm trying to do is listing all the database entries of a model called project using a ListView with a form below it (using the same view) that the user can use to filter the entries to be shown by means of submitting data in text fields.
I managed to make that work, and it looks like this:
However, once the user clicks the "Filter results" button providing some filtering pattern on the textfields (let's say, filter by name = "PROJECT3", leaving the other fields blank), instead of rendering a page with the filtered data and the form below it, which is my intention, it just returns a white page.
Can anyone please explain me what is wrong with my code?
Here are the relevant parts:
forms.py
class FilterForm(forms.Form):
pjt_name = forms.CharField(label='Name', max_length=200, widget=forms.TextInput(attrs={'size':'20'}))
pjt_status = forms.CharField(label='Status', max_length=20, widget=forms.TextInput(attrs={'size':'20'}) )
pjt_priority = forms.CharField(label='Priority', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_internal_sponsor = forms.CharField(label='Int Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_external_sponsor = forms.CharField(label='Ext Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
views.py
from App.models import Project
from django.views.generic import ListView
from django.shortcuts import render
from django.template import RequestContext
from App.forms import FilterForm
class ProjectListView(ListView):
context_object_name = 'project_list'
template_name='App/index.html'
def get_context_data(self, **kwargs):
context = super(ProjectListView, self).get_context_data(**kwargs)
if 'filter_form' not in context:
context['filter_form'] = FilterForm()
return context
def get_queryset(self):
form = FilterForm(self.request.GET)
if form.is_valid():
name = form.cleaned_data['pjt_name']
i_sp = form.cleaned_data['pjt_internal_sponsor']
e_sp = form.cleaned_data['pjt_external_sponsor']
status = form.cleaned_data['pjt_status']
pri = form.cleaned_data['pjt_priority']
return send_filtered_results(name, i_sp, e_sp, status, pri)
else:
return Project.objects.order_by('-project_creation_date')[:5]
def send_filtered_results(name, i_sp, e_sp, status, pri):
return Project.objects.filter(project_name__in=name,internal_sponsor_name__in=i_sp, external_sponsor_name__in=e_sp, project_status__in=status, project_priority__in=pri).exclude(alias__isnull=True).exclude(alias__exact='')
urls.py
from django.conf.urls import patterns, url
from App.views import ProjectListView
from django.views.generic import DetailView
from App.models import Project, Task
urlpatterns = patterns('',
url(r'^$',
ProjectListView.as_view())
The answer is in your response/status code:
After doing that I get a white page and runserver returns [23/Jan/2015 00:21:09] "POST /App/ HTTP/1.1" 405 0
You're POSTing to a view that has no POST handler. You should be getting an error saying so, but the 405 means method not allowed.
Add a post method to your CBV. Django class based views map request method to functions, so a GET is handled via CBV.get, POST via CBV.post
For demonstration purposes, add:
# essentially, mirror get behavior exactly on POST
def post(self, *args, **kwargs):
return self.get(*args, **kwargs)
And change your form handler to pull from request.POST not request.GET.
form = FilterForm(self.request.POST)
I suggest you start using print()s or log to start seeing what's happening. request.GET should be empty, as.. you're not using GET parameters. That data will be in request.POST.
Your code is messy and your Project.objects.filter(...) is far to aggressive. It just doesn't return any objects.
Don't use name__in=name but name__contains=name.

Ordering items on the root api view for DefaultRouter

I'm using the DefaultRouter provided by DRF because I need a root api view. However, the items on that view aren't in any logical order. I looked into the source and discovered that each entry is just put into a dictionary (which inherently isn't ordered).
class DefaultRouter(SimpleRouter):
"""
The default router extends the SimpleRouter, but also adds in a default
API root view, and adds format suffix patterns to the URLs.
"""
include_root_view = True
include_format_suffixes = True
root_view_name = 'api-root'
def get_api_root_view(self):
"""
Return a view to use as the API root.
"""
api_root_dict = {}
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
class APIRoot(views.APIView):
_ignore_model_permissions = True
def get(self, request, format=None):
ret = {}
for key, url_name in api_root_dict.items():
ret[key] = reverse(url_name, request=request, format=format)
return Response(ret)
return APIRoot.as_view()
I'd like to order the items on the root api view alphabetically and could easily do that by modifying the source. But I was wondering, have any of you come up with solutions to order the root api items without modifying the source code?
Along the lines of what you suggest and of the first point in Denis Cornehi's answer, here is an extension of DefaultRouter that orders the urls by their base_names:
# myapp/routers.py
from rest_framework import routers
from rest_framework import views
from rest_framework.response import Response
from rest_framework.reverse import reverse
import operator
import collections
class OrderedDefaultRouter(routers.DefaultRouter):
def get_api_root_view(self):
"""
Return a view to use as the API root but do it with ordered links.
"""
api_root_dict = {}
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
class APIRoot(views.APIView):
_ignore_model_permissions = True
def get(self, request, format=None):
ret = {}
for key, url_name in api_root_dict.items():
ret[key] = reverse(url_name, request=request, format=format)
sorted_ret = collections.OrderedDict(sorted(ret.items(), key=operator.itemgetter(0)))
return Response(sorted_ret)
return APIRoot.as_view()
I see two ways here:
as you suggested, override the router, change the APIRoot view (returning an OrderedDict should be enough). In this case I addiotinally would raise an issue with DRF to (perhaps) change it for everyone.
Extend/Override the JSONRenderer to allow sorting the keys when dumping the JSON. Then extend the BrowsableAPIRenderer to set this property. Or just sort all responses. (and, again, perhaps this is interesting for every user of DRF).
With DRF 3.11.2 [not sure from which older version the below will work] you can also do
router = DefaultRouter()
# Register routes ...
router.registry.sort(key=lambda x: x[0])

Adding more views to a Router or viewset (Django-Rest-Framework)

Essentially, I'm trying to find a good way to attach more views to a Router without creating a custom Router. What's a good way to accomplish this?
Here is something sort of equivalent to what I'm trying to accomplish. Variable names have been changed and the example method I want to introduce is extremely simplified for the sake of this question.
Router:
router = routers.SimpleRouter(trailing_slash=False)
router.register(r'myobjects', MyObjectViewSet, base_name='myobjects')
urlpatterns = router.urls
ViewSet
class MyObjectsViewSet(viewsets.ViewSet):
""" Provides API Methods to manage MyObjects. """
def list(self, request):
""" Returns a list of MyObjects. """
data = get_list_of_myobjects()
return Response(data)
def retrieve(self, request, pk):
""" Returns a single MyObject. """
data = fetch_my_object(pk)
return Response(data)
def destroy(self, request, pk):
""" Deletes a single MyObject. """
fetch_my_object_and_delete(pk)
return Response()
One example of another method type I need to include. (There are many of these):
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
The end-result is that the following URL would work correctly and be implemented 'cleanly'.
GET example.com/myobjects/123/locations
The answer given by mariodev above is correct, as long as you're only looking to make GET requests.
If you want to POST to a function you're appending to a ViewSet, you need to use the action decorator:
from rest_framework.decorators import action, link
from rest_framework.response import Response
class MyObjectsViewSet(viewsets.ViewSet):
# For GET Requests
#link()
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
# For POST Requests
#action()
def update_location(self, request, pk):
""" Updates the object identified by the pk """
location = self.get_object()
location.field = update_location_field() # your custom code
location.save()
# ...create a serializer and return with updated data...
Then you would POST to a URL formatted like:
/myobjects/123/update_location/
http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing has more information if you're interested!
You can now do this with the list_route and detail_route decorators: http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
For example:
from rest_framework.decorators import list_route
from rest_framework.response import Response
...
class MyObjectsViewSet(viewsets.ViewSet):
...
#list_route()
def locations(self, request):
queryset = get_locations()
serializer = LocationSerializer(queryset, many=True)
return Response(serializer.data)
You define method like you do now, but you need to use the same url as method name and add link decorator, so for
/myobjects/123/locations/
You add method like this
#link(permission_classes=[...])
def locations(self, request, pk=None):
...
and router will pick it automatically.
From Routing to extra methods on a ViewSet:
I think you may need to route the method by hand, i.e. The Old-Fashioned Way™.
First pull the method out as a separate view:
set_password_view = UserViewSet.as_view({'post': 'set_password'})
(or such)
Then assign your URL:
url(r'^users/username_available/$', set_password_view, name-=...)
(Or such)
There's a related question on SO.
If you want to extend a viewset with a view that is or should not directly be written inside your viewset, you can write a “wrapper” action to pass the data through.
For example, with class based views:
from somewhere import YourExternalClassView
class SomeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
#action(detail=True)
def your_action(self, request, pk):
return YourExternalClassView.as_view()(request, pk=pk)
How does it work?
On class based views, the as_view method returns a view function, to which we will pass the data we received from the action. The view will then hand over to process further.
For non-class based view, the views can be called/wrapped directly without .as_view(...)(...).