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...
Related
In my template i'm trying to hide a paypal button if the email item is None.
{% if object.email == None %}
<h1>Nothing here </h1>
{% else %}
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="business" value="{{ object.email }}">
</form>
{% endif %}
After testing by not entering the email in my form, the button still shows and the 'Nothing here' is not showing.
Try this
{% if not object.email %}
<h1>Nothing here </h1>
{% else %}
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="business" value="{{ object.email }}">
</form>
{% endif %}
I'm new to Django and would like some advice on how to set up a single language switching button to switch between two languages (English and Japanese in this case), and specifically how to set up this functionality in a view and access it from a template.
In the template, I've arrived at the following which works but it includes repeated code for the form and the button, which obviously isn't great.
{% get_current_language as LANGUAGE_CODE %}
{% ifequal LANGUAGE_CODE 'en' %}
<form action="{% url 'set_language' %}" method="post" id="form_{{ 'ja' }}" >
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<input name="language" type="hidden" value="{{ 'ja' }}" />
</form>
<button class="btn btn-sm btn-outline-primary" type="submit" form="form_{{ 'ja' }}" value="Submit">日本語</button>
{% else %}
<form action="{% url 'set_language' %}" method="post" id="form_{{ 'en' }}" >
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<input name="language" type="hidden" value="{{ 'en' }}" />
</form>
<button class="btn btn-sm btn-outline-primary" type="submit" form="form_{{ 'en' }}" value="Submit">English</button>
{% endifequal %}
My apologies for the beginner question but I'd really appreciate some advice as to how to put the above logic into a view and access it from the template. Thanks in advance.
In case this is useful to anyone, I solved this problem as follows using a custom filter included in a file I called language_selector.py.
In language_selector.py:
from django import template
register = template.Library()
#register.filter
def new_lang_code(current_lang_code):
if current_lang_code == ‘en’:
return ‘ja’
else:
return ‘en’
#register.filter
def new_lang_name(current_lang_code):
if current_lang_code == ‘en’:
return ‘日本語’
else:
return ‘English’
Set up a template tags directory containing the above file.
app/
__init__.py
models.py
templatetags/
__init__.py
language_selector.py
views.py
In the template:
{% load language_selector %}
⋮
<form action=“{% url ‘set_language’ %}” method=“post” id=“form_{{ LANGUAGE_CODE|new_lang_code }}” >
{% csrf_token %}
<input name=“next” type=“hidden” value=“{{ redirect_to }}” />
<input name=“language” type=“hidden” value=“{{ LANGUAGE_CODE|new_lang_code }}” />
</form>
<button class=“btn btn-sm btn-outline-primary” type=“submit” form=“form_{{ LANGUAGE_CODE|new_lang_code }}” value=“Submit”>{{ LANGUAGE_CODE|new_lang_name }}</button>
This works and seems to be a simple and clean way to implement a language-switching button, but any comments are welcome if there are better ways to do this.
def listing(request):
contact_list = Question.objects.all()
paginator = Paginator(contact_list, 1) # Show 25 contacts per page
page = request.GET.get('page')
contacts = paginator.get_page(page)
Let's say in each page I have a form, and when the form submitted I need to go to the next page.
I first tried to add a hidden field in the form, and manually calculate the next page, and put into HTTPResponseRedirect, but then I get an empty object error for the last page because of the following:
<input type="hidden" name="next" value="?page={{ contacts.next_page_number }}">
That page contains no results
{% for contact in contacts %}
{# Each "contact" is a Contact model object. #}
{{ contact.question_text|upper }}<br>
...
<form action="{% url 'listing' %}" method="post">
{% csrf_token %}
{% for choice in contact.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
<input type="hidden" name="next" value="?page={{ contacts.next_page_number }}">
{% endfor %}
<input type="submit" value="Vote">
</form>
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
last »
{% endif %}
</span>
</div>
So what should be my strategy? Do I need to create a seperate view for form action, or should I use listing as form action and check wheter it is a GET/POST method, I am kind of lost here.
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),
]
I am just learning Django and web development in general and I was wondering if what I want to do is possible. I would like to write a Django quiz that saves the answers a user inputs without needing a backend. Is this possible?If it is not possible what is the simplest and easiest way I can do this. My template:
{% extends "base.html" %}
{% block title %}Exam Questions{% endblock %}
{% block content %}
{% if all_questions %}
<form action="{% url 'save_answer' %}" method="post">
{% csrf_token %}
{% for question in all_questions %}
<h3>{{question.text }}</h3>
<input type="hidden" name="exam_id" value="{{ question.exam.id }}">
<input type="hidden" name="question_id" value="{{ question.id }}">
<input type="hidden" value="{{question.answer_set.all}}" name="answers">
{% for answer in question.answer_set.all %}
<p><input type="radio" name="answer" value="{{ answer.id }}">{{ answer.text }}</p>
{% endfor %}
{% endfor %}
<input type="submit" value="Send">
</form>
{% else %}
<h2>No questions available</h2>
{% endif %}
{% endblock %}
Now I would like to know how to save user answers without backends
Afaik, you can save it in your backend (django forms doc), or save it with javascript in a client browser cookie.
First, make a litle change with your template to make radio choices work
<p><input type="radio" name="answer_{{ question.id }}" value="{{ answer.id }}">{{ answer.option }}</p>
Then, here is the code to store answers to session:
def post(self, request, *args, **kwargs):
post = request.POST
question_ids = post.getlist('question_id')
results = dict()
for id in question_ids:
answer = post.get('answer_%s' % id)
results[id] = answer
# Save to session or do whatever you want.
request.session['results'] = results
# Response to review
return JsonResponse(results)