Django Admin Action Confirmation Page - django

In my Django project I have an admin action which requires an confirmation page. I oriented it on the delete_selected action, but it does not work. Here is what I have.
part of my admin.py
def my_action(modeladmin, request, queryset):
if request.POST.get('post'):
print "Performing action"
# action code here
return None
else:
return TemplateResponse(request, "admin/my_action_confirmation.html")
admin/my_action_confirmation.html
<form action="" method="post">{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes" />
<input type="hidden" name="action" value="my_action" />
<input type="submit" value="Confirm" />
</div>
</form>
This works almost. I get to the confirmation page but if I click "confirm" I just get back to the original page. The part with the action code is never reached. In fact the my_action function isn't called a second time. So how do I tell django, that the my_action function should be called a second time, once I clicked confirm?

Edit: I was more missing then I thought
The corrected my_action
def my_action(modeladmin, request, queryset):
if request.POST.get('post'):
print "Performing action"
# action code here
return None
else:
request.current_app = modeladmin.admin_site.name
return TemplateResponse(request, "admin/my_action_confirmation.html")
admin/my_action_confirmation.html
{% load l10n %}
<form action="" method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="post" value="yes" />
<input type="hidden" name="action" value="my_action" />
<input type="submit" value="Confirm" />
</div>
</form>

admin.py
def require_confirmation(func):
def wrapper(modeladmin, request, queryset):
if request.POST.get("confirmation") is None:
request.current_app = modeladmin.admin_site.name
context = {"action": request.POST["action"], "queryset": queryset}
return TemplateResponse(request, "admin/action_confirmation.html", context)
return func(modeladmin, request, queryset)
wrapper.__name__ = func.__name__
return wrapper
#require_confirmation
def do_dangerous_action(modeladmin, request, queryset):
return 42/0
admin/action_confirmation.html
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation
delete-selected-confirmation{% endblock %}
{% block content %}
<p>Are you sure you want to {{ action }}?</p>
<ul style="padding: 0">
{% for object in queryset.all %}
<li style="list-style: none; float: left; margin: 5px">
{{ object }}
</li>
{% endfor %}
</ul>
<hr>
<br>
<form action="" method="post">{% csrf_token %}
<fieldset class="module aligned">
{% for obj in queryset.all %}
<input type="hidden" name="_selected_action" value="{{ obj.pk|unlocalize }}"/>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="hidden" name="action" value="{{ action }}"/>
<input type="submit" name="confirmation" value="Confirm"/>
<a href="#" onclick="window.history.back(); return false;"
class="button cancel-link">{% trans "No, take me back" %}</a>
</div>
</form>
{% endblock %}

Just in case of anyone wanting to add a confirmation view to something more than an action.
I wanted to make the save of an admin creation view go to a confirmation view. My model was very complex and created a lot of implications for the system. Adding the confirmation view would make sure that the admin was aware of these implications.
The solution would be overriding some _changeform_view method which is called on the creation and the edition.
The full code is here: https://gist.github.com/rsarai/d475c766871f40e52b8b4d1b12dedea2

Here is what worked for me:
Add confirm action method (in admin.py)
from django.template.response import TemplateResponse
def confirm_my_action(modeladmin, request, queryset):
response = TemplateResponse(request,
'admin/confirm_my_action.html',
{'queryset': queryset})
return response
and point to it from your admin model (in admin.py)
class SomeModelAdmin(admin.ModelAdmin):
actions = [confirm_my_action]
Add template, which has a form whose action is pointing to my_action endpoint.
{% extends "admin/base_site.html" %}
{% block content %}
<div id="content" class="colM delete-confirmation">
<form method="post" action="/admin/my_action/">
{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="obj_ids[]" value="{{ obj.pk }}" />
<ul><li>Obj: ">{{obj}}</a></li></ul>
{% endfor %}
</div>
<input type="submit" value="Yes, I'm sure">
No, take me back
</form>
<br class="clear">
<div id="footer"></div>
</div>
{% endblock %}
Add appropriate endpoint (e.g. in urls.py).
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^admin/my_action/', my_action_method),
]

Related

if request.method == "POST": the pointer is not coming in the if statement can anybody tell?

This code is not working well for the post request in the views file so anybody please try to tell me why the cursor is not going inside the "POST" please find out the problem.
views.py
#login_required()
def candidate_view(request):
if request.method == "POST":
can = candidate.objects.filter(position = 'President')
return render(request,'poll/candidate.html',{'can':can})
else:
return render(request, 'poll/candidate.html')
candidate.html
{% extends 'base.html' %}
{% block title %}Candidates{% endblock %}
{%block body%}
<h2>Available Candidates of {{ obj.title }}</h2>
<form action="" method="POST">
{% csrf_token %}
{% for c in can.candidate_set.all %}
<!-- <input type="radio" name="{{ c.position}}" value="{{c.id}}" required> <strong>{{c.name}} Detail</strong>
<br> -->
<div class="custom-control custom-radio">
<input type="radio" id="id_{{c.id}}" name="{{ c.full_name}}" value="{{c.id}}" class="custom-control-input" required>
<label class="custom-control-label" for="id_{{c.id}}">{{c.full_name}}</label>
</div>
{% empty %}
<p>No Candidates Available</p>
{% endfor %}
<br><input type="submit" class="btn btn-outline-success btn-sm" value="VOTE">
</form>
<br><p>Back to Poll</p>
{% endblock %}
vote.html
{% extends 'base.html' %}
{% block title %}Positions{% endblock %}
{%block body%}
<form action="" method="POST">
{%csrf_token%}
<ul>
<li><h2> President</h2></li>
<li><h2> Vice President </h2></li>
<li><h2> Secratary </h2></li>
<li><h2> Vice Secratary </h2></li>
</ul>
</form>
{% endblock %}
urls.py
from django.contrib import admin
from django.urls import path,include
from . import views
urlpatterns = [
path('',views.home,name="home"),
path('register',views.register,name="register"),
path('login',views.view_login,name="view_login"),
path('logout',views.view_logout,name="view_logout"),
path('candidate_view',views.candidate_view,name="candidate_view"),
path('vote',views.vote,name="vote"),
path('result',views.result,name="result"),
]
Replace
<form action="" method="POST">
{%csrf_token%}
<ul>
<li><h2> President</h2></li>
<li><h2> Vice President </h2></li>
<li><h2> Secratary </h2></li>
<li><h2> Vice Secratary </h2></li>
</ul>
</form>
with
<form action="{% url 'candidate_view' %}" method="POST">
{%csrf_token%}
<label for="President">President</label>
<input type="radio" id="President" name="position" value="President" />
<label for="Vice-President">Vice President</label>
<input type="radio" id="Vice-President" name="position" value="Vice President" />
( similarly for secretary and vice secretary )
<button>Submit</button>
</form>
Also update view.py with
def candidate_view(request):
if request.method == "POST":
can = candidate.objects.filter(position = request.POST.get("position"))
return render(request,'poll/candidate.html',{'can':can})
else:
return render(request, 'poll/vote.html')

My django form is not working when i iterate through it using for loop

Basically if I tried to use this code
{% for field in form %}
<div class="input">
<label for="" class="labelinput">{{field.label}}</label>
{{field}}
</div>
{% endfor %}
the form data wont make it pass is_valid().But it renders out the form fine. and if I use this code
<form action="" method="post"> {% csrf_token %}
{{form}}
<input type="submit" value="">
it worked perfectly fine. How do I get the first code to work because I want to add classes between the label and the input field
and here's my view
def booklist_view(request):
bkff = BookListForm()
if request.method == 'POST':
bkff = BookListForm(request.POST)
if bkff.is_valid():
bkff.save()
bkff = BookListForm()
context = {
'form': bkff,
}
return render(request, 'booklist1st/booklist.html', context)
Please try this.
views.py
def booklist_view(request):
form = BookListForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
context = {'form': form }
return render(request, 'booklist1st/booklist.html', context)
Here we render field according according to field type(hidden_fields,visible_fields).
html template:
<form method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="input">
{{field.label}}
{{field}}
</div>
{% endif %}
<input class="btn btn-primary" type="submit" name="add_book" value="Save and add book" />
</form>
You need to specify each field relate data like for=, id=, etc. To have maximum control over how your form is rendered, specify each field and control its style, for example, as we can't see your Form definition, this is an example on how it would be for a title field:
<form method="post">{% csrf_token %}
{# Include the hidden fields #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# Include the visible fields #}
{{ form.non_field_errors }}
{{ form.title.errors }}
<label for="{{ form.title.id_for_label }}">Title</label>
{{ form.title }}
{% if form.title.help_text %}
<small id="titleHelp">{{ form.title.help_text|safe }}</small>
{% endif %}
</div>
<input class="btn btn-primary" type="submit" name="add_book" value="Save and add book" />
</form>
<form method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="input">
<label for="" class="label">{{field.label}}</label>
{{field}}
</div>
{% endfor %}
<input class="btn btn-primary" type="submit" name="add_book" value="Save and add book" />
</form>
View.py
from django.views.decorators.csrf import csrf_protect
#csrf_protect
def booklist_view(request):
bkff = BookListForm()
if request.method == 'POST':
bkff = BookListForm(request.POST)
if bkff.is_valid():
bkff.save()
context = {
'form': bkff,
}
return render(request, 'booklist1st/booklist.html', context)

Get an object id from HTML in in request.POST

I have an user with list of tasks that he can add. I want to give him ability to delete those tasks, or mark as done.
The problem is that my solution is working only when user has one task, because of non-unique id's problem
Is there any way to pass the id to html so that it will be easily accesible in views? Thank you!
This is my current code
{% for task in tasks %}
<form id='is_checked' method='POST' action="{% url 'mark_as_done'%}" enctype="multipart/form-data">
{% csrf_token %}
<div class="input-group-text">
<input type="hidden" id="id_checked" value="{{task.id}}" name = "id_checked">
</div>
</form>
<form class="input-group" action="{% url 'delete_task'%}" method='POST' enctype="multipart/form-data">
{% csrf_token %}
<div class="input-group-prepend">
<div class="input-group-text">
<input onChange="document.getElementById('is_checked').submit()" type="checkbox" {% if task.is_done %}checked{% endif %}>
</div>
</div>
<h7 type="text" class="form-control">{{task}}</h7>
<input type="hidden" id="id" value="{{task.id}}" name = "id">
<button type="submit" class="input-group-append btn btn-danger">Delete</button>
</form>
{% endfor %}
And in views:
def delete_task(request):
if request.method == 'POST':
task = Task.objects.get(pk=request.POST['id'])
task.delete()
return redirect('tasks')
#login_required()
def mark_as_done(request):
if request.method == 'POST':
task = Task.objects.get(pk=request.POST['id_checked'])
task.is_done = True
task.save()
return redirect('tasks')```

Django admin - Raw Id Input on intermediate page

I've have a Topic model in the django admin (v1.6). I'm trying to make an action that takes the queryset and makes the objects aliases of another Topic. This other Topic will be selected in an intermediate page. I am having a hard time understanding how to get the Raw ID input into the intermediate page, and I haven't seen any code like this.
Here are my models
class Topic(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Alias(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
topic = models.ForeignKey(Topic, related_name='aliases')
My admin.py is something like this:
from django.template.response import TemplateResponse
from breakingnews.topics.models import Topic, Alias
class TopicAdmin(admin.ModelAdmin):
def make_alias(self, request, queryset):
if request.POST.get('post'):
# process the queryset here
head = request.POST.get('head')
tail = queryset
for topic in tail:
a, is_new = Alias.objects.get_or_create(name=topic.name, slug=topic.slug, topic=head)
else:
context = {
'title': "To what topic would you like the alias to point?",
'queryset': queryset,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}
return TemplateResponse(request, 'admin/make_alias.html',
context, current_app=self.admin_site.name)
add_tag.short_description = "Make these topics an alias to another"
...
actions = [
add_tag,
]
admin.site.register(Topic, TopicAdmin)
And the make_alias.html is this:
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls %}
{% block content %}
<p>{% blocktrans %}To what topic would you like the alias to point? {% endblocktrans %}</p>
<ul>{{ queryset|unordered_list }}</ul>
<form action="" method="post">{% csrf_token %}
<div>
<input type="text" name="head" value="" />
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="make_alias" />
<input type="hidden" name="post" value="yes" />
<input type="submit" value="Confirm" />
</div>
<!-- <ul>{{ queryset|unordered_list }}</ul> -->
</form>
{% endblock %}
Here's what happened to make it work:
Add this to Admin.py:
from django.template.response import TemplateResponse
from breakingnews.topics.models import Topic, Alias
class AliasCreateForm(forms.ModelForm):
parents = forms.ModelChoiceField(
queryset=Topic.objects.all(),
widget=ManyToManyRawIdWidget(
Topic._meta.get_field('parents').rel, admin.site)
)
class Meta:
model = Topic
fields = ('parents',)
def make_alias(self, request, queryset):
if request.POST.get('post'):
# Handle the response
else:
formset = AliasCreateForm()
context = {
'title': "To what topic would you like the alias to point?",
'queryset': queryset,
'formset': formset,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'action':'make_alias',
}
return TemplateResponse(request, 'admin/make_alias.html',
context, current_app=self.admin_site.name)
make_alias.short_description = "Make these topics an alias to another"
And make_alias.html:
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls %}
{% block extrahead %}
<script type="text/javascript" src="/static/admin/js/admin/RelatedObjectLookups.js"></script>
{% endblock %}
{% block content %}
<form action="" method="post">{% csrf_token %}
<ul>{{ queryset|unordered_list }}</ul>
{{ formset }}
<br /><br />
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="{{action}}" />
<input type="hidden" name="post" value="yes" />
<input type="submit" value="Submit" />
</form>
{% endblock %}

Django admin action not executed after confirming

In my Django app I want to implement an intermediate page which asks for confirmation before executing a specific admin action. I took this post as an example.
I used the existing delete_confirmation.html template as a departing point and partially got it working. The confirmation page is shown and the selected objects are displayed. However my admin action is never called after clicking "Yes, I'm sure".
In my admin.py I have:
def cancel_selected_bookings(self, request, queryset):
"""
Cancel selected bookings.
"""
if request.POST.get("post"):
for booking in queryset:
booking.cancel()
message = "Booking %s successfully cancelled." % booking.booking_id
messages.info(request, message)
else:
context = {
"objects_name": "bookings",
'title': "Confirm cancellation of selected bookings:",
'cancellable_bookings': [queryset],
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}
return TemplateResponse(request, 'admin/bookings/confirm_cancel.html', context, current_app=self.admin_site.name)
In my template I have (a clipping of the full template):
<div class="grp-group">
<h2>
{% blocktrans %}
Are you sure you want to cancel the selected {{ objects_name }}?
{% endblocktrans %}
</h2>
{% for cancellable_booking in cancellable_bookings %}
<ul class="grp-nested-list">{{ cancellable_booking|unordered_list }}</ul>
{% endfor %}
</div>
<form action="" method="post">{% csrf_token %}
<div id="submit" class="grp-module grp-submit-row grp-fixed-footer">
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="cancel_selected_bookings" />
<input type="hidden" name="post" value="yes" />
<ul>
<li class="grp-float-left">{% trans "Cancel" %}</li>
<li><input type="submit" value="{% trans "Yes, I'm sure" %}" class="grp-button grp-default" /></li>
</ul>
<input type="hidden" name="post" value="yes" />
</div>
</form>
EDIT:
I found one problem in the above template. The lines:
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
need to be replaced by:
{% for cancellable_booking in cancellable_bookings %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ cancellable_booking.id }}" />
{% endfor %}
For some mysterious reason however, the value of the hidden fields isn't being set by {{cancellable_booking.id}}. When I hard code that with an existing id it all works as expected. What am I doing wrong??
This will work:
In the action method:
context = {
'objects_name': 'bookings',
'title': 'Confirm cancellation of selected bookings:',
'cancellable_bookings': [queryset],
'ids': queryset.values_list("id"),
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}
In the template:
{% for id in ids %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ id.0|unlocalize }}" />
{% endfor %}
Not sure why iterating the queryset doesn't work but alas...