mogoh
mogoh

Reputation: 1071

Django Admin Action Confirmation Page

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?

Upvotes: 15

Views: 13033

Answers (5)

A slightly modified response from @mogoh:

@admin.action(description="Description")
def action_name(self, request: Request, queryset: QuerySet[YOUR_MODEL]):
    if "confirm" in request.POST:
        try:
           # action code here(queryset)
            messages.success(
                request,
                mark_safe("Success message"),
            )
        except Exception as e:
            messages.error(request, mark_safe(str(e)))
        return None

    request.current_app = self.admin_site.name
    return render(
        request,
        "admin/action_confirmation.html",
        context={
            "queryset": queryset,
            "action_name": "action_name",
            "opts": self.model._meta,
        },
    )

admin/action_confirmation.html
It didn't work until I added a loop of inputs of name="_selected_action’

<form action="" method="POST">
{% csrf_token %}
{% for obj in queryset %}
   <input type="hidden" name="_selected_action" value="{{ obj.pk }}" />
{% endfor %}
<input type="hidden" name="post" value="yes" />
<input type="hidden" name="action" value="{{ action_name }}">
<input type="submit" class="btn btn-primary btn-sm" name="confirm" value="Confirm">
<input type="button" class="btn btn-outline-secondary" onclick="location.href=`{% url 'admin:app_model_changelist' %}`" value="Cancel" />

Upvotes: 0

mogoh
mogoh

Reputation: 1071

Edit: I was missing more than 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>

Upvotes: 12

Azimjon Pulatov
Azimjon Pulatov

Reputation: 151

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 %}

Upvotes: 9

knopch1425
knopch1425

Reputation: 759

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">
        <a href="/admin/app/somemodel/" class="button cancel-link">No, take me back</a>
    </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),
]

Upvotes: 5

rsarai
rsarai

Reputation: 179

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

Upvotes: 4

Related Questions