Can not use POST method with Django 4 Rest Framework - django

I'm having an issue with a new django 4 app not processing or not allowing POST method. I used to bypass this in Django version 3 by adding a trailing slash "/" to the end of url in Postman API Tester like http://127.0.0.1:8000/api/didipay/ instead of http://127.0.0.1:8000/api/didipay . But now in my Django 4 API I completed the url with the slash but POST method is still not processing the data. It gives me a "500 internal server error" and I don't understand where it is coming from. The GET method is rather giving an empty array which is normal because I've not inserted any data yet in the database. I'm using venv, Django 4 Rest framework, serializer, viewSet and these is my models and API urls configurations:
//Serialize.py
from rest_framework.decorators import permission_classes
from didipay_app.models import Didipay_app
from rest_framework import viewsets, permissions
from .serializers import Didipay_appSerializer
class Didipay_appViewSet(viewsets.ModelViewSet):
queryset = Didipay_app.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = Didipay_appSerializer
// Project folder's urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('didipay_app.urls')),
]
// App folder's urls.py
from rest_framework import routers, urlpatterns
from .api import Didipay_appViewSet
router = routers.DefaultRouter()
router.register('api/didipay', Didipay_appViewSet, 'didipay')
urlpatterns = router.urls
The application's web page says:
HTTP 200 OK Allow: GET, HEAD, OPTIONS Content-Type: application/json
Vary: Accept
{
"api/didipay": "http://127.0.0.1:8000/api/didipay/" }
And POST is even missing from the allowed method, and I don't understand why, since I granted all permissions in the Serialize.py file (No authentication systhem yet).
I will be very greatful for your help regarding this issue. Thank you in advance.

I got it fixed by changing fields = '__ all __' in the Model by all the table attributes in fields = ('id', 'first_name', 'last_name', 'phone', 'email', 'country', 'currency', 'status', 'payingType') because restarting the computer made console erroring out this
TypeError: The fields option must be a list or tuple or "__ all __"
which was not shown before restarting the system. I also suspect having set fields = '__ all __' instead of fields = '__ all __' (there is an issue about using __ in stackoverflow editor because it is hidden when used like in real code editor, but the overall idea is that there should not be spaces around "all" when using "__".

Related

Adapt global PAGE_SIZE setting in Django REST Framework's browsable API to a different local PAGE_SIZE setting

We are using the Django REST Framework with pagination and a browsable API.
The global parameter for the page size in settings.py is set to 10000 in order to get higher performance when accessing the API via HTTP requests which works well:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10000,
}
Now the problem is that this is directly linked to the browsable API which also lists 10000 entries per page. This of course takes very long or even breaks within certain browsers.
Is there a way to change this behaviour locally for the browsable API?
Thanks in advance!
I found a solution using a custom pagination class e. g. in utils.py:
from rest_framework.pagination import PageNumberPagination
class AdjustableResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = "page_size"
max_page_size = 10000
which can be used in views.py:
from rest_framework import viewsets
from .utils import AdjustableResultsSetPagination
class RestrictedViewSet:
"""
Parent view that restricts viewers without authentication.
"""
# ...
pagination_class = AdjustableResultsSetPagination
class DeviceViewSet(RestrictedViewSet, viewsets.ModelViewSet):
# ...
pass
and allows to set the page_size manually, e. g. via:
GET /api/addresses/?page_size=5
Thanks to Andrew for pointing me in the right direction!

Django rest_framework DefaultRouter() class vs normal url

I'm using REST in Django, And I couldn't understand what is the main difference between classic URL and instantiating DefaultRouter() for registering URL by ViewSet.
I have a model:
class Article(models.Model):
title = models.CharField()
body = models.TextField()
author = models.ForeignKey()
Serializing model like this:
from blog.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'body', 'author']
View Class:
from blog.models import Article
from rest_framework import viewsets
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
and URLS:
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Is it possible to use classic URL in URLS.py instead of instantiating the object for a ViewSet like this:
urlpatterns = [
path('api/', 'views.someAPI'),
]
I just know HTTP method in ViewSet translate methods to retrieve, list and etc...
The Question is can we use traditional(Classic) URL style in this situation, Should we ?
Thanks for your help.
Well, in a nutshell as a django developer it is notorious how it is hard to deal with normal urls in django in some cases. Every now and again we get confused with the id type of the detail page that in some case are strings or integers with its regex, and so on.
For example:
urlpatterns = [
url(r'^(?P<content_type_name>[a-zA-z-_]+)$', views.content_type, name = 'content_type'),
]
# or
urlpatterns = [
url(r'^(?P<content_type_name>comics|articles|videos)$', views.content_type, name='content_type'),
]
Not mentioning that in almost every case its needed to have two urls like:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
THE MAIN DIFFERENCE
However, using DRF routers the example above is done automatically:
# using routers -- myapp/urls.py
router.register(r"store", StoreViewSet, basename="store")
How django will understand it:
^store/$ [name='store-list']
^store\.(?P<format>[a-z0-9]+)/?$ [name='store-list']
^store/(?P<pk>[^/.]+)/$ [name='store-detail']
^store/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='store-detail']
See how much job and headache you have saved with a line of code only?
To contrast, according to DRF documentation the routers is a type of standard to make it easy to declare urls. A pattern brought from ruby-on-rails.
Here is what the documentation details:
Resource routing allows you to quickly declare all of the common
routes for a given resourceful controller. Instead of declaring
separate routes for your index... a resourceful route declares them in
a single line of code.
— Ruby on Rails Documentation
Django rest framework documentation:
Some Web frameworks such as Rails provide functionality for
automatically determining how the URLs for an application should be
mapped to the logic that deals with handling incoming requests.
REST framework adds support for automatic URL routing to Django, and
provides you with a simple, quick and consistent way of wiring your
view logic to a set of URLs.
For more details follow the django rest framework documentation.

