From 08cdaec615c00c346681f29dd74327b21369d207 Mon Sep 17 00:00:00 2001 From: sax Date: Thu, 5 Feb 2015 21:30:54 +0100 Subject: [PATCH 1/3] open 0.5 --- admin_extra_urls/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin_extra_urls/__init__.py b/admin_extra_urls/__init__.py index f0cf8a5..93b5a91 100644 --- a/admin_extra_urls/__init__.py +++ b/admin_extra_urls/__init__.py @@ -3,7 +3,7 @@ import os NAME = 'admin-extra-urls' -VERSION = __version__ = (0, 4, 0, 'final', 0) +VERSION = __version__ = (0, 5, 0, 'alpha', 0) __author__ = 'sax' From 807e136619b79855bb5234f791d64545820aade2 Mon Sep 17 00:00:00 2001 From: sax Date: Sat, 21 Mar 2015 05:10:01 +0100 Subject: [PATCH 2/3] Potentially backward incompatible: Changed the way the urls options are put in the context. add 'visible' attribute to `link()` and `action()` add new custom tag `extraurl` --- CHANGES | 8 +++ admin_extra_urls/extras.py | 59 +++++++++++-------- .../admin_extra_urls/change_form.html | 14 ++--- .../admin_extra_urls/change_list.html | 8 +-- admin_extra_urls/templatetags/extra_urls.py | 55 +++++++++++++++++ tests/conftest.py | 1 - tests/demo/admin.py | 3 +- tests/demo/settings.py | 1 - tests/demo/urls.py | 5 +- tests/test_extra_url.py | 5 +- 10 files changed, 115 insertions(+), 44 deletions(-) diff --git a/CHANGES b/CHANGES index f1269fe..ddca615 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +Release 0.5 +----------- +* Potentially backward incompatible: + Changed the way the urls options are put in the context. +* add 'visible' attribute to `link()` and `action()` +* add new custom tag `extraurl` + + Release 0.4 ----------- * add css_class attribute diff --git a/admin_extra_urls/extras.py b/admin_extra_urls/extras.py index e989def..f18dbd2 100644 --- a/admin_extra_urls/extras.py +++ b/admin_extra_urls/extras.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from collections import namedtuple import inspect import six from functools import update_wrapper @@ -12,7 +13,15 @@ def labelize(label): return label.replace('_', ' ').strip().title() -def link(path=None, label=None, icon='', permission=None, css_class="btn btn-success", order=999): +class ExtraUrlConfigException(RuntimeError): + pass + + +opts = namedtuple('UrlOptions', 'path,label,icon,perm,order,css_class,visible') + + +def link(path=None, label=None, icon='', permission=None, + css_class="btn btn-success", order=999, visible=True): """ decorator to mark ModelAdmin method as 'url' links. @@ -34,19 +43,21 @@ def _inner(self, *args, **kwargs): return HttpResponseRedirect(url) return ret - _inner.link = (path or func.__name__, - label or labelize(func.__name__), - icon, - permission, - order, - css_class) + _inner.link = opts(path or func.__name__, + label or labelize(func.__name__), + icon, + permission, + order, + css_class, + visible) return _inner return link_decorator -def action(path=None, label=None, icon='', permission=None, css_class="btn btn-success", order=999): +def action(path=None, label=None, icon='', permission=None, + css_class="btn btn-success", order=999, visible=True): """ decorator to mark ModelAdmin method as 'url' action. @@ -62,19 +73,24 @@ def action(path=None, label=None, icon='', permission=None, css_class="btn btn-s def action_decorator(func): def _inner(self, request, pk): - ret = func(self, request, pk) + try: + ret = func(self, request, pk) + except TypeError: + raise ExtraUrlConfigException("'%s()' must accept 3 arguments. " + "Did you missed 'request' and 'pk' ?" % func.__name__) if not isinstance(ret, HttpResponse): url = reverse(admin_urlname(self.model._meta, 'change'), args=[pk]) return HttpResponseRedirect(url) return ret - _inner.action = (path or func.__name__, - label or labelize(func.__name__), - icon, - permission, - order, - css_class) + _inner.action = opts(path or func.__name__, + label or labelize(func.__name__), + icon, + permission, + order, + css_class, + visible) return _inner @@ -118,21 +134,18 @@ def wrapper(*args, **kwargs): extras = [] for __, entry in extra_urls.items(): - isdetail, method_name, (path, label, icon, perm_name, order, css_class) = entry + isdetail, method_name, options = entry info[2] = method_name if isdetail: - self.extra_detail_buttons.append([method_name, label, - icon, perm_name, css_class, order]) - uri = r'^%s/(?P.*)/$' % path + self.extra_detail_buttons.append([method_name, options]) + uri = r'^%s/(?P.*)/$' % options.path else: - uri = r'^%s/$' % path - self.extra_buttons.append([method_name, label, - icon, perm_name, css_class, order]) + uri = r'^%s/$' % options.path + self.extra_buttons.append([method_name, options]) extras.append(url(uri, wrap(getattr(self, method_name)), name='{}_{}_{}'.format(*info))) - self.extra_buttons = sorted(self.extra_buttons, key=lambda d: d[-1]) self.extra_detail_buttons = sorted(self.extra_detail_buttons, key=lambda d: d[-1]) diff --git a/admin_extra_urls/templates/admin_extra_urls/change_form.html b/admin_extra_urls/templates/admin_extra_urls/change_form.html index 8e2d7ac..37d01c0 100644 --- a/admin_extra_urls/templates/admin_extra_urls/change_form.html +++ b/admin_extra_urls/templates/admin_extra_urls/change_form.html @@ -4,14 +4,14 @@
{% block object-tools-items %} {{block.super}} - {% for method_name, label, icon, perm, css_class in adminform.model_admin.extra_detail_buttons %} - {% has_permission perm as authorized %} - {% if authorized %} + {% for method_name,urlattrs in adminform.model_admin.extra_detail_buttons %} + {% has_permission urlattrs.perm as authorized %} + {% if authorized and urlattrs.visible %} {%nlless%} - - {% if icon %} {% endif %} - {{ label }} + + {% if icon %} {% endif %} + {{ urlattrs.label }} {% endnlless %} {% endif %} {% endfor %} diff --git a/admin_extra_urls/templates/admin_extra_urls/change_list.html b/admin_extra_urls/templates/admin_extra_urls/change_list.html index a93394c..47054b8 100644 --- a/admin_extra_urls/templates/admin_extra_urls/change_list.html +++ b/admin_extra_urls/templates/admin_extra_urls/change_list.html @@ -11,12 +11,12 @@ {% endif %} - {% for method_name, label, icon, perm, css_class in cl.model_admin.extra_buttons %} - {% has_permission perm as authorized %} - {% if authorized %} + {% for method_name,urlattrs in cl.model_admin.extra_buttons %} + {% has_permission urlattrs.perm as authorized %} + {% if authorized and urlattrs.visible %} {% spaceless %} {% if icon %} {% endif %}{{ label }} + class="extra-link {{urlattrs.css_class}} {{method_name}}">{% if urlattrs.icon %} {% endif %}{{ urlattrs.label }} {% endspaceless %} {% endif %} diff --git a/admin_extra_urls/templatetags/extra_urls.py b/admin_extra_urls/templatetags/extra_urls.py index 0e48d86..f5166eb 100644 --- a/admin_extra_urls/templatetags/extra_urls.py +++ b/admin_extra_urls/templatetags/extra_urls.py @@ -1,5 +1,9 @@ +from itertools import chain import re from django import template +from django.contrib.admin.templatetags.admin_urls import admin_urlname +from django.core.urlresolvers import reverse +from django.template import Node, TemplateSyntaxError register = template.Library() @@ -33,3 +37,54 @@ def nlless(parser, token): nodelist = parser.parse(('endnlless',)) parser.delete_first_token() return NewlinelessNode(nodelist) + + +class ExtraUrlNode(Node): + def __init__(self, methodname, varname, target): + self.methodname = methodname + self.varname = varname + self.object = target + + def render(self, context): + methodname = template.Variable(self.methodname).resolve(context) + target = template.Variable(self.object).resolve(context) + + model_admin = context['adminform'].model_admin + for name, options in chain(model_admin.extra_detail_buttons, model_admin.extra_buttons): + if name == methodname: + opts = options._asdict() + if target: + opts['href'] = reverse(admin_urlname(target._meta, methodname), + args=[target.pk]) + context[self.varname] = opts + return '' + raise TemplateSyntaxError("'%s' is not a valid extra url name" % target) + + +@register.tag +def extraurl(parser, token): + """ + Usages: + + {% extraurl 'transitions' as options for adminform.form.instance %} +
  • {{ options.label }} +
  • + + :param parser: + :param token: + :return: + """ + bits = token.contents.split() + target = None + if len(bits) not in [4, 6]: + raise TemplateSyntaxError("extraurl require five or six arguments. %s passed" % len(bits)) + if bits[2] != 'as': + raise TemplateSyntaxError("third argument to the extraurl tag must be 'as'") + + if len(bits) == 6: + if bits[4] != 'for': + raise TemplateSyntaxError("fifth argument to the extraurl tag must be 'for'. (not '%s'" % bits[5]) + target = bits[5] + + return ExtraUrlNode(bits[1], bits[3], target) diff --git a/tests/conftest.py b/tests/conftest.py index 79aa6fe..eacaa3d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,4 +17,3 @@ def pytest_configure(config): django.setup() except ImportError: pass - diff --git a/tests/demo/admin.py b/tests/demo/admin.py index ae8301f..c97788f 100644 --- a/tests/demo/admin.py +++ b/tests/demo/admin.py @@ -23,9 +23,8 @@ def custom_path(self, request): def no_response(self, request): self.message_user(request, 'No_response') -class Admin2(ExtraUrlMixin, admin.ModelAdmin): - +class Admin2(ExtraUrlMixin, admin.ModelAdmin): @action() def update(self, request, pk): opts = self.model._meta diff --git a/tests/demo/settings.py b/tests/demo/settings.py index 15a5ac5..48cdf1b 100644 --- a/tests/demo/settings.py +++ b/tests/demo/settings.py @@ -1,4 +1,3 @@ - DEBUG = True STATIC_URL = '/static/' diff --git a/tests/demo/urls.py b/tests/demo/urls.py index f7c7688..a3d2a2d 100644 --- a/tests/demo/urls.py +++ b/tests/demo/urls.py @@ -1,10 +1,9 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import patterns, include from django.contrib import admin - admin.autodiscover() urlpatterns = patterns('', (r'^admin/', include(include(admin.site.urls))), -) + ) diff --git a/tests/test_extra_url.py b/tests/test_extra_url.py index d446577..b4a159e 100644 --- a/tests/test_extra_url.py +++ b/tests/test_extra_url.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import logging from django.core.urlresolvers import reverse -from django.utils.translation import gettext as _ from django_dynamic_fixture import G import django_webtest import pytest @@ -46,11 +45,11 @@ def test_link_custom_path_reverse(app, admin_user): def test_action(app, demomodel2, admin_user): url = reverse('admin:demo_demomodel2_change', args=[demomodel2.pk]) res = app.get(url, user=admin_user) - res = res.click(r' Update', index=1).follow() + res = res.click(r'Update', index=1).follow() assert str(res.context['messages']._loaded_messages[0].message) == 'action called' -def test_default_httpresponseaction(app, admin_user): +def test_default_httpresponseaction(app, admin_user): url = reverse('admin:demo_demomodel1_changelist') res = app.get(url, user=admin_user) res = res.click('No Response').follow() From b90ec61196171a4ae1af341162b3439b62980b16 Mon Sep 17 00:00:00 2001 From: sax Date: Sat, 21 Mar 2015 05:11:37 +0100 Subject: [PATCH 3/3] bump 0.5 --- admin_extra_urls/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin_extra_urls/__init__.py b/admin_extra_urls/__init__.py index 93b5a91..3a887ff 100644 --- a/admin_extra_urls/__init__.py +++ b/admin_extra_urls/__init__.py @@ -3,7 +3,7 @@ import os NAME = 'admin-extra-urls' -VERSION = __version__ = (0, 5, 0, 'alpha', 0) +VERSION = __version__ = (0, 5, 0, 'final', 0) __author__ = 'sax'