Skip to content

Commit

Permalink
Issue edoburu#1: Generate toolbar with template rather than hard-code…
Browse files Browse the repository at this point in the history
…d HTML.

Allows overriding the template if using different frameworks.
New dependency on `django-app-settings` to permit easy testing.
  • Loading branch information
Caroline Nadel committed Nov 12, 2014
1 parent c759ee8 commit d041cec
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 121 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
*.mo
*.db
*.egg-info/
*.egg/
*.egg
.coverage
.project
.ropeproject
.idea/
.pydevproject
.idea/workspace.xml
Expand All @@ -15,3 +16,6 @@
dist/
docs/_build/
htmlcov/
*.swo
*.swp
test_staff_toolbar
51 changes: 40 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ This package has been tested in Django 1.5 and 1.6.
:alt: django-staff-toolbar preview


Requirements
============

Due to needing to test multiple settings, [django-app-settings](https://github.com/mwana/django-app-settings) is required.
Make sure to install this before continuing.


Installation
============

First install the module, preferably in a virtual environment::

git clone https://github.com/edoburu/django-staff-toolbar.git
cd django-staff-toolbar
pip install .
pip install git+git://github.com/edoburu/django-staff-toolbar.git


Configuration
Expand All @@ -42,20 +47,36 @@ Add the HTML widget to the template::

{% load staff_toolbar_tags %}

{% staff_toolbar %}
{% render_staff_toolbar %}

Make sure the layout is loaded in the template::

<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}staff_toolbar/staff_toolbar.css" />

Layout
------

By default, a simple layout is included.
You can change this layout to your own liking.
There is a default stylesheet for the staff toolbar. The SASS is included in this repo to enable you to customise::

<link rel="stylesheet" type="text/css" href="{% static "staff_toolbar/staff_toolbar.css" %}" />

To customise the HTML of the toolbar, override "staff_toolbar/toolbar.html".
The default is::

The source SASS file is included, making it easier to
integrate this into your project stylesheets when needed.
{% load staff_toolbar_tags %}
<div id="django-staff-toolbar">
<ul>
{% staff_toolbar %}
<li>
{{ item }}
{% if children %}
<ul>
{{ children }}
</ul>
{% endif %}
</li>
{% end_staff_toolbar %}
</ul>
</div>

`children` is a special template variable which recurses over the nested items. The HTML between `{% staff_toolbar %}` and `{% end_staff_toolbar %}` will be used to display the children.


Customizing the admin URL
Expand All @@ -69,6 +90,7 @@ The admin URL is auto-detected using:
In some cases, this is not sufficient. When the auto-detected "Change object"
link does not point to the right page, this can be resolved using two methods:


Using the view
--------------

Expand All @@ -77,6 +99,7 @@ that information will be used to render the proper "Change object" link.

This requires Django 1.5, which exports the ``view`` variable to the template.


Using the template
------------------

Expand Down Expand Up @@ -159,3 +182,9 @@ This module is designed to be generic, and easy to plug into your site.
Pull requests and improvements are welcome!

If you have any other valuable contribution, suggestion or idea, please let us know as well!


Testing
=======

To run the tests, `python setup.py test`. This will automatically install `BeautifulSoup` and `mock`.
16 changes: 14 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@
from setuptools import setup, find_packages
from os import path
import codecs
import os
import re
import os
import sys


# When creating the sdist, make sure the django.mo file also exists:
if 'sdist' in sys.argv or 'develop' in sys.argv:
try:
os.chdir('staff_toolbar')
from django.core.management.commands.compilemessages import compile_messages
compile_messages(sys.stderr)
finally:
os.chdir('..')


def read(*parts):
file_path = path.join(path.dirname(__file__), *parts)
return codecs.open(file_path, encoding='utf-8').read()
Expand Down Expand Up @@ -56,5 +66,7 @@ def find_version(*parts):
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules',
]
],
tests_require=['mock', 'beautifulsoup4'],
test_suite='tests',
)
4 changes: 2 additions & 2 deletions staff_toolbar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ def __init__(self, import_path, *args, **kwargs):
self.kwargs = kwargs
self.real_instance = None

def __call__(self, request, context):
def __call__(self, context):
if self.real_instance is None:
# Init on demand.
from staff_toolbar.loading import load_toolbar_item
self.real_instance = load_toolbar_item(self.import_path, *self.args, **self.kwargs)

return self.real_instance(request, context)
return self.real_instance(context)
63 changes: 11 additions & 52 deletions staff_toolbar/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@
These are the items that can be added to the staff toolbar.
"""
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.html import format_html

__all__ = (
'Group',
'RootNode',
'Link',
'AdminLink',
'ObjectNode',
)


Expand All @@ -25,7 +21,7 @@ class Title(object):
def __init__(self, title):
self.title = title

def __call__(self, request, context):
def __call__(self, context):
return format_html(u'<div class="toolbar-title">{0}</div>', self.title)


Expand All @@ -37,7 +33,7 @@ class Literal(object):
def __init__(self, title):
self.title = title

def __call__(self, request, context):
def __call__(self, context):
return self.title


Expand All @@ -51,44 +47,8 @@ def __init__(self, *children, **kwargs):
self.children = list(children)
self.title = kwargs.get('title', self.title)

def __call__(self, request, context):
"""
Render the group
"""
rows = self.get_rows(request, context)
return self.render(rows)

def get_rows(self, request, context):
"""
Get all rows as HTML
"""
from staff_toolbar.loading import load_toolbar_item
rows = []
for i, hook in enumerate(self.children):
# Allow dotted paths in groups too, loads on demand (get import errors otherwise).
if isinstance(hook, (basestring, tuple, list)):
hook = load_toolbar_item(hook)
self.children[i] = hook

html = hook(request, context)
if not html:
continue

rows.append(html)
return rows

def render(self, rows):
"""
Join the HTML rows.
"""
if not rows:
return ''

li_tags = mark_safe(u"\n".join(format_html(u'<li>{0}</li>', force_text(row)) for row in rows))
if self.title:
return format_html(u'<div class="toolbar-title">{0}</div>\n<ul>\n{1}\n</ul>', self.title, li_tags)
else:
return format_html(u'<ul>\n{0}\n</ul>', li_tags)
def __call__(self, context):
return ''


class RootNode(Group):
Expand All @@ -98,7 +58,6 @@ class RootNode(Group):
title = _("Staff features")



class Link(object):
"""
Add a hard-coded link in the toolbar.
Expand All @@ -112,14 +71,14 @@ def __init__(self, url=None, title=None):
self.url = url or self.url
self.title = title or self.title

