7👍
Yuji is on the right track, but I’ve used a simpler solution that may work for you. If you override response_action as is done below you can replace the empty queryset with a queryset containing all objects before the check happens. This code also checks which action you’re running to make sure it’s approved to run on all objects before changing the queryset, so you can restrict it to only happen in some cases.
def response_action(self, request, queryset):
# override to allow for exporting of ALL records to CSV if no chkbox selected
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
if request.META['QUERY_STRING']:
qd = dictify_querystring(request.META['QUERY_STRING'])
else:
qd = None
data = request.POST.copy()
if len(selected) == 0 and data['action'] in ('export_to_csv', 'extended_export_to_csv'):
ct = ContentType.objects.get_for_model(queryset.model)
klass = ct.model_class()
if qd:
queryset = klass.objects.filter(**qd)[:65535] # cap at classic Excel maximum minus 1 row for headers
else:
queryset = klass.objects.all()[:65535] # cap at classic Excel maximum minus 1 row for headers
return getattr(self, data['action'])(request, queryset)
else:
return super(ModelAdminCSV, self).response_action(request, queryset)
31👍
The accepted answer didn’t work for me in django 1.6, so I ended up with this:
from django.contrib import admin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
class MyModelAdmin(admin.ModelAdmin):
....
def changelist_view(self, request, extra_context=None):
if 'action' in request.POST and request.POST['action'] == 'your_action_here':
if not request.POST.getlist(ACTION_CHECKBOX_NAME):
post = request.POST.copy()
for u in MyModel.objects.all():
post.update({ACTION_CHECKBOX_NAME: str(u.id)})
request._set_post(post)
return super(MyModelAdmin, self).changelist_view(request, extra_context)
When my_action
is called and nothing is selected, select all MyModel
instances in db.
- [Django]-Determine complete Django url configuration
- [Django]-Django 1.7 migrate gets error "table already exists"
- [Django]-How can I temporarily disable a foreign key constraint in MySQL?
13👍
I wanted this but ultimately decided against using it. Posting here for future reference.
Add an extra property (like acts_on_all
) to the action:
def my_action(modeladmin, request, queryset):
pass
my_action.short_description = "Act on all %(verbose_name_plural)s"
my_action.acts_on_all = True
In your ModelAdmin
, override changelist_view
to check for your property.
If the request method was POST, and there was an action specified, and the action callable has your property set to True, modify the list representing selected objects.
def changelist_view(self, request, extra_context=None):
try:
action = self.get_actions(request)[request.POST['action']][0]
action_acts_on_all = action.acts_on_all
except (KeyError, AttributeError):
action_acts_on_all = False
if action_acts_on_all:
post = request.POST.copy()
post.setlist(admin.helpers.ACTION_CHECKBOX_NAME,
self.model.objects.values_list('id', flat=True))
request.POST = post
return admin.ModelAdmin.changelist_view(self, request, extra_context)
- [Django]-How to add a cancel button to DeleteView in django
- [Django]-Django "Cannot add or update a child row: a foreign key constraint fails"
- [Django]-How do I integrate Ajax with Django applications?
3👍
Is there a way to override this
behaviour and let the action run
anyway?
I’m going to say no there is no easy way.
If you grep your error message, you see that the code is in django.contrib.admin.options.py
and the problem code is deep inside the changelist_view.
action_failed = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
# Actions with no confirmation
if (actions and request.method == 'POST' and
'index' in request.POST and '_save' not in request.POST):
if selected:
response = self.response_action(request, queryset=cl.get_query_set())
if response:
return response
else:
action_failed = True
else:
msg = _("Items must be selected in order to perform "
"actions on them. No items have been changed.")
self.message_user(request, msg)
action_failed = True
It’s also used in the response_action
function as well, so you can’t just override the changelist_template and use that either — it’s going to be easiest to define your own action-validity checker and runner.
If you really want to use that drop down list, here’s an idea with no guarantees.
How about defining a new attribute for your selection-less admin actions: myaction.selectionless = True
Copy the response_action
functionality to some extent in your overridden changelist_view
that only works on actions with a specific flag specified, then returns the ‘real’ changelist_view
# There can be multiple action forms on the page (at the top
# and bottom of the change list, for example). Get the action
# whose button was pushed.
try:
action_index = int(request.POST.get('index', 0))
except ValueError:
action_index = 0
# Construct the action form.
data = request.POST.copy()
data.pop(helpers.ACTION_CHECKBOX_NAME, None)
data.pop("index", None)
# Use the action whose button was pushed
try:
data.update({'action': data.getlist('action')[action_index]})
except IndexError:
# If we didn't get an action from the chosen form that's invalid
# POST data, so by deleting action it'll fail the validation check
# below. So no need to do anything here
pass
action_form = self.action_form(data, auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
# If the form's valid we can handle the action.
if action_form.is_valid():
action = action_form.cleaned_data['action']
select_across = action_form.cleaned_data['select_across']
func, name, description = self.get_actions(request)[action]
if func.selectionless:
func(self, request, {})
You’d still get errors when the ‘real’ action is called. You could potentially modify the request.POST to remove the action IF the overridden action is called.
Other ways involve hacking way too much stuff. I think at least.
- [Django]-What is a "django backend"?
- [Django]-Django createview how to get the object that is created
- [Django]-What is a Django "app" supposed to mean?
3👍
I made a change to @AndyTheEntity response, to avoid calling the action once per row.
def changelist_view(self, request, extra_context=None):
actions = self.get_actions(request)
if (actions and request.method == 'POST' and 'index' in request.POST and
request.POST['action'].startswith('generate_report')):
data = request.POST.copy()
data['select_across'] = '1'
request.POST = data
response = self.response_action(request, queryset=self.get_queryset(request))
if response:
return response
return super(BaseReportAdmin, self).changelist_view(request, extra_context)
- [Django]-Update to Django 1.8 – AttributeError: django.test.TestCase has no attribute 'cls_atomics'
- [Django]-How to send a correct authorization header for basic authentication
- [Django]-How do I extend the Django Group model?
1👍
I use the following mixin to create actions that do not require the user to select at least one object. It also allow you to get the queryset that the user just filtered: https://gist.github.com/rafen/eff7adae38903eee76600cff40b8b659
here an example of how to use it (there’s more info of how to use it on the link):
@admin.register(Contact)
class ContactAdmin(ExtendedActionsMixin, admin.ModelAdmin):
list_display = ('name', 'country', 'state')
actions = ('export',)
extended_actions = ('export',)
def export(self, request, queryset):
if not queryset:
# if not queryset use the queryset filtered by the URL parameters
queryset = self.get_filtered_queryset(request)
# As usual do something with the queryset
- [Django]-How to set and get session in Django?
- [Django]-How to obtain a QuerySet of all rows, with specific fields for each one of them?
- [Django]-Create if doesn't exist
0👍
Since object selection isn’t part of what you need, it sounds like you might be best served by creating your own admin view.
Making your own admin view is pretty simple:
- Write the view function
- Put a
@staff_member_required
decorator on it - Add a pattern to your URLconf that points to that view
- Add a link to it by overriding the relevant admin template(s)
You can also use a new 1.1 feature related to this, but you may find it simpler to do as I just described.
- [Django]-Using a UUID as a primary key in Django models (generic relations impact)
- [Django]-Django: Can I create a QueryDict from a dictionary?
- [Django]-Checking the number of elements in an array in a Django template
0👍
Ok, for those of you stubborn enough to want this working, this is an ugly hack(for django 1.3) that will allow ANY action to run even if you didn’t select anything.
You have to fool the original changelist_view into thinking that you have something selected.
class UsersAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
post = request.POST.copy()
if helpers.ACTION_CHECKBOX_NAME not in post:
post.update({helpers.ACTION_CHECKBOX_NAME:None})
request._set_post(post)
return super(ContributionAdmin, self).changelist_view(request, extra_context)
So, in your modeladmin you override the changelist_view adding to the request.POST a key that django uses to store the ids of the selected objects.
In your actions you can check if there are no selected items with:
if queryset == None:
do_your_stuff()
It goes without saying that you are not supposed to do this.
- [Django]-Django: how to do calculation inside the template html page?
- [Django]-Distributing Django projects with unique SECRET_KEYs
- [Django]-Django's forms.Form vs forms.ModelForm
0👍
The simplest solution I found was to create your django admin function as per django docs then in your website admin select any object randomly and run the function. This will pass the item through to your function but you simply don’t use it anywhere so it is redundant. Worked for me.
- [Django]-How to change a django QueryDict to Python Dict?
- [Django]-Using window functions in an update statement
- [Django]-Django Template Ternary Operator