Redirect using CBV's in Django - python

I believe this is a simple one, just can't spot the solution. I have a view that does a bit of work on the server then passes the user back to another view, typically the original calling view.
The way I'm rendering it now, the url isn't redirected, ie it's the url of the original receiving view. So in the case the user refreshes, they'll run that server code again.
class CountSomethingView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
# so some counting
view = MyDetailView.as_view()
return view(request, *args, **kwargs)

I strongly recommend not overriding get or post methods. Instead, override dispatch. So, to expand on Platinum Azure's answer:
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get_redirect_url(self, **kwargs):
url = you_can_define_the_url_however_you_want(**kwargs)
return url
def dispatch(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).dispatch(request, *args, **kwargs)

when a user does an action and I need to redirect him to the same page, first of all I use a templateView to display a simple "thanks" (for example) then provide a link to go back to the previous page with a simple {% url %}
for example :
from django.views.generic import CreateView, TemplateView
from django.http import HttpResponseRedirect
class UserServiceCreateView(CreateView):
form_class = UserServiceForm
template_name = "services/add_service.html"
def form_valid(self, form):
[...]
return HttpResponseRedirect('/service/add/thanks/')
class UserServiceAddedTemplateView(TemplateView):
template_name = "services/thanks_service.html"
def get_context_data(self, **kw):
context = super(UserServiceAddedTemplateView, self).\
get_context_data(**kw)
context['sentance'] = 'Your service has been successfully created'
return context
in the template thanks_service.html i use {% url %} to go back to the expected page
Hope this can help

Performing a redirect in a Django Class Based View is easy.
Simply do a return redirect('your url goes here').
However, I believe this isn't what you want to do.
I see you're using get().
Normally, when speaking about HTTP, a GET request is seldom followed by a redirect.
A POST request is usually followed by a redirect because when the user goes backwards you wouldn't want to submit the same data again.
So what do you want to do?
What I think you want to do is this:
def get(self, request, *args, **kwargs):
return render_to_response('your template', data)
or even better
def get(self, request, *args, **kwargs):
return render(request, self.template_name, data)

If you're creating or updating a model, consider inheriting from CreateView or UpdateView and specifying a success_url.
If you're really doing a redirect off of an HTTP GET action, you can inherit from RedirectView and override the get method (optionally also specifying permanent = False):
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).get(self, request, *args, **kwargs)
Note that it's really bad practice to have a get action with side-effects (unless it's just populating a cache or modifying non-essential data). In most cases, you should consider using a form-based or model-form-based view, such as CreateView or UpdateView as suggested above.

Related

Django session not persisting with generic views

class IndexTemplateView(TemplateView):
'''Index TemplateView.'''
template_name = 'frontend/index.html'
def post(self, request, *args, **kwargs):
'''Manages credentials received for methods calling authentication.'''
bitrix24_domain = request.GET.get('DOMAIN')
request.session['bitrix24_domain'] = bitrix24_domain
print(request.session['bitrix24_domain']) # String is stored and printed to the screen.
return redirect('index')
# Bitrix24 sends credentials via POST right after GET request.
# CSRF protection would cause error in this case.
#csrf_exempt
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class LoginTemplateView(TemplateView):
'''Login TemplateView.'''
template_name = 'frontend/login.html'
def get(self, request, *args, **kwargs):
'''Renders the login page.'''
redirect_uri = get_google_redirect_uri()
print(request.session.items()) # Returns empty session. The string was never saved.
return redirect(redirect_uri)
I've tried setting request.session.modified = True but it didn't work either. I really don't know why I can't store a string in session. I've also tried to store in self.request within the POST request, but without success, tried to store it in the dispatch() function, in the setup() function. Tried almost everything and I can't store a single piece of information in my session. I'm also using Django Rest framework.
Can anyone help me on how to use sessions with generic views?
Problem solved, session does not work with tunneling service as Ngrok.

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.

Setting test cookie in generic view in Django

I want to set a test cookie in my CreateView and be able to get a test result in form_valid function (after sending a form).
Where should I put the code responsible for setting the cookie?
self.request.session.set_test_cookie()
I tried to override get_form_kwargs and put it there, but it didn't work.
My code:
class MyView(CreateView):
def form_valid(self, form):
if not self.request.session.test_cookie_worked():
pass
else:
pass
See the docs for test_cookie_worked:
https://docs.djangoproject.com/en/1.6/topics/http/sessions/#django.contrib.sessions.backends.base.SessionBase.test_cookie_worked
"Due to the way cookies work, you’ll have to call set_test_cookie() on a previous, separate page request."
Therefore I would suggest to set_test_cookie in the get method of the view:
class MyView(CreateView):
def get(self, request, *args, **kwargs):
self.request.session.set_test_cookie()
super(MyView, self).get(request, *args, **kwargs)
def form_valid(self, form):
if not self.request.session.test_cookie_worked():
pass
else:
pass

Django DeleteView without confirmation template

I am using Django DeleteView in a template and I've created a url & view.
Is it possible to skip the process of loading the _confirm_delete template and just post the delete immediately.
DeleteView responds to POST and GET requests, GET request display confirmation template, while POST deletes instance.
You can send POST request, without confirmation with form like this:
<form method="POST" action="{% url "your_delete_url_name" %}">
{% csrf_token %}<input type="submit" value="DELETE">
</form>
If you do not want to have a link instead form button, use some javascript to make invisible form, that will be submitted on link click.
It is not good practice to use GET request for updating or deleting, but if you really insist you can shortcut get method in your class view to post, ie:
def get(self, *args, **kwargs):
return self.post(*args, **kwargs)
Or you can redefine get() method in your DeleteView:
class YourDeleteView(DeleteView):
model = YourModel
success_url = '<success_url>'
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
But be careful with that, ensure that this doesn't affect other functionality.
Yes, just change the next parameter. In your return response, make sure that the dictionary that you pass in is has something like this : { 'next': '/<your_path_here>}/' }, make sure you commit the changes before adding the next parameter. You might want to change your view's get and post functions.
Or you could only allow HTTP request method delete by routing the request directly to the delete method of your class.
from django.views.generic import DeleteView
from django.http import HttpResponseForbidden
class PostDeleteView(DeleteView):
model = Post
http_method_names = ['delete']
def dispatch(self, request, *args, **kwargs):
# safety checks go here ex: is user allowed to delete?
if request.user.username != kwargs['username']:
return HttpResponseForbidden()
else:
handler = getattr(self, 'delete')
return handler(request, *args, **kwargs)
def get_success_url(self):
username = self.kwargs.get('username')
success_url = str(reverse_lazy('post:user_home', kwargs={'username': username}))
return success_url
Let's say your URL looks like this:
path('posts/delete/<int:pk>/', PostDeleteView.as_view(), name='post_delete'),
For clarity why this works, you have to analyze the post and delete methods.
def post(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect to the
success URL.
"""
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
post just calls delete and delete gets the object and success URL, deletes the object, then redirects to the success URL you provided. pk_url_kwarg = 'pk' is why I showed the <int:pk> part in the URL.
You can override the get() method to behave exactly like the delete() method:
def get(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
See CCBV here: https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/DeleteView/
All you have to do is override the get_success_url method of your delete view. Then it will directly delete the object from the DB. Eg:
class YourView(DeleteView):
model = YourModel
def get_success_url(self):
return reverse('your_redirect_view')

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)

Categories