def __call__(self, request, context):
linkdata = self.get_link(request, context)
def __call__(self, context):
linkdata = self.get_link(context)
if linkdata:
return format_html(u'<a href="{0}">{1}</a>', *linkdata)
else:
return None

def get_link(self, request, context):
def get_link(self, context):
return (self.url, self.title)


Expand All @@ -143,8 +102,9 @@ class ChangeObjectLink(Link):
* ``view.get_staff_object()`
* ``view.get_staff_url()`
"""
def get_link(self, request, context):
# When `set_staff_object` is used, take that information,
def get_link(self, context):
request = context['request']
# When `staff_object` is used, take that information,
object = getattr(request, 'staff_object', None)
url = getattr(request, 'staff_url', None)

Expand Down Expand Up @@ -184,7 +144,6 @@ class LogoutLink(Link):
title = _("Logout")



def get_object(context):
"""
Get an object from the context or view.
Expand Down
55 changes: 20 additions & 35 deletions staff_toolbar/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import traceback
import sys
from django.core.exceptions import ImproperlyConfigured
from staff_toolbar import appsettings
from staff_toolbar.items import RootNode, Group

try:
Expand All @@ -21,18 +20,27 @@
'load_toolbar_item'
)


def get_toolbar_root():
def import_symbol(import_path, setting_name):
"""
Init on demand.
:rtype RootNode:
Import a class or function by name.
"""
global _toolbar_root
if _toolbar_root is None:
items = [load_toolbar_item(item) for item in appsettings.STAFF_TOOLBAR_ITEMS]
_toolbar_root = RootNode(*items)
return _toolbar_root
mod_name, class_name = import_path.rsplit('.', 1)

# import module
try:
mod = import_module(mod_name)
cls = getattr(mod, class_name)
except ImportError, e:
__, __, exc_traceback = sys.exc_info()
frames = traceback.extract_tb(exc_traceback)
if len(frames) > 1:
raise # import error is a level deeper.

raise ImproperlyConfigured("{0} does not point to an existing class: {1}".format(setting_name, import_path))
except AttributeError:
raise ImproperlyConfigured("{0} does not point to an existing class: {1}".format(setting_name, import_path))

return cls

def load_toolbar_item(import_path, *args, **kwargs):
"""
Expand All @@ -42,10 +50,9 @@ def load_toolbar_item(import_path, *args, **kwargs):
:param kwargs: For classes, any keyword arguments to pass to the constructor.
"""
if isinstance(import_path, (tuple, list)):
children = [load_toolbar_item(path) for path in import_path]
return Group(*children)
return Group(*import_path)
elif isinstance(import_path, basestring):
symbol = _import_symbol(import_path, 'STAFF_TOOLBAR_ITEMS')
symbol = import_symbol(import_path, 'STAFF_TOOLBAR_ITEMS')
else:
symbol = import_path

Expand All @@ -58,25 +65,3 @@ def load_toolbar_item(import_path, *args, **kwargs):

return symbol


def _import_symbol(import_path, setting_name):
"""
Import a class or function by name.
"""
mod_name, class_name = import_path.rsplit('.', 1)

# import module
try:
mod = import_module(mod_name)
cls = getattr(mod, class_name)
except ImportError, e:
__, __, exc_traceback = sys.exc_info()
frames = traceback.extract_tb(exc_traceback)
if len(frames) > 1:
raise # import error is a level deeper.

raise ImproperlyConfigured("{0} does not point to an existing class: {1}".format(setting_name, import_path))
except AttributeError:
raise ImproperlyConfigured("{0} does not point to an existing class: {1}".format(setting_name, import_path))

return cls
6 changes: 2 additions & 4 deletions staff_toolbar/appsettings.py → staff_toolbar/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from django.conf import settings

STAFF_TOOLBAR_ITEMS = getattr(settings, 'STAFF_TOOLBAR_ITEMS', (
STAFF_TOOLBAR_ITEMS = (
'staff_toolbar.items.AdminIndexLink',
'staff_toolbar.items.ChangeObjectLink',
'staff_toolbar.items.LogoutLink',
))
)
15 changes: 15 additions & 0 deletions staff_toolbar/templates/staff_toolbar/toolbar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% load staff_toolbar_tags %}
<div id="django-staff-toolbar">
<ul>
{% staff_toolbar %}
<li>
{{ item }}
{% if children %}
<ul>
{{ children }}
</ul>
{% endif %}
</li>
{% end_staff_toolbar %}
</ul>
</div>
Loading

0 comments on commit d041cec

Please sign in to comment.