Testing custom admin actions in django

Here is how you do it with login and everything, a complete test case:

from django.test import TestCase
from django.urls import reverse

from content_app.models import Content

class ContentModelAdminTests(TestCase):

    def setUp(self):
        # Create some object to perform the action on
        self.content = Content.objects.create(titles='{"main": "test tile", "seo": "test seo"}')

        # Create auth user for views using api request factory
        self.username = 'content_tester'
        self.password = 'goldenstandard'
        self.user = User.objects.create_superuser(self.username, '[email protected]', self.password)

    def shortDescription(self):
        return None

    def test_actions1(self):
        """
        Testing export_as_json action
        App is content_app, model is content
        modify as per your app/model
        """
        data = {'action': 'export_as_json',
                '_selected_action': [self.content._id, ]}
        change_url = reverse('admin:content_app_content_changelist')
        self.client.login(username=self.username, password=self.password)
        response = self.client.post(change_url, data)
        self.client.logout()

        self.assertEqual(response.status_code, 200)

Just modify to use your model and custom action and run your test.

UPDATE: If you get a 302, you may need to use follow=True in self.client.post().


Just pass the parameter action with the action name.

response = client.post(change_url, {'action': 'mark_as_read', ...})

Checked items are passed as _selected_action parameter. So code will be like this:

fixtures = [MyModel.objects.create(read=False),
            MyModel.objects.create(read=True)]
should_be_untouched = MyModel.objects.create(read=False)

#note the unicode() call below
data = {'action': 'mark_as_read',
        '_selected_action': [unicode(f.pk) for f in fixtures]}
response = client.post(change_url, data)

Note that even if the POST is successful, you still need to test that your action performed the operations intended successfully.

Here's another method to test the action directly from the Admin class:

from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.contrib.messages.storage.fallback import FallbackStorage

from myapp.models import MyModel
from myapp.admin import MyModelAdmin


class MyAdminTestCase(TestCase):
    def setUp(self) -> None:
        self.site = AdminSite()
        self.factory = RequestFactory()
        self.superuser = User.objects.create_superuser(username="superuser", is_staff=True)

    def test_admin_action(self):
        ma = MyModelAdmin(MyModel, self.site)
        url = reverse("admin:myapp_mymodel_changelist")
        superuser_request = self.factory.get(url)
        superuser_request.user = self.superuser

        # if using 'messages' in your actions
        setattr(superuser_request, 'session', 'session')
        messages = FallbackStorage(superuser_request)
        setattr(superuser_request, '_messages', messages)

        qs = MyModel.objects.all()        
        ma.mymodeladminaction(superuser_request, queryset=qs)
        
        # check that your action performed the operations intended
        ...

This is what I do:

data = {'action': 'mark_as_read', '_selected_action': Node.objects.filter(...).values_list('pk', flat=True)}
response = self.client.post(reverse(change_url), data, follow=True)
self.assertContains(response, "blah blah...")
self.assertEqual(Node.objects.filter(field_to_check=..., pk__in=data['_selected_action']).count(), 0)

A few notes on that, comparing to the accepted answer:

  • We can use values_list instead of list comprehension to obtain the ids.
  • We need to specify follow=True because it is expected that a successfull post will lead to a redirect
  • Optionally assert for a successful message
  • Check that the changes indeed are reflected on the db.