I want a way to automatically route GET and POST requests to subsequent methods in a centralized way.
I want to create my handler in the following way.
class MyHandler(BaseHandler):
def get(self):
#handle get requests
def post(self):
#handle post requests
This is what webapp2 does and I very much like the style, is it possible to do in Django?
I also want the view in Class-method style. What kind of BaseHandler and router should I write.
HINT: Use django generic views.
This is supported in Django as class based views. You can extend the generic class View and add methods like get(), post(), put() etc. E.g. -
from django.http import HttpResponse
from django.views.generic import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('This is GET request')
def post(self, request, *args, **kwargs):
return HttpResponse('This is POST request')
The dispatch() method from View class handles this-
dispatch(request, *args, **kwargs)
The view part of the view – the
method that accepts a request argument plus arguments, and returns a
HTTP response.
The default implementation will inspect the HTTP method and attempt to
delegate to a method that matches the HTTP method; a GET will be
delegated to get(), a POST to post(), and so on.
By default, a HEAD request will be delegated to get(). If you need to
handle HEAD requests in a different way than GET, you can override the
head() method. See Supporting other HTTP methods for an example.
The default implementation also sets request, args and kwargs as
instance variables, so any method on the view can know the full
details of the request that was made to invoke the view.
Then you can use it in urls.py -
from django.conf.urls import patterns, url
from myapp.views import MyView
urlpatterns = patterns('',
url(r'^mine/$', MyView.as_view(), name='my-view'),
)
Related
I am new to django and I am trying to understand class views.
In urls.py (main) I have:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('webapp.urls')),
]
in webapp folder I have:
urls.py (webapp):
from django.conf.urls import url
from webapp.views import Firstapp
urlpatterns = [
url(r'^whatever$', Firstapp.as_view()),
]
views.py (webapp):
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
class Firstapp(View):
def something(self):
return HttpResponse('Yes it works!')
As I have said, I am trying to use class views and I would appreciate if you could help me understand why class returns 405 error. Thank you. CMD returns 0 problems.
Because you are subclassing View and the only method you define is called something.
View expects you to define a method for each valid http verb. (GET, POST, HEAD etc). Since Firstapp has no such method, View.dispatch will return a response with http status 405 (Method not allowed).
dispatch(request, *args, **kwargs)
The view part of the view – the
method that accepts a request argument plus arguments, and returns a HTTP response.
The default implementation will inspect the HTTP method and attempt to delegate to a method that matches the HTTP method; a GET will be delegated to get(), a POST to post(), and so on.
By default, a HEAD request will be delegated to get(). If you need to handle HEAD requests in a different way than GET, you can override the head() method. See Supporting other HTTP methods for an example.
To fix this, change your something method:
def get(self, request):
return HttpResponse('Yes it works!')
I have a Serializer in my code like this
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = Model
and Viewset like this
class SampleViewSet(GenericAPIView):
serializer_class = SampleSerializer
def get(self, request, *args, **kwargs):
pass
def post(self, request, *args, **kwargs):
pass
def put(self, request, *args, **kwargs):
pass
I have url like this for this viewset
Url #1:
url(r'^sample/$', SampleViewSet.as_view())
This makes url for all methods I have in my viewset like get, post and put etc. I want to make separate url for my get method but using same serializer. This url will look like this
Url #2:
url(r'^sample/(?P<model_id>\d+)/$', SampleViewSet.as_view())
How can I do this using same Viewset and Serializer? When I write Url #2 in urls.py, Swagger shows me all three type (get, post and put) of methods for that Url.
You could use require_GET decorator from django.views.decorators.http for this, and use it in your URL config:
urlpatterns = [
url(r'^sample/$', SampleViewSet.as_view()),
url(r'^sample/(?P<model_id>\d+)/$', require_GET(SampleViewSet.as_view())),
]
for more fine tuning there is also a require_http_method decorator which receives allowed methods in its parameters, e.g.:
url(r'^sample/(?P<model_id>\d+)/$', require_http_method(['GET', 'DELETE'])(SampleViewSet.as_view()))
See https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-in-urlconf for details.
Why don't you inherit ViewSet from viewsets.ViewSet and map your urls view DefaultRouter?
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'sample', SampleViewSet)
urlpatterns = router.urls
It will handle all urls for your. /sample/:id now will be available for GET, PUT and DELETE methods.
Also if it plain CRUD for your Sample model, there is a better solution to use a viewsets.ModelViewset.
For some reason, I'm getting some weird problems with sending a POST request.
This is my URL settings:
http://host/api/user/1/edit/
http://host/api/address/search/
Where /api/ is the API-Root, user is a model, 1 is user ID, and edit and search are custom functions.
This is inside views.py:
class UserViewSet(viewsets.ModelViewSet):
...
def post(self, request, pk, *args, **kwargs):
... (processing)
#detail_route(methods=['post'])
def edit(self, request, pk, *args, **kwargs):
... (processing)
class AddressViewSet(viewsets.ModelViewSet):
...
def post(self, request, *args, **kwargs):
... (processing)
#detail_route(methods=['post'])
def search(self, request, *args, **kwargs):
... (processing)
This is inside urls.py:
router = DefaultRouter()
router.register(r'user', views.UserViewSet)
router.register(r'address', views.AddressViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^authentication/', include('rest_framework.urls', namespace='rest_framework'))
]
The strange thing I'm encountering is this:
If I'm using httpie and sending a POST edit request like this:
http POST http://host/api/user/1/edit/ name="john" address="google.com"
Then the stuff inside def post will never get executed. This is the same if I make the browser send a POST request.
However, if I send a POST search request by doing:
http POST http://host/api/address/search/ name="john"
Then in this case the stuff inside def search will never get executed, while only the stuff inside def post will get executed.
The only difference I can see is that for edit, there's an extra pk (value of 1 for this example), while there are no pk for `search.
Does anyone know why this is happening? It's really confusing for me
Defining post() in a ViewSet doesn't do anything, this is for APIView derived classes. If you want to override default object creation in a ViewSet you can either
def create(self, request, *args, **kwargs):
... do stuff ...
or even better
def perform_create(self, serializer):
... do stuff ...
serializer.save()
As for the address, you need to use list_route decorator instead of detail_route. detail_route is for operating on an individual object and list_route is for lists. So /address/search/ should be list route and /address/1/search/ would be a detail_route. Mind you that I don't think your code in post() runs in either case.
Here is a relevant piece of docs http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
When submitting a post request to rest framework the request is mapped to methods on the ViewSet, unlike Django views that simply call the post method.
When posting to user/{pk}/ your request is mapped to the UserViewSet.create method. A post request to user/{pk}/edit/ maps to your UserViewSet.edit method. In both cases, UserViewSet.post is not called.
It seems that when you post to a custom url that doesn't exist the post method is called instead (probably as a fallback). In your case address/search/ isn't a valid url, and instead you've defined an endpoint as address/{pk}/search/.
If you want to access address/search/ you need to update your search method to use a list_route decorator instead of detail_route:
class AddressViewSet(viewsets.ModelViewSet):
...
#list_route(methods=['post'])
def search(self, request, *args, **kwargs):
... (processing)
Hope this helps to clear things up for you.
I have a pretty small, basic site with a handful of views. Pretty much all of the views perform some interaction with an external API.
In each view I instantiate an object of my API class, which obviously is not very DRY. I also have a piece of middleware that adds one or two useful variables to the request object (my middleware runs last in the chain).
I was thinking of instantiating my api class in the middleware, and then passing it to each view as an argument, i.e.
def my_view(request, my_api_class_instance):
and then calling the view from the middleware, something like:
def process_view(self, request, view_func, view_args, view_kwargs):
my_api = api(some, args, here)
response = view_func(request, my_api, *view_args, **view_kwargs)
return response
It seems like a quick and easy way to tidy some code and reduce repetition. Is there anything inherently BAD about this?
If you look at the Django middleware documentation, you'll see;
If process_view returns an HttpResponse object, Django won’t bother calling any other view or exception middleware, or the appropriate view; it’ll apply response middleware to that HttpResponse, and return the result.
So returning an HttpResponse will skip the execution of other middleware classes below this one which in general should be avoided unless your middleware is the last one in settings.MIDDLEWARE_CLASSES list.
However, you still can bind your API object to HttpRequest instance passed on to middleware. It is the same approach to what AuhenticationMiddleware does in order to populate request.user.
def APIMiddleware(object):
def process_request(self, request):
request.api = API(host, port, user, password, extra={"url": request.get_full_path()})
you can just change view_kwargs in middleware
class SomeMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
view_kwargs['my_api'] = api(some, args, here)
return None
def my_view(request, my_api):
# you can use you api there
def my_view(request, args, my_api)
# you can use you api there
document it's there
middleware returns None, Django will continue processing this request, executing any other process_view() middleware.
but, this only applies to every view function can got the keyword argument 'myapi', otherwise will raise the TypeError.
so the best way isn't pass your api by func arguments, like #ozgur pass your api by request.
You can use a middleware but there are two other possibilities too, both more flexible. The first one is to use a decorator and wrap the view:
#my_api_init_decorator
def my_view(request, my_api):
...
This allows you to explicitly select views, check user authorization or permissions before you init your api...
The second solution is to use class based views and create your own view you inherit from.
Here is my code. I was just trying to implement this but could not.I am in a stage of leaning Django
class BCD(View):
def start(self):
return HttpResponse("Huray Finally I called")
urls.py
urlpatterns = patterns('',
url(r'^login1/$',BCD.as_view(),)
And even i tried
urlpatterns = patterns('',
url(r'^login1/$',BCD.start.as_view(),)
It throws error.I have read the docs of django methods such as get and post ,head can be written they will be called directly according to the request made.But what I is need to make my own methods in class based views
Thanks in Advance
You need to call methods in get(), post() or whatever the HTTP request is. There isn't a start HTTP request so Django won't call that.
You can do the following in your view:
def get(self, request, *args, **kwargs):
return self.start()
This return the result of self.start() whenever you visit your view with a HTTP GET request.