32

I'm new to django and I'm having trouble testing custom actions(e.g actions=['mark_as_read']) that are in the drop down on the app_model_changelist, it's the same dropdown with the standard "delete selected". The custom actions work in the admin view, but I just dont know how to call it in my mock request, I know I need to post data but how to say I want "mark_as_read" action to be done on the data I posted?

I want to reverse the changelist url and post the queryset so the "mark_as_read" action function will process the data I posted.

change_url = urlresolvers.reverse('admin:app_model_changelist')
response = client.post(change_url, <QuerySet>)
2
  • I am trying to repeat this. How does your urls.py look like for this app? In other words: where is admin:app_model_changelist coming from? Commented Aug 31, 2016 at 14:18
  • 1
    Never mind, I found it here already: docs.djangoproject.com/en/dev/ref/contrib/admin/… Commented Aug 31, 2016 at 14:21

4 Answers 4

45

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)
Sign up to request clarification or add additional context in comments.

4 Comments

To make it a bit more robust you can use django.contrib.admin.ACTION_CHECKBOX_NAME instead of "_selected_action".
It's only available from django.contrib.admin.helpers.ACTION_CHECKBOX_NAME since Nov 2019.
if it doesn't work make sure change_url has not query params
what is the use of unicode() here?
10

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().

3 Comments

This is very, very close to working. I had to add the follow=true parameter to the self.client.post() call. Without it, I received a 302 redirect and the assertion failed.
works actually, your test may need to follow the redirect. In my case I did not have to do that.
make sure to add testserver to your ALLOWED_HOSTS variable in your settings file. See code.djangoproject.com/ticket/27760 for debugging bad requests when testing
6

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.

1 Comment

if it doesn't work make sure change_url has not query params
3

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
        ...

2 Comments

It's not necessary to use RequestFactory separately as it's already built into django.test.TestCase - it's accessible through self.client.get(...) and eliminates all the boilerplate code.
Note this does not actually make an http request, as self.client.get() does, it's creating a request object for use with calling the modeladmin's action.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.