Skip to content

Commit

Permalink
wip to select GET or POST for actions (#168)
Browse files Browse the repository at this point in the history
Another try at enforcing POST actions. This change is more gradual than
#149 - when library user doesn't change default options the behavior is
exactly the same as before the change, that is:

1. Action buttons send GET requests
2. Action handlers accept GET and POST requests

However, user can change this behavior using `methods` and `button_type`
kwargs. For example `@action(methods=['POST'], button_type='form')`
results in

1. Action button sends POST requests
2. Action handler accepts only POST request

Unfortunately I have this tested only within my project. Also the docs
are missing.

And one more thing - I think it is better to use `<input type="submit">`
instead of js to submit the form. This js is need to make the buttons
look the same in both versions. With proper CSS (that is beyond my
ability to write ;) ) js is avoidable and we could be using pretty
semantic html submit button. I took the form button template from #149.
  • Loading branch information
SupraSummus authored Sep 9, 2024
1 parent 813687e commit 1274ae7
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% load add_preserved_filters from admin_urls %}

{% if tool.button_type == 'a' %}
<a href="{% add_preserved_filters action_url %}"
title="{{ tool.standard_attrs.title }}"
{% for k, v in tool.custom_attrs.items %}
{{ k }}="{{ v }}"
{% endfor %}
class="{{ tool.standard_attrs.class }}"
>{{ tool.label|capfirst }}</a>
{% elif tool.button_type == 'form' %}
<form method="post" action="{% add_preserved_filters action_url %}">
{% csrf_token %}
<a href="#" onclick="this.parentNode.submit(); return false;"
title="{{ tool.standard_attrs.title }}"
{% for k, v in tool.custom_attrs.items %}
{{ k }}="{{ v }}"
{% endfor %}
class="{{ tool.standard_attrs.class }}"
>{{ tool.label|capfirst }}</a>
</form>
{% endif %}
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
{% extends "admin/change_form.html" %}
{% load add_preserved_filters from admin_urls %}

{% block object-tools-items %}
{% for tool in objectactions %}
<li class="objectaction-item" data-tool-name="{{ tool.name }}">
{% url tools_view_name pk=object_id tool=tool.name as action_url %}
<a href="{% add_preserved_filters action_url %}" title="{{ tool.standard_attrs.title }}"
{% for k, v in tool.custom_attrs.items %}
{{ k }}="{{ v }}"
{% endfor %}
class="{{ tool.standard_attrs.class }}">
{{ tool.label|capfirst }}
</a>
{% include 'django_object_actions/action_trigger.html' %}
</li>
{% endfor %}
{{ block.super }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
{% extends "admin/change_list.html" %}
{% load add_preserved_filters from admin_urls %}

{% block object-tools-items %}
{% for tool in objectactions %}
<li class="objectaction-item" data-tool-name="{{ tool.name }}">
{% url tools_view_name tool=tool.name as action_url %}
<a href="{% add_preserved_filters action_url %}" title="{{ tool.standard_attrs.title }}"
{% for k, v in tool.custom_attrs.items %}
{{ k }}="{{ v }}"
{% endfor %}
class="{{ tool.standard_attrs.class }}">
{{ tool.label|capfirst }}
</a>
{% include 'django_object_actions/action_trigger.html' %}
</li>
{% endfor %}
{{ block.super }}
Expand Down
17 changes: 11 additions & 6 deletions django_object_actions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.contrib.admin.utils import unquote
from django.db.models.query import QuerySet
from django.http import Http404, HttpResponseRedirect
from django.http.response import HttpResponseBase
from django.http.response import HttpResponseBase, HttpResponseNotAllowed
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import MultipleObjectMixin
Expand Down Expand Up @@ -159,6 +159,7 @@ def _get_tool_dict(self, tool_name):
label=getattr(tool, "label", tool_name.replace("_", " ").capitalize()),
standard_attrs=standard_attrs,
custom_attrs=custom_attrs,
button_type=tool.button_type,
)

def _get_button_attrs(self, tool):
Expand Down Expand Up @@ -238,7 +239,7 @@ def back_url(self):
"""
raise NotImplementedError

def get(self, request, tool, **kwargs):
def dispatch(self, request, tool, **kwargs):
# Fix for case if there are special symbols in object pk
for k, v in self.kwargs.items():
self.kwargs[k] = unquote(v)
Expand All @@ -248,15 +249,15 @@ def get(self, request, tool, **kwargs):
except KeyError:
raise Http404("Action does not exist")

if request.method not in view.methods:
return HttpResponseNotAllowed(view.methods)

ret = view(request, *self.view_args)
if isinstance(ret, HttpResponseBase):
return ret

return HttpResponseRedirect(self.back_url)

# HACK to allow POST requests too
post = get

def message_user(self, request, message):
"""
Mimic Django admin actions's `message_user`.
Expand Down Expand Up @@ -314,7 +315,9 @@ def decorated_function(self, request, queryset):


def action(
function=None, *, permissions=None, description=None, label=None, attrs=None
function=None, *, permissions=None, description=None, label=None, attrs=None,
methods=('GET', 'POST'),
button_type='a',
):
"""
Conveniently add attributes to an action function:
Expand Down Expand Up @@ -349,6 +352,8 @@ def decorator(func):
func.label = label
if attrs is not None:
func.attrs = attrs
func.methods = methods
func.button_type = button_type
return func

if function is None:
Expand Down

0 comments on commit 1274ae7

Please sign in to comment.