diff --git a/README.md b/README.md index 6cb35c5..615c024 100644 --- a/README.md +++ b/README.md @@ -186,11 +186,12 @@ preferable that actions use a HTTP POST instead of a GET. You can configure an action to only use POST with: ```python -@action(methods=("post",), button_type="form") +@action(methods=("POST",), button_type="form") ``` -One caveat is Django's styling is pinned to anchor tags[^1], so to maintain visual -consistency with other actions, we have to use anchor tags too. +One caveat is Django's styling is pinned to anchor tags[^1], so to maintain +visual consistency with other actions, we have to use anchor tags too and use +JavaScript to turn make it act like a form. [^1]: https://github.com/django/django/blob/826ef006681eae1e9b4bd0e4f18fa13713025cba/django/contrib/admin/static/admin/css/base.css#L786 @@ -267,4 +268,3 @@ open a simple form in a modal dialog. If you want an actions menu for each row of your changelist, check out [Django Admin Row Actions](https://github.com/DjangoAdminHackers/django-admin-row-actions). - diff --git a/django_object_actions/tests/test_admin.py b/django_object_actions/tests/test_admin.py index f4db02c..de24fdf 100644 --- a/django_object_actions/tests/test_admin.py +++ b/django_object_actions/tests/test_admin.py @@ -76,10 +76,22 @@ def test_changelist_template_context(self): self.assertIn("foo", response.context_data) def test_changelist_action_view(self): - url = "/admin/polls/choice/actions/delete_all/" + url = reverse("admin:polls_choice_actions", args=("delete_all",)) response = self.client.get(url) self.assertRedirects(response, "/admin/polls/choice/") + def test_changelist_action_post_only_tool_rejects_get(self): + poll = PollFactory.create() + url = reverse( + "admin:polls_choice_actions", + args=( + poll.pk, + "reset_vote", + ), + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 405) + def test_changelist_nonexistent_action(self): url = "/admin/polls/choice/actions/xyzzy/" response = self.client.get(url) diff --git a/django_object_actions/tests/tests.py b/django_object_actions/tests/tests.py index bee08b3..e78fa4b 100644 --- a/django_object_actions/tests/tests.py +++ b/django_object_actions/tests/tests.py @@ -46,6 +46,7 @@ def test_can_return_template(self): # it's good to document that this is something we can do. url = reverse("admin:polls_poll_actions", args=(1, "delete_all_choices")) response = self.client.get(url) + print(url, response) self.assertTemplateUsed(response, "clear_choices.html") def test_message_user_sends_message(self): diff --git a/django_object_actions/utils.py b/django_object_actions/utils.py index b1d90c6..99afb40 100644 --- a/django_object_actions/utils.py +++ b/django_object_actions/utils.py @@ -218,7 +218,6 @@ class BaseActionView(View): model = None actions = None current_app = None - methods = ("GET", "POST") @property def view_args(self): @@ -250,8 +249,11 @@ def dispatch(self, request, tool, **kwargs): except KeyError: raise Http404("Action does not exist") - if request.method not in self.methods: - return HttpResponseNotAllowed(view.methods) + # TODO move ('get', 'post' config default someplace else) + allowed_methods = getattr(view, "methods", ("GET", "POST")) + print("dispatch", request.method, allowed_methods) + if request.method.upper() not in allowed_methods: + return HttpResponseNotAllowed(allowed_methods) ret = view(request, *self.view_args) if isinstance(ret, HttpResponseBase): diff --git a/example_project/polls/admin.py b/example_project/polls/admin.py index 8d784a8..3c2ffdb 100644 --- a/example_project/polls/admin.py +++ b/example_project/polls/admin.py @@ -45,7 +45,7 @@ def decrement_vote(self, request, obj): def delete_all(self, request, queryset): self.message_user(request, "just kidding!") - @action(description="0") + @action(description="0", methods=("POST",), button_type="form") def reset_vote(self, request, obj): obj.votes = 0 obj.save()