Django: Handing POST and GET requests in view - django

I have a django app that processes both POST and GET requests.
What is "the best" way to design the view? Should I have separate methods to handle each type of request? Or should I just use one method? Or should the methods be dependent on the functionality?
Thanks in advance.

You could use function based views or Class based views:
In the first case:
# function based views
def my_view(request):
if request.method == 'POST':
# Handle post method
else: # request.method == 'GET'
# Handle get method
In the second case:
# Class based views
class MyView(View): # Use the view that fix your needs
def get(self, request, *args, **kwargs):
# Handle get method
return HttpResponse()
def post(self, request, *args, **kwargs):
# Handle post method
return HttpResponse()

Related

How to do form handling in Django Generic CBV?

I'm trying to do the same thing below in Django Generic CBV.
Function Based View
def form_handling(request):
if request.method == "POST":
form = SimilarStore(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('...')
else:
form = SimilarStore()
...
Class Based View
class SimilarStoreCreateView(CreateView):
model = SimilarStore
form_class = SimilarStoreForm
template_name='cms/similarstore_new.html'
success_url=reverse_lazy('cms:similarstore')
def form_valid(self, form):
form = SimilarStoreForm(self.request.POST, self.request.FILES)
form.save()
return redirect(self.get_success_url())
def form_invalid(self, form):
form = SimilarStoreForm()
...
I'm confused about how I can check if request.method is POST or GET in Django Generic CBV. I know Django regular CBV supports get() and post(). However, in Generic views, there's no post(). What should I do to handle POST requests in Django Generic?
Also, my CBV codes throw an error 'SimilarStoreForm' object has no attribute 'cleaned_data'. Why is that?
CreateView has post() method it's inherited from ProcessFormView. You can find method source here. As for your question from the source code you may see that form_valid and form_invalid method will be triggered only for POST requests.
You don't need to override form_invalid method since by default it will render invalid form with form's error. And you don't need to override form_valid also, CreateView do redirect automatically after. So you can simple do:
class SimilarStoreCreateView(CreateView):
model = SimilarStore
form_class = SimilarStoreForm
template_name='cms/similarstore_new.html'
success_url=reverse_lazy('cms:similarstore')
And this will give you same logic as in your function based view.
CreateViews have a post method as shown below that you can use
def post(self, request, *args, **kwargs):
self.object = None
return super().post(request, *args, **kwargs)
Here is a good reference for all the methods and attributes of CBVs
Basically, your current setup is all you need. You see, CreateView's post method is effectively its base class'es (BaseCreateView) post method which calls its own base class'es (ProcessFormView) post method, that being
class ProcessFormView(View):
# ...
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Therefore, whenever a POST HTTP request gets routed to SimilarStoreCreateView it will finally be dispatched to the aforementioned post method already implementing the desired workflow of yours.

How to deal with unintended request methods in python?

Suppose that I have a views that should only respond to GET requests:
def myview(request, topic_id):
if request.method == "GET":
#do something
#return some url
else:
#What should I do here to have the least performance or security issues?
You can use the decorators Django provides to limit what method types are allowed:
from django.views.decorators import require_GET
#require_GET
def myview(request, topic_id):
# Guaranteed to be GET only
Django will raise a 405 Method Not Allowed for any other methods.
See the docs for more information.
As an alternative to Ben's recommendation, you can also use a class-based view, which will do this for you. For example:
from django.views.generic import View
class MyView(View):
def get(self, request, *args, **kwargs):
# Only the GET method will work. Others will return a 405.
Although it should be noted that you would not generally use the base View just by itself. The other generic class-based views, such as TemplateView, ListView, and DetailView are usually far more desirable.
You could try with HttpResponseNotAllowed:
def my_view(request, topic_id):
if request.method == 'GET':
# do domething
pass
else:
return HttpResponseNotAllowed(['GET'])

how to redirect url in django's templateview

i have a templateview,the code is here so how do
class MyTemplateView(TemplateView):
def get_context_data(self, **kwargs):
context = super(UBaseTemplateView, self).get_context_data(**kwargs)
# i want to redirect another url in here
# how to do it
return context
Well, you would so something like this:
class MyTemplateView(TemplateView):
def get(self, request, *args, **kwargs):
return HttpResponseRedirect('/<your path here>/')
You can learn more about it here, and in more detail here.
If you want to pass on post data, then all you have to do is this:
class MyTemplateView(TemplateView):
def get_context_data(self, **kwargs):
return HttpResponseRedirect(reverse('/<your url here>/', [params]))
You can also do this using the post function.
class MyTemplateView(TemplateView):
def post(self, request, *args, **kwargs):
# do what you want with post data
# you can get access via request.POST.get()
return HttpResponseRedirect('/<your url>/')
# Or use the above example's return statement, if you want to pass along parameters
A more generic way to do this is with dispatch() as described here. More background on what dispatch does is in the Django docs.
The advantage is that this is going to work whatever HTTP method (GET, PUT, POST etc) has been specified on the request, whereas the get() function's only going to get called if the method is GET.

How to write separate views for GET and POST

First of all I want both views use exact same URL because I don't want to make my URLConf more complicated. I want separate views for GET and POST to make my code cleaner. The code is something like this:
def view2 (request):
# handle POST request, possibly a ajax one
return HTTPRESPONSE(json_data, mimetype="Application/JSON")
def view1 (request):
if method == POST:
view2(request)
# What should I return here???
else:
# handle GET
return render(request, template, context)
My question is about the # What should I return here??? line. If I don't put a return there, error occurs:
not returning http response
But I already return an HTTP response in view2. How can I make this work?
Another, probably a bit cleaner way would be using class-based views
from django.views.generic import TemplateView
class View1(TemplateView):
def get(self, request, *args, **kwargs):
"""handle get request here"""
def post(self, request, *args, **kwargs):
"""handle post request here"""
def head(self, request, *args, **kwargs):
"""handle head request here. Yes, you can handle any kind of requests, not just get and post"""
Of course you can add common methods, __init__ (which is useless unless you are sure what you are doing), apply login_required (see this SO question) and pretty much everything you can do with django views (e.g. apply middleware, permissions, etc.) and python classes (e.g. inheritance, metaclasses/decorators, etc.)
Also, there's a whole bunch of generic class based view coming with Django to address common situations like list page, details page, edit page, etc.
You need to return the results of view2:
def view1 (request):
if request.method == 'POST':
return view2(request)
else:
# handle GET
return render(request, template, context)

Redirect from Generic View DetailView in Django

I'm using Django's class based DetailView generic view to look up an object for display. Under certain circumstances, rather than displaying the object, I wish to back out and issue a HTTP rediect instead. I can't see how I go about doing this. It's for when a user hits an object in my app, but without using the canonical URL. So, for example, on StackOverflow URLs take the form:
http://stackoverflow.com/<content_type>/<pk>/<seo_friendly_slug>
eg:
http://stackoverflow.com/questions/5661806/django-debug-toolbar-with-django-cms-and-django-1-3
You can actually type anything as the seo_friendly_slug part and it will redirect you to the correct canonical URL for the object looked up via the PK.
I wish to do the same in my DetailView. Retrieve the object, check that it's the canonical URL, and if not redirect to the item's get_absolute_url URL.
I can't return an HttpResponseRedirect in get_object, as it's expecting the looked up object. I can't seem to return it from get_context_data, as it's just expecting context data.
Maybe I just need to write a manual view, but I wondered if anyone knew if it was possible?
Thanks!
Ludo.
This isn't a natural fit for DetailView. To do this you need to override the get method of BaseDetailView, which looks like:
class BaseDetailView(SingleObjectMixin, View):
def get(self, request, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
So in your class you'd need to provide a new get method which did the URL check between fetching the object and setting up the context. Something like:
def get(self, request, **kwargs):
self.object = self.get_object()
if self.request.path != self.object.get_absolute_url():
return HttpResponseRedirect(self.object.get_absolute_url())
else:
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you end up overriding so much of the functionality it becomes questionable whether it's worth actually using a generic view for this, but youknow.
Developing on Rolo's answer and comments, I came up with the following generic view to serve this purpose:
from django import http
from django.views import generic
class CanonicalDetailView(generic.DetailView):
"""
A DetailView which redirects to the absolute_url, if necessary.
"""
def get_object(self, *args, **kwargs):
# Return any previously-cached object
if getattr(self, 'object', None):
return self.object
return super(CanonicalDetailView, self).get_object(*args, **kwargs)
def get(self, *args, **kwargs):
# Make sure to use the canonical URL
self.object = self.get_object()
obj_url = self.object.get_absolute_url()
if self.request.path != obj_url:
return http.HttpResponsePermanentRedirect(obj_url)
return super(CanonicalDetailView, self).get(*args, **kwargs);
This is used in the same manner as the normal DetailView, and should work for any model which implements get_absolute_url correctly.