30

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>)
Jonathan Drapeau
  • 2,610
  • 2
  • 26
  • 32
user2106729
  • 323
  • 1
  • 3
  • 4

4 Answers4

41

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)
catavaran
  • 44,703
  • 8
  • 98
  • 85
7

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, 'test@example.com', 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().

radtek
  • 34,210
  • 11
  • 144
  • 111
  • 1
    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. – gallen Feb 26 '19 at 07:10
  • works actually, your test may need to follow the redirect. In my case I did not have to do that. – radtek Feb 27 '19 at 18:43
  • make sure to add `testserver` to your `ALLOWED_HOSTS` variable in your settings file. See https://code.djangoproject.com/ticket/27760 for debugging bad requests when testing – J.Vo Aug 18 '20 at 02:56
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.
Wtower
  • 18,848
  • 11
  • 103
  • 80
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
        ...
monkut
  • 42,176
  • 24
  • 124
  • 155
  • 1
    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. – bdoubleu Dec 17 '21 at 12:23
  • 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. – monkut Dec 20 '21 at 01:42