What is dispatch used for in django? - python

I have been trying to wrap my head around the dispatch method, particularly in Django. However, I cannot seem to figure out exactly what it does. I tried to gain an understanding from the Django docs but didn't find them to informative on this topic. Per my understanding it is a listener that listens to all events happening on a page but I am not sure if this is the case?
class OrderDetail(DetailView):
model = Order
def **dispatch**(self, request, *args, **kwargs):
try:
user_check_id = self.request.session.get("user_checkout_id")
user_checkout = UserCheckout.objects.get(id=user_check_id)
except UserCheckout.DoesNotExist:
user_checkout = UserCheckout.objects.get(user=request.user)
except:
user_checkout = None
obj = self.get_object()
if obj.user == user_checkout and user_checkout is not None:
return super(OrderDetail, self).dispatch(request, *args, **kwargs)
else:
raise Http404

The dispatch method takes in the request and ultimately returns the response. Normally, it returns a response by calling (ie, dispatching to) another method like get. Think of it as a middleman between requests and responses.
Normally, it simply decides what method in the class (e.g. get(),post(), etc) should be used (ie, dispatched) based on the HTTP method that was used in the request. Something like
def dispatch(self, request, *args, **kwargs):
if request.method == 'GET':
return self.get(*args, **kwargs)
elif request.method == 'POST':
return self.post(*args, **kwargs)
elif #... and so on
You can use your own dispatch method to change this behavior to call whatever methods you want that should return the HTTP response or even 'intercept' and modify the arguments that ultimately reach those methods. For example, you might use this to block/filter certain kinds of requests or even inject arguments...
def dispatch(self, request, *args, **kwargs):
"""Updates the keyword args to always have 'foo' with the value 'bar'"""
if 'foo' in kwargs:
# Block requests that attempt to provide their own foo value
return HttpResponse(status_code=400)
kwargs.update({'foo': 'bar'}) # inject the foo value
# now process dispatch as it otherwise normally would
return super().dispatch(request, *args, **kwargs)
But the key concept is that it's the entry point for requests and ultimately responsible for returning the response.

When a request url matches a url in your urls.py file, django passes that request to the view you specified. The request can only be passed to callable functions. This is why when using class-based views, you use the as_view() method. The as_view() method returns a function that can be called.
This function then creates an instance of the view class and calls it's dispatch() method. The dispatch method then looks at the request and decides whether the GET or POST method of the view class should handle the request.

Related

Django redirect from inside a view

I'm using django 1.11.8.
I need to perform some check on the requested view and inside it redirect the user to another Http page, but this code fail, as the redirect is ignored.
What am I missing?
class ThisCreateView(CreateView):
def get_form_kwargs(self):
if self.kwargs['x'] == 1:
return redirect(reverse('this_app:this_list'))
return kwargs
You can't return a redirect from the get_form_kwargs method. Its job is to return a dictionary of kwargs for the form.
You could move this check to the dispatch method.
def dispatch(self, request, *args, **kwargs):
if kwargs['x'] == 1:
# note you don't need to call reverse when you use redirect
return redirect('this_app:this_list')
return super(ThisCreateView, self).dispatch(*args, **kwargs)
Solution from #Alasdair worked perfectly for me albeit with a small change:
return super(ThisCreateView, self).dispatch(request, *args, **kwargs)

Override update method of UpdateAPIView

I need to do some actions, before calls update().
my code
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
super(CarView, self).update(request, *args, **kwargs)
But I'm getting an error
error message
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <type 'NoneType'>
How can I fix that?
Like most Django views, your update method on the ViewSet should be returning a response. Right now you aren't returning anything, which is why Django is complaining about receiving NoneType (as that is the default return value).
The issue is coming from the last line of your update method, where you are calling the parent update but aren't returning it.
super(CarView, self).update(request, *args, **kwargs)
If you returned it, the response that came from the update method that is normally defined would be passed down the chain and rendered as you would expect.
return super(CarView, self).update(request, *args, **kwargs)
This is happening because you have not returned anything in your update method. Django views expect a Response object to be returned. Just add a return in your update method.
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
return super(CarView, self).update(request, *args, **kwargs)
According to docs,
REST framework supports HTTP content negotiation by providing a
Response class which allows you to return content that can be rendered
into multiple content types, depending on the client request.
The Response class subclasses Django's SimpleTemplateResponse.
Response objects are initialised with data, which should consist of
native Python primitives. REST framework then uses standard HTTP
content negotiation to determine how it should render the final
response content.
So, to render the data into different content types, you have to return a response.

Redirecting to external page from django class based view