Expected view Equation1View to be called with a URL keyword argument named "pk"

I'm trying to get data directly from request body in json. Making calculations in API will be main part of my Django REST Api.
In the future I'm going to add database from which I'll be getting some data that will be used in calculations.
I'm having a problem with error AssertionError: Expected view Equation1View to be called with a URL keyword argument named "pk". Fix your URL conf, or set the 'lookup_field' attribute on the view correctly.
I don't know where should I put the pk parameter.
Using the api below I should be able to send {"name":"xxx", value:1.23} in request body and get {"emission":1.23} in json response. Ofcourse it will be more complicated in future as getting more data from request body and connecting it with data from db but now I have to make it simple.
api urls.py:
from .views import Equation1View
from django.urls import path
urlpatterns = [
path('equation/1', Equation1View.as_view())
#path('equation/2'),
#path('equation/3'),
]
views.py
from aiohttp import request
from rest_framework import generics
from .serializers import EmissionSerializer, EquaSerializer
# Create your views here.
class Equation1View(generics.RetrieveAPIView):
queryset = ''
serializer_class = EquaSerializer(context={'request':request})
serializers.py
from rest_framework import serializers
class EquaSerializer(serializers.Serializer):
emission = serializers.SerializerMethodField('perform_equation')
def perform_equation(self):
request = self.context.get("request")
if request and hasattr(request, 'name') and hasattr(request, 'value'):
return request.value
RetrieveAPIView is generic api view to retrieve specific data from the model and that data is retrieved based on the pk that is passed through the url so change your urls.py as
from .views import Equation1View
from django.urls import path
urlpatterns = [
path('equation/<int:pk>', Equation1View.as_view())
#path('equation/2'),
#path('equation/3'),
]

Django-Rest-Framework router register

