What's the best way to add a "cancel" button to a generic class-based view in Django?
In the example below, I would like the cancel button to take you to success_url without deleting the object. I have tried adding a button <input type="submit" name="cancel" value="Cancel" /> to the template. I can detect if this button was pressed by overriding the post method of the AuthorDelete class, but I can't work out how to redirect from there.
Example myapp/views.py:
from django.views.generic.edit import DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
def post(self, request, *args, **kwargs):
if request.POST["cancel"]:
return ### return what? Can I redirect from here?
else:
return super(AuthorDelete, self).post(request, *args, **kwargs)
Example myapp/author_confirm_delete.html:
<form action="" method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" value="Confirm" />
<input type="submit" name="cancel" value="Cancel" />
</form>
(Examples adapted from the docs)
Your approach of overriding the post method and checking to see if the cancel button was pressed is ok. You can redirect by returning an HttpResponseRedirect instance.
from django.http import HttpResponseRedirect
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
url = self.get_success_url()
return HttpResponseRedirect(url)
else:
return super(AuthorDelete, self).post(request, *args, **kwargs)
I've used get_success_url() to be generic, its default implementation is to return self.success_url.
Why don't you simply put a "Cancel" link to the success_url instead of a button? You can always style it with CSS to make it look like a button.
This has the advantage of not using the POST form for simple redirection, which can confuse search engines and breaks the Web model. Also, you don't need to modify the Python code.
If using CBV's you can access the view directly from the template
Cancel
Note: you should access it through the getter in case it has been subclassed.
This is noted in the ContextMixin docs
The template context of all class-based generic views include a view
variable that points to the View instance.
Having an element of type button, will not send a POST request. Therefore, you can use this to do a http redirection like this:
<button type="button" onclick="location.href='{{ BASE_URL }}replace-with-url-to-redirect-to/'">Cancel</button>
Do you even need the get_success_url, why not just use:
Cancel
and go to any other url you want?
Related
I have a problem converting from a function-based view to a class-based view, function
VIEWS.PY
# login_required
def favourite_add(request, id):
post = get_object_or_404(Perfumes, id=id)
if post.favourites.filter(id=request.user.id).exists():
post.favourites.remove(request.user)
else:
post.favourites.add(request.user)
return HttpResponseRedirect(request.META['HTTP_REFERER'])
URLS.PY
urlpatterns = [
path('fav/<int:id>/', views.favourite_add, name='favourite_add'),
]
TEMPLATE.HTML
<div>
Add
</div>
In general, the goal is to get the id of a certain perfume on the page, and using the get_object_or_404 function, I'm pulling its object from the Perfumes database - the post variable. Next, I want to retrieve the id of the logged-in user and check if the id of the above user is in the favourites section of the post variable. If not then add, otherwise remove the user id to the favourites section of the post variable.
You should not do this through a GET request, as the safe methods section of the HTTP specifications [w3.org] says:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe".
GET and HEAD are thus supposed to have no side effects. You can for example work with a POST request:
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class FavouriteView(LoginRequiredMixin, View):
def post(self, request, id):
perfume = get_object_or_404(Perfumes, id=id)
if request.user in perfume.favourites.all():
perfume.favourites.remove(request.user)
else:
perfume.favourites.add(request.user)
return HttpResponseRedirect(request.META['HTTP_REFERER'])
You then make a mini-form to make a POST request:
<form method="post" action="{% url 'favourite_add' perfume.id %}">
{% csrf_token %}
<button class="btn btn-outline-primary">Add</button>
</form>
Note: normally a Django model is given a singular name, so Perfume instead of Perfumes.
A function based one probably works fine for what you require.
Anyway, here is a view that should perform the same job as yours:
urls.py:
urlpatterns = [
path('fav/<int:id>/', views.FavouriteView.as_view(), name='favourite_add'),
]
views.py:
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class FavouriteView(LoginRequiredMixin, View):
def get(self, *args, **kwargs):
post = get_object_or_404(Perfumes, id=self.kwargs.get('id'))
if post.favourites.filter(id=self.request.user.id).exists():
post.favourites.remove(self.request.user)
else:
post.favourites.add(self.request.user)
return HttpResponseRedirect(self.request.META['HTTP_REFERER'])
I recommend looking at something like https://ccbv.co.uk/ to help you understand class based views
I have a Django project in which I have a TableView with filters on it. It can redirect to UpdateViews and DeleteViews from the rows in the table, and it works fine, and the UpdateView correctly redirects to the TableView on success.
My issue is, I can't manage to make the UpdateView redirected to the TableView while keeping the filters the TableView had when the UpdateView was called.
The UpdateView has this get_context_data method, in which I'm able to send the filtered URL:
def get_context_data(self, **kwargs):
context = super(SaleUpdateView, self).get_context_data(**kwargs)
...
context['referrer'] = self.request.META.get('HTTP_REFERER')
return context
I made a button in the HTML template to redirect to that referrer, which should redirect to the filtered TableView, but it redirects me to the unfiltered TableView. I think it has to do with my form_valid method and get_success_url method:
def form_valid(self, form):
...
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self, form):
...
return reverse_lazy('sale_list')
How can I access either the self.request.META.get('HTTP_REFERER') or the referer data I sent to the template, in the context of the get_success_url method?
I imagine there must be better ways to solve this, but I solved the issue by doing the following:
1. Sending the referer to the UpdateView's template like this:
def get_context_data(self, **kwargs):
context = super(SaleDeleteView, self).get_context_data(**kwargs)
context['referrer'] = self.request.META.get('HTTP_REFERER')
return context
2. Adding it to the template's <form> in a hidden input like this:
<input name="referrer" id="id_referrer" type="hidden" value="{{ referrer }}">
3. Retrieving it in the get_success_url method in my UpdateView
def get_success_url(self):
referrer = self.request.POST['referrer']
if str(referrer) not in ['', 'None']:
return referrer
return reverse_lazy('sale_list')
I added that last validation in case the user reloads the UpdateView, in which case the referer would be empty.
I have a app named "mysite". I want it to execute a python script,when button is clicked, that will put dummy data in database(add row) for now(originally i will be pulling data from a API) but nothing happens when button is clicked.
urls.py
path('', views.index),
views.py
from .fetch_data import get_data
def index(request):
if (request.method == 'POST' and 'script' in request.POST):
get_data()
return render(request, 'mysite/index.html')
index.html in "mysite/templates/mysite" folder
<form method="POST" name='script'>
{% csrf_token %}
<button type="submit" >Fetch data from source</button>
</form>
fetch_data.py
from .models import Hosts
import time, sys
def get_data():
print('here in function')
p = Hosts(hostname="first data")
p.save()
But when i click button, nothing happens. View should remain the same even after successful button click.
Note: fetch_data.py is on same folder as views.py and urls.py.
The name attribute in the form does not send anything to server as post data.
You should change the button like this:
<button type="submit" name="script">Fetch data from source</button>
try this may be this will help you
from .fetch_data import get_data
def index(request):
if (request.method == 'POST'):
script = get_data.objects.all()
script.save()
return render(request, 'mysite/index.html')
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')
With django-allauth, I am forcing a new user to fill out additional profile information on signup using a custom ACCOUNT_SIGNUP_FORM.
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'profiles.signup.ProfileSignupForm'
SOCIALACCOUNT_AUTO_SIGNUP = False
This ProfileSignupForm is then rendered in a modified allauth/templates/socialaccount/signup.html template. This modified template renders the logo of the new user's company that is defined in the new user's session (I used an invitation link that first goes to a RedirectView, writes into the session, and then forwards to the new signup).
signup.html
<html>
<body>
<img src="{{ logo }}" />
{% crispy form %}
</body>
</html>
How can I pull the company logo from my session and pass it to my template without forking the repository and modifying the SignUp View?
That approach would look like this:
class SignupView(RedirectAuthenticatedUserMixin, CloseableSignupMixin, FormView):
def dispatch(self, request, *args, **kwargs):
...
self.company = request.session.get('company')
...
def get_context_data(self, **kwargs):
...
context['logo'] = company.logo
...
Either you can follow the above mentioned way to Inherit the View and Define custom url for Signup to use your view.
Or
you car directly access company logo in your template as:
{{ request.session.company.logo }}
This can be done because request is available as a context variable in Templates if rendered with RequestContext instance.
you can inherit SignupView directly in your code instead of forking and modifying the original SignupView.
class MySignupView(SignupView):
def dispatch(self, request, *args, **kwargs):
...
self.company = request.session.get('company')
...
return super(MySignupView, self).dispatch(request, *args, *kwargs)
def get_context_data(self, **kwargs):
context = super(MySignupView, self).get_context_data(**kwargs)
context['logo'] = self.company.logo
return context
Then using MysignupView.as_view() in the urls.py.