Testing custom admin actions in django
Asked Answered
L

4

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>)
Luckily answered 13/3, 2015 at 7:19 Comment(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?Lottielotto
Never mind, I found it here already: docs.djangoproject.com/en/dev/ref/contrib/admin/…Lottielotto
B
43

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)
Bridgman answered 13/3, 2015 at 7:50 Comment(4)
To make it a bit more robust you can use django.contrib.admin.ACTION_CHECKBOX_NAME instead of "_selected_action".Hett
It's only available from django.contrib.admin.helpers.ACTION_CHECKBOX_NAME since Nov 2019.Shaven
if it doesn't work make sure change_url has not query paramsQuiver
what is the use of unicode() here?Phototherapy
V
9

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

Volumetric answered 21/2, 2019 at 5:36 Comment(3)
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.Landin
works actually, your test may need to follow the redirect. In my case I did not have to do that.Volumetric
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 testingGalactic
W
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.
Wanettawanfried answered 16/11, 2015 at 15:5 Comment(1)
if it doesn't work make sure change_url has not query paramsQuiver
P
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
        ...
Paroicous answered 5/6, 2021 at 4:19 Comment(2)
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.Accessory
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.Paroicous

© 2022 - 2024 — McMap. All rights reserved.