I'm trying to redirect users based on the referer in the request header. Basically, if the referer is say https://www.google.com, I would like to send them to a page, not on my website. Otherwise, continue processing as usual.
Here is what I have so far
class ArticleAccess(TemplateView, SomeMixin):
http_method_names = ['get']
template_name = 'template.html'
def dispatch(self, request, *args, **kwargs):
return super(ArticleAccess, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ArticleAccess, self).get_context_data(**kwargs)
item = get_object_or_404(ClientItem.objects.using(self.get_site().name), id=kwargs['article_id'])
if self.request.META.get('HTTP_REFERER') == 'https://www.google.com/':
return redirect(item.item_url)
context['id'] = item.id
context['name'] = item.name
context['html'] = item.description
context['item_url'] = item.item_url
return context
This just stays on the same page instead of redirecting. I have also tried HttpResponseRedirect, but to no avail
alecxe is correct.. you'd have to redirect from a method that is expected to return an HttpResponse.
get_context_data is not expected to return an HttpResponse and isn't ever returned by the view. It's always used to get a data dict to populate say a template. No matter what you return from this method, it will never override the response.
Therefore wherever you write this override, it needs to be in a place that is expected to return a response, such as get, post, dispatch.
The problem now is to determine how to get your object outside of the get_context_data method.
For debugging, I recommend you to start by just using a plain redirect('some_view') without conditions on your dispatch method so you can check if redirection is hit as expected and only then, go for conditions and anything else. #Yuji-tomita-tomita is just right! :) Django-pdb and ipdb are very nice tools.

Render Django view class to either string or response

I have a template that I want to be able to both serve directly and embed in arbitrary other templates in my Django application. I tried to create a view class for it that looks like this:
class TemplateView(View):
def get(self, request):
context = self._create_context(request)
return render_to_response('template.html', context)
def get_string(self, request):
context = self._create_context(request)
return render_to_string('template.html', context)
def _create_context(self, request):
context = {}
# Complex context initialization logic...
return context
I've wired get to my Django URLs. However, I haven't been able to figure out how to instantiate TemplateView so that I can call get_string from other views.
There must be a better way to go about doing this. Ideas?
Update: I've seen some folks talking about making a request internally and using response.content, which would save me from having to write the get_string method. So, perhaps a better question is: How do I make a request to TemplateView from another view?
I'd follow in django's CBV pattern: it determines via dispatch what method to return. By default based on request.method. Why not based on any other argument passed to dispatch()?
So subclass dispatch and give it a way to determine whether or not to return get_string.
def dispatch(self, request, *args, **kwargs):
if 'as_string' in kwargs:
return self.get_string(request)
return super(TemplateView, self).dispatch(request, *args, **kwargs)
response = TemplateView.as_view()(request, as_string=True)

Validating a form in a get request, how?

in my way of perfectionism, I'm here to ask more questions about the not-so-well-documented class-based views.
I spend like 5 hours learning about class-based views, lurking into the code and I got a question.
Maybe what I'm trying to do is stupid, and if so, just say that.
I will put a simple example:
class SearchFormView(FormView):
template_name = 'search/search.html'
form_class = SearchForm
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
This is a perfect valid class (it is, right?).
You have a form, and you make a GET request with a query parameter.
Works like a charm.
But lets imagine... I validate the query input to prevent some type of attack and I see that the query is malicious so I put a validation error.
With the old functions, I have a form instance (empty) and I put data in it and validation errors if needed. I always return that instance, if empty (first request) or if it filled with errors (the case of the malicious query).
The problem is with class-based views. In my get method I work with an extra instance of SearchForm so if I put validation stuff would be there and if I call get on the father it will use the instance on "form_class" that would be empty.
So, I think that there should be a way where I use the same form always, I mean: I call the request method, I pick the form_class (not create a new form), pass the data, validate and the father will return that form with the validation stuff.
Im not sure if I explained this correctly. So in short, Im creating a copy of the form in the get but I return the father get who have another copy that will be empty, so my when I display the template, there will be no errors because the form sended is empty.
Any ideas? Thanks.
Your problem is that super(SearchFormView, self).get(request, *args, **kwargs) renders its own form and own context. It's only a 3 line view function, so you should really be overriding what you need to change its behavior.
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return self.render_to_response(self.get_context_data(form=form))
Update: alternate idea if you'd like to continue using the super call
def get(self, request, *args, **kwargs):
self.form = SearchForm(self.request.GET or None)
if self.form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return getattr(self, 'form', None) or form_class(**self.get_form_kwargs())
The problem appears to be the fact that Django class based views only populate the form kwargs if the HTTP method is POST or PUT:
class FormMixin(object):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
I found this a bit peculiar also, since I have on occasion used a form in a GET request (eg. a "search" form), which needed to perform some basic validation. I just override the get_form_kwargs() method on such views, to also populate the kwargs['data'] item, even when the HTTP method is GET.

Categories