diff --git a/README.md b/README.md index eba9601..83a4773 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,12 @@ our templates. In your admin.py: ```python -from django_object_actions import DjangoObjectActions +from django_object_actions import DjangoObjectActions, action class ArticleAdmin(DjangoObjectActions, admin.ModelAdmin): + @action(label="Publish", description="Submit this article") # optional def publish_this(self, request, obj): publish_obj(obj) - publish_this.label = "Publish" # optional - publish_this.short_description = "Submit this article" # optional change_actions = ('publish_this', ) ``` @@ -49,10 +48,13 @@ views too. There, you'll get a queryset like a regular [admin action][admin acti from django_object_actions import DjangoObjectActions class MyModelAdmin(DjangoObjectActions, admin.ModelAdmin): + + @action( + label="This will be the label of the button", # optional + description="This will be the tooltip of the button" # optional + ) def toolfunc(self, request, obj): pass - toolfunc.label = "This will be the label of the button" # optional - toolfunc.short_description = "This will be the tooltip of the button" # optional def make_published(modeladmin, request, queryset): queryset.update(status='p') @@ -92,9 +94,17 @@ class RobotAdmin(DjangoObjectActions, admin.ModelAdmin): [admin actions]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/actions/ ### Customizing *Object Actions* +To give the action some a helpful title tooltip, -To give the action some a helpful title tooltip, add a -`short_description` attribute, similar to how admin actions work: +```python +@action(description="Increment the vote count by one") +def increment_vote(self, request, obj): + obj.votes = obj.votes + 1 + obj.save() +``` + +Alternatively, you can also add a `short_description` attribute, +similar to how admin actions work: ```python def increment_vote(self, request, obj): @@ -107,6 +117,15 @@ By default, Django Object Actions will guess what to label the button based on the name of the function. You can override this with a `label` attribute: +```python +@action(label="Vote++") +def increment_vote(self, request, obj): + obj.votes = obj.votes + 1 + obj.save() +``` + +or + ```python def increment_vote(self, request, obj): obj.votes = obj.votes + 1 @@ -119,6 +138,15 @@ by adding a Django widget style [attrs](https://docs.djangoproject.com/en/stable/ref/forms/widgets/#django.forms.Widget.attrs) attribute: +```python +@action(attrs = {'class': 'addlink'}) +def increment_vote(self, request, obj): + obj.votes = obj.votes + 1 + obj.save() +``` + +or + ```python def increment_vote(self, request, obj): obj.votes = obj.votes + 1 diff --git a/django_object_actions/tests/test_utils.py b/django_object_actions/tests/test_utils.py index d6f05f9..1360a43 100644 --- a/django_object_actions/tests/test_utils.py +++ b/django_object_actions/tests/test_utils.py @@ -144,6 +144,20 @@ def action_2(modeladmin, request, queryset): def action_3(modeladmin, request, queryset): pass + @action( + attrs={ + "class": "addlink", + } + ) + def action_4(modeladmin, request, queryset): + pass + self.assertEqual(action_1.short_description, "First action of this admin site.") self.assertEqual(action_2.allowed_permissions, ["do_action2"]) self.assertEqual(action_3.label, "Third action") + self.assertEqual( + action_4, + { + "class": "addlink", + }, + ) diff --git a/django_object_actions/utils.py b/django_object_actions/utils.py index 7d6ba5f..67d10b7 100644 --- a/django_object_actions/utils.py +++ b/django_object_actions/utils.py @@ -313,7 +313,9 @@ def decorated_function(self, request, queryset): return decorated_function -def action(function=None, *, permissions=None, description=None, label=None): +def action( + function=None, *, permissions=None, description=None, label=None, attrs=None +): """ Conveniently add attributes to an action function:: @@ -346,6 +348,8 @@ def decorator(func): func.short_description = description if label is not None: func.label = label + if attrs is not None: + func.attrs = attrs return func if function is None: diff --git a/example_project/polls/admin.py b/example_project/polls/admin.py index 47d115b..72c2e74 100644 --- a/example_project/polls/admin.py +++ b/example_project/polls/admin.py @@ -4,7 +4,11 @@ from django.http import HttpResponseRedirect from django.urls import reverse -from django_object_actions import DjangoObjectActions, takes_instance_or_queryset +from django_object_actions import ( + DjangoObjectActions, + takes_instance_or_queryset, + action, +) from .models import Choice, Poll, Comment, RelatedData @@ -15,38 +19,37 @@ class ChoiceAdmin(DjangoObjectActions, admin.ModelAdmin): # Actions ######### + @action( + description="+1", + label="vote++", + attrs={ + "test": '"foo&bar"', + "Robert": '"); DROP TABLE Students; ', # 327 + "class": "addlink", + }, + ) @takes_instance_or_queryset def increment_vote(self, request, queryset): queryset.update(votes=F("votes") + 1) - increment_vote.short_description = "+1" - increment_vote.label = "vote++" - increment_vote.attrs = { - "test": '"foo&bar"', - "Robert": '"); DROP TABLE Students; ', # 327 - "class": "addlink", - } - actions = ["increment_vote"] # Object actions ################ + @action(description="-1") def decrement_vote(self, request, obj): obj.votes -= 1 obj.save() - decrement_vote.short_description = "-1" - def delete_all(self, request, queryset): self.message_user(request, "just kidding!") + @action(description="0") def reset_vote(self, request, obj): obj.votes = 0 obj.save() - reset_vote.short_description = "0" - def edit_poll(self, request, obj): url = reverse("admin:polls_poll_change", args=(obj.poll.pk,)) return HttpResponseRedirect(url) @@ -101,6 +104,7 @@ def change_view(self, request, object_id, form_url="", extra_context=None): # Object actions ################ + @action(label="Delete All Choices") def delete_all_choices(self, request, obj): from django.shortcuts import render @@ -111,8 +115,6 @@ def delete_all_choices(self, request, obj): self.message_user(request, "All choices deleted") return render(request, "clear_choices.html", {"object": obj}) - delete_all_choices.label = "Delete All Choices" - def question_mark(self, request, obj): """Add a question mark.""" obj.question = obj.question + "?"