Skip to content
This repository has been archived by the owner on Jul 29, 2022. It is now read-only.

Commit

Permalink
Merge branch 'release/0.5'
Browse files Browse the repository at this point in the history
* release/0.5:
  bump 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`
  open 0.5
  • Loading branch information
saxix committed Mar 21, 2015
2 parents 2ff52fd + b90ec61 commit ceceed3
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 45 deletions.
8 changes: 8 additions & 0 deletions 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
Expand Down
2 changes: 1 addition & 1 deletion admin_extra_urls/__init__.py
Expand Up @@ -3,7 +3,7 @@
import os

NAME = 'admin-extra-urls'
VERSION = __version__ = (0, 4, 0, 'final', 0)
VERSION = __version__ = (0, 5, 0, 'final', 0)
__author__ = 'sax'


Expand Down
59 changes: 36 additions & 23 deletions 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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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<pk>.*)/$' % path
self.extra_detail_buttons.append([method_name, options])
uri = r'^%s/(?P<pk>.*)/$' % 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])

Expand Down
14 changes: 7 additions & 7 deletions admin_extra_urls/templates/admin_extra_urls/change_form.html
Expand Up @@ -4,14 +4,14 @@
<div class="object-tools">
{% 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%}
<a id="btn-{{method_name}}" href="{% url opts|admin_urlname:method_name original.pk %}{% if is_popup %}?_popup=1{% endif %}"
class="extra-link {{css_class}} {{method_name}}">
{% if icon %}<i class="{{ icon }}"></i>&nbsp;{% endif %}
{{ label }}</a>
<a id="btn-{{method_name}}" href="{% url opts|admin_urlname:method_name opts.original.pk %}{% if is_popup %}?_popup=1{% endif %}"
class="extra-link {{urlattrs.css_class}} {{urlattrs.method_name}}">
{% if icon %}<i class="{{ urlattrs.icon }}"></i>&nbsp;{% endif %}
{{ urlattrs.label }}</a>
{% endnlless %}
{% endif %}
{% endfor %}
Expand Down
8 changes: 4 additions & 4 deletions admin_extra_urls/templates/admin_extra_urls/change_list.html
Expand Up @@ -11,12 +11,12 @@
</a>
{% 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 %}
<a id="btn-{{method_name}}" href="{% url cl.opts|admin_urlname:method_name %}{% if is_popup %}?_popup=1{% endif %}"
class="extra-link {{css_class}} {{method_name}}">{% if icon %}<i class="{{ icon }} icon-white"></i>&nbsp;{% endif %}{{ label }}
class="extra-link {{urlattrs.css_class}} {{method_name}}">{% if urlattrs.icon %}<i class="{{ urlattrs.icon }} icon-white"></i>&nbsp;{% endif %}{{ urlattrs.label }}
</a>
{% endspaceless %}
{% endif %}
Expand Down
55 changes: 55 additions & 0 deletions 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()

Expand Down Expand Up @@ -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 %}
<li><a href="{{ options.href }}" class="{{ options.css_class }}"><i
class="{{ options.icon }} icon-alpha75"></i>{{ options.label }}</a>
</li>
: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)
1 change: 0 additions & 1 deletion tests/conftest.py
Expand Up @@ -17,4 +17,3 @@ def pytest_configure(config):
django.setup()
except ImportError:
pass

3 changes: 1 addition & 2 deletions tests/demo/admin.py
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion tests/demo/settings.py
@@ -1,4 +1,3 @@

DEBUG = True
STATIC_URL = '/static/'

Expand Down
5 changes: 2 additions & 3 deletions 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))),
)
)
5 changes: 2 additions & 3 deletions 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
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit ceceed3

Please sign in to comment.