i'm having a issue when i try to register more than 2 routers using Django-REST-FRAMEWORK. Please take a look on my example:
urls.py
from rest_framework import routers
from collaborativeAPP import views
router = routers.DefaultRouter()
router.register(r'get_vocab', views.VocabViewSet)
router.register(r'get_term', views.TermViewSet)
router.register(r'get_discipline', views.DisciplineViewSet)
urlpatterns = patterns(
...
url(r'^service/', include(router.urls))
)
views.py
class VocabViewSet(viewsets.ModelViewSet):
queryset = Vocab.objects.all()
serializer_class = VocabSerializer
class TermViewSet(viewsets.ModelViewSet):
queryset = Term.objects.all()
serializer_class = TermSerializer
class DisciplineViewSet(viewsets.ModelViewSet):
queryset = Vocab.objects.filter(kwdGroup=4)
serializer_class = DisciplineSerializer
the result in my localhost is the following:
http://localhost:8000/service/
HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS
{
"get_vocab": "http://127.0.0.1:8000/service/get_discipline/",
"get_term": "http://127.0.0.1:8000/service/get_term/",
"get_discipline": "http://127.0.0.1:8000/service/get_discipline/"
}
As you can see i have registered 3 routers expecting that they will display 3 urls for each methodname(get_vocab, get_term, get_discipline). The final result is get_discipline is occuring two times and get_vocab url is missing.
Notice that for methods that uses different models it works fine, but in case of get_discipline and get_vocab they use the same model which will create this mess. Should i use a viewset for each model? If so, how can a define different methods in a viewset?
It should occur the following result:
HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS
{
"get_vocab": "http://127.0.0.1:8000/service/get_vocab/",
"get_term": "http://127.0.0.1:8000/service/get_term/",
"get_discipline": "http://127.0.0.1:8000/service/get_discipline/"
}
What am i missing? I supposed that i could register as many routers as i want. It is supposed to have one router per model? Why doesn't seem to work for viewsets that share a same model?
Try explicitly adding a base_name to each registered viewset:
router = routers.DefaultRouter()
router.register(r'vocabs', views.VocabViewSet, 'vocabs')
router.register(r'terms', views.TermViewSet, 'terms')
router.register(r'disciplines', views.DisciplineViewSet, 'disciplines')
As a side note, your should probably exclude get_ prefix in your urls since that is not RESTful. Each URL should specify a resource, not an action on the resource. Thats what HTTP verbs are used for:
GET http://127.0.0.1:8000/service/vocabs/
# or this to create resource
POST http://127.0.0.1:8000/service/vocabs/
...
Here some more information about router:
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls
Here exist two mandatory arguments for register() method:
prefix : The URL prefix to use for this set of routes.
viewset : The viewset class.
And what abut base_name?
The base to use for the URL names that are created. if you don't set the base_name, it will be automatically generated according to the model or queryset attribute on the viewset.
Here is the URL patterns generated by example above:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
URL pattern: ^accounts/$ Name: 'account-list'
URL pattern: ^accounts/{pk}/$ Name: 'account-detail'
Now if you want create custome route you should write methods on the viewset decorated with #link or #action like this:
#action(permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
The following URL generated:
URL pattern: ^users/{pk}/set_password/$ Name: 'user-set-password'
You used the DefaultRouter and this router is similar to SimpleRouter, but additionally includes a default API root view, that returns a response containing hyperlinks to all the list views. It also generates routes for optional .json style format suffixes.
See this picture of detail table
In previous answer you got the right answer .. me just have given you some more information about router
I am not sure if this is an answer or comment (or a question).
In my case I have two endpoints to a single model and #miki725 answer was not help for me. I was forced to use separated views and separated serializers.
In urls.py I have the 3rd parameter (like #miki725 suggests)
router.register(r'doc_v1', views.DocV1ViewSet, basename='doc_v1')
router.register(r'doc_v2', views.DocV2ViewSet, basename='doc_v2')
But this is not enough. I must use a separated serializers and connect them to the proper detail view via explicitly defined url field:
url = serializers.HyperlinkedIdentityField(view_name='doc_v1-detail')
This is a pain, because (sometimes) both views differ in really minor details like access permissions are. And I have to make almost identical duplicates of view and serializer.
Not good behavior but I have no other solution for now.

JWT authentication doesn't work for custom controller in Django

I am using the Django Rest Framework in my Python app, and am using JSON Web Token Authentication (DRF JWT) for the api authentication.
My problem comes when I am building a custom controller. I pointed a specific URL to a function in my calculations.py file that I created. Following are how they look.
urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from rest_framework import routers
from app.serializers import xxxViewSet, yyyViewSet
from app.calculations import getReturns
router = routers.DefaultRouter()
router.register(r"xxx", xxxViewSet)
router.register(r"yyy", yyyViewSet)
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^api/auth/token/$', 'rest_framework_jwt.views.obtain_jwt_token'),
url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-token-verify/', 'rest_framework_jwt.views.verify_jwt_token'),
url(r'^api/', include(router.urls)),
**url(r'^getReturns/', getReturns),**
)
calculations.py
from django.http import HttpResponse
from .models import xxx, yyy, zzz, aaa
def getReturns(request):
data = request.GET('data')
**running calculations here on data and giving out response**
return HttpResponse(response)
serializers.py
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework import routers, serializers, viewsets, permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from .models import xxx, yyy, zzz, aaa
class xxxSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = xxx
fields = ('id', 'name')
class xxxViewSet(viewsets.ModelViewSet):
authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]
permission_classes = [permissions.IsAuthenticated, permissions.IsAdminUser]
queryset = xxx.objects.all()
serializer_class = xxxSerializer
The above serializers.py file contains serializer classes for all my models, and also viewsets for the same. I haven't yet transferred the viewsets into views.py, so that file is empty for now.
Anyway, my calculations.pyis separate from these files, and the function defined in this file is directly being called by the '/getReturns/' URL without going through a view. How do I incorporate the functions defined in my calculations file into a viewset so that my authorization classes are called before the function gets executed?
I started doing this in comments and it was too long. Generally, you haven't really provided enough code to assist properly, but here's my crack anyway. It's not obvious which version/implementation of Django JWT you're using (there are a few), how you're authorising your views, or whether your calculations.py file is a view or something else. (If it's something else, I'd authorise in the view, and call it from there.)
Why are you unable to send a POST? Generally, Once you have the token in your front end, you can use from rest_framework.decorators import authentication_classes and #authentication_classes([JSONWebTokenAuthentication,]) wrapper on any function that needs authorisation.
That looks like this:
#authentication_classes([JSONWebTokenAuthentication,])
def function_here(arguments):
#function does stuff
How are you passing/trying to send the web token back to the application?
Presumably in CURL your initial auth to get a token looks something like:
curl -X POST -d "username=admin&password=abc123
after that you get (if you're using rest_framework_jwt) the token back:
{JWTAuthorization: YourTokenHere}.
After that, to return it to DRF protected pages (assuming they're wrapped, as above, or have a similar protection) - you haven't outlined how you're authorisation - then from the docs you do:
curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/
If you're generating the call in Angular or similar, then it's the same - you need to pass it in the headers.
Edit: I'd also note you've drastically increased the amount of code here since my original answer. Fundamentally though, you need to check the auth classes you're declaring; the easiest way is as specified above.