I've seen this Q&A:
Django DeleteView without confirmation template
and this one:
Django CSRF token won't show
but that doesn't address the built-in intended functionality of the DeleteViewm CBV when issued a GET. From the docs (emphasis mine):
https://docs.djangoproject.com/en/2.1/ref/class-based-views/generic-editing/#django.views.generic.edit.DeleteView
"If this view is fetched via GET, it will display a confirmation page that should contain a form that POSTs to the same URL."
The problem is that as I understand it, the rendered template in response to a GET will not included the RequestContext necessary to include the {% csrf_token %} in the mentioned POST form. I worked around it for the time being by overriding the get() method so that it uses render() to return the page, since it automatically includes the appropriate context.
How do I maximally leverage the DeleteView? What am I doing wrong that I need to implement the following code in my view?
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return render(self.request,'mainapp/template_confirm_delete.html')
Related
I'm still a noob at mixins in general so I'm just trying to understand what happens in this code that uses Ajax to submit forms. I've been scouring the docs for a whole day to try to figure out what happens. The code seems to work but I just don't entirely understand why. I still have a lot of question marks on the whole process so if anyone can correct my thinking that would be amazing
The AjaxableResponseMixin extends the form_invalid() and form_valid() methods of the FormView to support Ajax requests
Why is the Ajax class referring to itself with super()?
How does the class know to extend FormView's methods?
If the request is not Ajax it returns response
What does the response object do/have? Does it show a template? Does it have context for the template?
CreatePostView is a child class of the two parent classes that are passed in (AjaxableResponseMixin, FormView)
Do the order of the classes in the params have an impact in general and more specifically when calling super()?
When CreatePostView calls form_valid() and form_invalid() is it overwriting the Ajax class? If it isn't, what class methods is it changing?
If the form is valid then a post gets created and super().form_valid() gets called which then redirects to the success url because thats what the FormView.form_valid() does
Again, why is the super() referring to the FormView?
Am I better off just doing a HttpResponseRedirect instead of using super().form_valid()
If the form is invalid it redirects to the url with the "create_post" name
How do I redirect to the create post page and keep the data in the form the user tried to submit?
views.py
class AjaxableResponseMixin(object):
"""
Mixin to add AJAX support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
def form_invalid(self, form):
response = super(AjaxableResponseMixin, self).form_invalid(form)
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return response
def form_valid(self, form):
response = super(AjaxableResponseMixin, self).form_valid(form)
if self.request.is_ajax():
data = {
'pk': self.object.pk,
}
return JsonResponse(data)
else:
return response
class CreatePostView(AjaxableResponseMixin, FormView):
form_class = CreatePostForm
template_name = 'forum/create_post.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
user = self.request.user
form.create_post(user_obj=user)
messages.success(self.request, 'Your post was published')
return super().form_valid(form)
def form_invalid(self, form):
messages.error(self.request, 'Your post could not be published. Please try again')
return HttpResponseRedirect(reverse('create_post'))
Thank you so much to anybody who answers.
Most of your question relates to python multiple inheritance and MRO (method resolution order). Look it up, there are plenty of other resources that explain it in detail. But to help with your specific case, you have defined the inheritance in this order:
CreatePostView --> AjaxableResponseMixin ...> FormView
This is the order in which subclassed methods will be called. I've made the arrows different because the first is a subclass - parent class relationship, the second one isn't (CreatePostView is a subclass of both FormView and AjaxableResponseMixin)
I'll explain what happens with form_valid: So if you call the form_valid() method on a CreatePostView, its form_valid() method is called. This runs all the code of that method.
The last line of that method is return super().form_valid(form) telling python to call the parent's class method. With MRO, that is the AjaxableResponseMixin.form_valid method. But the first line is response = super().form_valid(...), which is FormView.form_valid().
So here, because super() is at the beginning, you effectively tell python to first go to the next in the MRO chain.
AjaxableResponseMixin does nothing with the super() response if the request is an ajax request (it returns JSON with the object pk as data), but if it's not an ajax request, it just returns the FormView response. In this case it's a redirect to the success_url, because that's what FormView does in case of success. You can see that here.
Now you can do the same exercise with form_invalid(). In fact, it should just return super().form_invalid(form). It should never redirect, because you want it to render the same form on the same page (in the normal case) or just report the form errors in the ajax case.
Notes:
If your method does not call super(), which is entirely legal, the parent's class method will never be called.
The order of the classes and the order of the calls is important. With Django CBVs, you will most often first call super() to take care of the basics (the default behaviour) and then add your own stuff. In your form_valid, you do it the other way round because you first want to save the object, otherwise the JSON cannot contain the object's pk. And actually, if used incorrectly, this mixin will crash on that line.
A mixin like AjaxableResponseMixin doesn't contain any python code forcing it to be "mixed" with a FormView, but it has to, because it calls super().form_valid(). In fact many IDEs warn you with that call. There's no way to tell python that AjaxableResponseMixin MUST be mixed with a FormView, but it should be part of the documentation so other developers know how to use it. The same holds for the warning that the object must be saved before calling AjaxableResponseMixin.form_valid.
Im trying to check either a user tried to enter a url by himself or he follows the urls and put the values needed in the form i build for him..
In some Ungeneric class, I can check that thing -
if request.method == 'GET':
But in DeleteView i can't do that thing so i don't know how to prevent from the user from doing bad things by input url by himself.
How can i use a function that does the same in generic View and checks if the user enter a url by himself or fill in the form?
It should be a POST, there isn't any need to check it yourself.
From the docs
The given object will only be deleted if the request method is POST. If this view is fetched via GET, it will display a confirmation page that should contain a form that POSTs to the same URL.
By default DeleteView does deletion only on POST request. So your user will not be able to delete items just making GET request.
But for your information all class based views(CBV) call dispatch method which then calls ether post or get depending on request.method.
You can add some logic directly in dispatch method or modify get and do your checks there
Example
class MyDeleteView(DeleteView):
def post(self, request, *args, **kwargs):
...
def get(self, request, *args, **kwargs):
# here you can make redirect to any url
...
I'm building a job application form. A logged-in user is permitted to apply to the job only once (there's only one job). At the moment, a user is able to directly access the job application (FormView), by typing in its specific URL, and an error message is thrown AFTER the user submits the form. To achieve this, I'm doing a couple of things:
(1) Wrapping the job application view in login_required()
(2) Using the form_valid() method to check whether or not a user has already submitted an application to this specific job via:
def form_valid(self, form):
form = form.save(commit=False)
form.user = self.request.user
if Application.objects.filter(user=user_id):
messages.error(self.request, 'Sorry, our records show that you have already applied to this job.')
return redirect('/')
But I'd rather not permit them to reach the page at all. Instead, I want to check whether or not a user has already applied (during the request), and redirect them away from the form, if they have already applied. I have limited access to logged-in users that pass a test in the past, using something like:
def job_application_view(request):
active_user = request.user
if Application.objects.filter(user=active_user):
return HttpResponse("Some response.")
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can still use decorators on class-based views, but they are slightly more difficult to apply than function-based views.
class ApplicationView(FormView):
# ...
#method_decorator(user_passes_test(job_application_view))
def dispatch(self, *args, **kwargs):
return super(ApplicationView, self).dispatch(*args, **kwargs)
Answering specific parts of your post...
I have limited access to logged-in users that pass a test in the past
With class-based views, you need to decorate the url or decorate the dispatch method with any decorators you are interested in applying.
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can access the request with self.request
I have a project with a Post model, that is basic posts. I want to create a link on each post page to be able to delete that post (with appropriate security).
There are a few questions on this on stack overflow, but I can't seem to find a complete, workable answer (I am using Django 1.7) that doesn't throw up errors when I implement it.
I have been able to implement a delete function which works ok, but need to add a POST form with CSRF token for validation, and also check that the user deleting it is the one that created it. I can't seem figure out how to add these two in.
So far, in my views.py:
def delete(request, id):
post = Post.objects.filter(pk=id).delete()
return HttpResponseRedirect(reverse('posts.views.all_posts'))
In urls.py:
url(r'^delete/(?P<id>\d+)/$','posts.views.delete'),
In html:
Delete
This all works, but there is no security - so appreciate guidance on how to add a form and checking.
Also, I've seen an answer that uses DeleteView, but couldn't get that one to work either.
Indeed, using a GET method to delete your objects makes you vulnerable to CSRF attacks.
DeleteView only deletes on POST, and shows a confirmation page on GET.
Your code should look something like this in views.py:
from django.views.generic import DeleteView
class PostDelete(DeleteView):
model = Post
success_url = reverse_lazy('posts.views.all_posts')
In urls.py:
url(r'^delete/(?P<pk>\d+)/$', PostDelete.as_view(),
name='entry_delete'),
Your form (without using a confirmation template. There is an example of confirmation template in the docs):
<form action="{% url 'entry_delete' object.pk %}" method="post">
{% csrf_token %}
<input type="submit" value="Delete" />
</form>
If you are not using a confirmation template, make sure to point the form's action attribute to the DeleteView (this is why).
To ensure the user deleting the post is the user that owns it, I like to use mixins. Assuming your Post model has a created_by foreign key pointing to User, you could write a mixin like:
from django.core.exceptions import PermissionDenied
class PermissionMixin(object):
def get_object(self, *args, **kwargs):
obj = super(PermissionMixin, self).get_object(*args, **kwargs)
if not obj.created_by == self.request.user:
raise PermissionDenied()
else:
return obj
Finally, your DeleteView should inherit from this mixin:
class PostDelete(PermissionMixin, DeleteView):
model = Post
success_url = reverse_lazy('posts.views.all_posts')
I am switching to the class-based views. I also use JavaScript to confirm any deletion on the client side. Django DeleteView requires a delete confirmation template which I don't care about.
Is there any simple way of disabling the confirmation on any kind of deletes in Django?
class EntryDeleteView(DeleteView):
model = Entry
success_url = reverse_lazy('entry_list') # go back to the list on successful del
template_name = 'profiles/entry_list.html' # go back to the list on successful del
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(EntryDeleteView, self).dispatch(*args, **kwargs)
You should make a POST query from clientside (with AJAX or POSTing a form). That's because if you'll allow to delete something by GET, your service will be vulnerable to CSRF. Someone will send your admin a in email or somehow else, and you'll be in trouble.
The DeleteView renders the confirmation page on GET and deletes the object if you use a POST or DELETE. If your JS does a POST to the url after confirmation it should work like you want.