Skip to content

Instantly share code, notes, and snippets.

@mariocesar
Last active December 28, 2023 19:17
Show Gist options
  • Save mariocesar/8adc56de00104e90bac7 to your computer and use it in GitHub Desktop.
Save mariocesar/8adc56de00104e90bac7 to your computer and use it in GitHub Desktop.
Django admin decorator to create a confirmation form action, like the default delete action works
from .models import Post, Category
from .decorators import action_form
class PostCategoryForm(forms.Form):
title = 'Update category for the selected posts'
myfile = forms.FileField()
category = forms.ModelChoiceField(queryset=Category.objects.all())
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
actions = ['change_category']
@action_form(PostCategoryForm)
def change_category(self, request, queryset, form):
category = form.cleaned_data['category']
return queryset.update(category=category)
import functools
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
def action_form(form_class=None):
def decorator(func):
@functools.wraps(func)
def wrapper(self, request, queryset):
form = form_class()
if 'confirm' in request.POST and request.POST:
form = form_class(request.POST, request.FILES)
if form.is_valid():
obj_count = func(self, request, queryset, form)
self.message_user(request, '%s objects updated' % obj_count)
return None
context = dict(
self.admin_site.each_context(request),
title=form_class.title,
action=func.__name__,
opts=self.model._meta,
queryset=queryset, form=form,
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME)
return TemplateResponse(request, 'admin/form_action_confirmation.html', context)
wrapper.short_description = form_class.title
return wrapper
return decorator
{% 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 breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {{ title }}
</div>
{% endblock %}
{% block content %}
<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" enctype="multipart/form-data">{% csrf_token %}
<fieldset class="module aligned">
{% for obj in queryset.all %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}"/>
{% endfor %}
<div class="form-row">
{{ form }}
</div>
</fieldset>
<div class="submit-row">
<input type="hidden" name="action" value="{{ action }}"/>
<input type="submit" name="confirm" value="{% trans "Confirm" %}"/>
<a href="#" onclick="window.history.back(); return false;"
class="button cancel-link">{% trans "No, take me back" %}</a>
</div>
</form>
{% endblock %}
@mikicz
Copy link

mikicz commented Dec 6, 2018

Thanks for the inspiration!

@Maytrovato
Copy link

I'm missing something, after I hit the confirmation button, it does not return to the verification in the decorator, why?

@gamesbook
Copy link

The admin.py part is not clear; is it possible to show a simple worked example for just changing a single, unlinked model - for example, updating basic user information?

@twolfson
Copy link

For supporting file uploads in forms, use:

{# form_action_confirmation.html #}
{# enctype required for file support, won't affect submissions except POST body format, https://stackoverflow.com/a/33891429/1960509 #}
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
# decorators.py
# https://docs.djangoproject.com/en/4.0/topics/http/file-uploads/#basic-file-uploads
form = form_class(request.POST, request.FILES)

@mariocesar
Copy link
Author

@twolfson thank you, I added the lines

@IT-HONGREAT
Copy link

Thank you so much!!

@DKey96
Copy link

DKey96 commented Aug 23, 2022

Unfortunately, after confirmation inside by confirmation button, I'm facing an issue, that the method (decorated one) is not being triggered so wanted actions does not run. By debugging, it can be seen, that the code is not reached after the confirmation. Do you face the same issue?

@holvi-mikael
Copy link

holvi-mikael commented Nov 3, 2023

For those not getting to the confirmation: You might have dropped the {% for obj in queryset.all %} loop, or the hidden "action" field, or added a value as the form action. All should stay as-is for the admin action view to be called again after the submit button is clicked.

@vuducmanh11
Copy link

vuducmanh11 commented Dec 28, 2023

I do same your instructions but exist values not autofill in confirmation form, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment