Commits

David Jean Louis committed 56db8ff

added a "bookmarks" menu item and the code to manage bookmarks (fixes issue #10)

  • Participants
  • Parent commits 180312f

Comments (0)

Files changed (10)

admin_tools/dashboard/templates/dashboard/dashboard.html

 {% load i18n admin_tools_dashboard_tags %}
-
 <script type="text/javascript" charset="utf-8">
-    // When jQuery is sourced, it's going to overwrite whatever might be in the
-    // '$' variable, so store a reference of it in a temporary variable...
-    var _$ = window.$;
     // load jquery if it's not loaded yet
     if (typeof jQuery == 'undefined') {
-        var jquery_url = '{{ media_url }}/admin_tools/js/jquery/jquery-1.4.1.min.js';
-        document.write(unescape('%3Cscript src="' + jquery_url + '" type="text/javascript"%3E%3C/script%3E'));
+        var script_url = '{{ media_url }}/admin_tools/js/jquery/jquery-1.4.1.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
     }
 </script>
 <script type="text/javascript" charset="utf-8">
     // load jquery ui if it's not loaded yet
     if (typeof jQuery.ui == 'undefined') {
-        var jquery_ui_url = '{{ media_url }}/admin_tools/js/jquery/jquery-ui-1.8rc1.custom.min.js';
-        document.write(unescape('%3Cscript src="' + jquery_ui_url + '" type="text/javascript"%3E%3C/script%3E'));
+        var script_url = '{{ media_url }}/admin_tools/js/jquery/jquery-ui-1.8rc1.custom.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
     }
 </script>
-<script type="text/javascript" src="{{ media_url }}/admin_tools/js/json.min.js"></script>
-<script type="text/javascript" src="{{ media_url }}/admin_tools/js/jquery/jquery.cookie.min.js"></script>
+<script type="text/javascript" charset="utf-8">
+    // load json if it's not loaded yet
+    if (typeof JSON == 'undefined') {
+        var script_url = '{{ media_url }}/admin_tools/js/json.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
+    }
+</script>
+<script type="text/javascript" charset="utf-8">
+    // load jquery cookie if it's not loaded yet
+    if (typeof jQuery.cookie == 'undefined') {
+        var script_url = '{{ media_url }}/admin_tools/js/jquery/jquery.cookie.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
+    }
+</script>
 <script type="text/javascript" src="{{ media_url }}/admin_tools/js/jquery/jquery.dashboard.js"></script>
 {% if dashboard.Media.js %}
 {% for js in dashboard.Media.js %}

admin_tools/media/admin_tools/css/dashboard.css

 
 .warning {
     color: red;
-    background: transparent url(../images/admin-tools.png) 0 -417px no-repeat;
+    background: transparent url(../images/admin-tools.png) 0 -412px no-repeat;
     padding-left: 25px;
 }
 

admin_tools/media/admin_tools/css/menu.css

 /* menu styles */
 
-
 #header {
     overflow: visible;
 }
     list-style: none;
 }
 
+#header ul li a {
+    white-space: nowrap;
+}
+
 #header #navigation-menu {
-    background: transparent url(../images/admin-tools.png) 0 -205px repeat-x;
     height: 35px;
     z-index: 9999;
+    background: transparent url(../images/admin-tools.png) repeat-x 0 -205px;
+}
+
+#header #bookmark-button {
+    position: absolute;
+    top: auto;
+    right: 15px;
+    height: 25px;
+    width: 24px;
+    margin-top: 4px;
+    background: transparent url(../images/admin-tools.png) no-repeat 0 -425px;
+}
+
+#header #bookmark-button.bookmarked {
+    background-position: 0 -445px;
 }
 
 #header ul#navigation-menu li {
     color: #555;
 }
 
+#header ul#navigation-menu li.disabled a {
+    color: #bbb;
+    cursor: default;
+}
 
 #header ul#navigation-menu li.first a {
-    margin-left: 12px;
-    background: transparent url(../images/admin-tools.png) 0 -380px no-repeat;
+    margin: 5px 0 0 12px;
+    padding-top: 4px;
     text-indent: 14px;
+    background: transparent url(../images/admin-tools.png) no-repeat 0 -385px;
 }
 
 #header ul#navigation-menu li span {
 #header ul#navigation-menu li ul li ul li.over {
     background: transparent url(../images/admin-tools.png) repeat-x 0 -245px;
 }
+
+#header ul#navigation-menu li.disabled:hover,
+#header ul#navigation-menu li.disabled .hover {
+    background: none;
+}

admin_tools/media/admin_tools/images/admin-tools.png

Old
Old image
New
New image

admin_tools/media/admin_tools/js/menu.js

+/**
+ * Save/remove bookmarks to/from the bookmark menu item and the cookie.
+ *
+ * @param string url        The current page url path (request.get_full_path)
+ * @param string title      The current page title
+ * @param string prompt_msg The message to ask for prompting
+ * @return void
+ */
+var process_bookmarks = function(url, title, prompt_msg) {
+    var json_str = $.cookie('menu.bookmarks');
+    var bookmarks = json_str ? JSON.parse(json_str) : [];
+    $('#bookmark-button').click(function() {
+        if ($(this).hasClass('bookmarked')) {
+            $(this).removeClass('bookmarked');
+            $('#navigation-menu li.bookmark ul li a[href="' + url + '"]').parent().remove();
+            if (!$('#navigation-menu li.bookmark ul li').length) {
+                $('#navigation-menu li.bookmark ul').remove();
+                $('#navigation-menu li.bookmark a span').remove();
+                $('#navigation-menu li.bookmark').addClass('disabled');
+            }
+            for (var i=0; i < bookmarks.length; i++) {
+                if (bookmarks[i].url == url) {
+                    bookmarks.splice(i, 1);
+                    $.cookie('menu.bookmarks', JSON.stringify(bookmarks), {
+                        expires: 1825,
+                        path: '/admin/' // harcode path to have a unique cookie across pages
+                    });
+                    break;
+                }
+            }
+        } else {
+            title = prompt(prompt_msg, title);
+            if (!title) {
+                return;
+            }
+            $(this).addClass('bookmarked');
+            if (!$('#navigation-menu li.bookmark ul').length) {
+                $('#navigation-menu li.bookmark a').prepend('<span class="icon"/>');
+                $('#navigation-menu li.bookmark').append('<ul/>');
+            }
+            $('#navigation-menu li.bookmark ul').append(
+                '<li><a href="' + url + '">' + title + '</a></li>'
+            );
+            $('#navigation-menu li.bookmark').removeClass('disabled');
+            var already_bookmarked = false;
+            for (var i=0; i < bookmarks.length; i++) {
+                if (bookmarks[i].url == url) {
+                    already_bookmarked = true;
+                }
+            }
+            if (!already_bookmarked) {
+                bookmarks.push({url: url, title: title});
+                $.cookie('menu.bookmarks', JSON.stringify(bookmarks), {
+                    expires: 1825,
+                    path: '/admin/' // harcode path to have a unique cookie across pages
+                });
+            }
+        }
+    });
+};

admin_tools/menu/models.py

 
 from django.contrib import admin
 from django.core.urlresolvers import reverse
+from django.utils.safestring import mark_safe
 from django.utils.text import capfirst
 from django.utils.translation import ugettext_lazy as _
 from admin_tools.utils import AppListElementMixin
+from admin_tools.menu.utils import get_menu_bookmarks
 
 
 class Menu(object):
         access to all context variables and to the ``django.http.HttpRequest``.
         """
         pass
-
+    
 
 class MenuItem(object):
     """
         An optional string that will be used as the ``title`` attribute of 
         the menu-item ``a`` tag. Default value: None.
 
+    ``enabled``
+        Boolean that determines whether the menu item is enabled or not.
+        Disabled items are displayed but are not clickable.
+        Default value: True.
+
     ``template``
         The template to use to render the menu item.
         Default value: 'menu/item.html'.
         self.css_classes = kwargs.get('css_classes', [])
         self.accesskey = kwargs.get('accesskey')
         self.description = kwargs.get('description')
+        self.enabled = kwargs.get('enabled', True)
         self.template = kwargs.get('template', 'menu/item.html')
         self.children = kwargs.get('children', [])
 
             self.children.append(item)
 
 
+class BookmarkMenuItem(MenuItem, AppListElementMixin):
+    """
+    A menu item that lists pages bookmarked by the user. This menu item also 
+    adds an extra button to the menu that allows the user to bookmark or
+    un-bookmark the current page.
+
+    Here's a small example of adding a bookmark menu item::
+ 
+        from admin_tools.menu.models import *
+         
+        class MyMenu(Menu):
+            def __init__(self, **kwargs):
+                super(MyMenu, self).__init__(**kwargs)
+                self.children.append(BookmarkMenuItem(title='My bookmarks'))
+
+    The screenshot of what this code produces:
+
+    .. image:: images/bookmark_menu_item.png
+    """
+
+    def __init__(self, **kwargs):
+        super(BookmarkMenuItem, self).__init__(**kwargs)
+        self.title = kwargs.get('title', _('Bookmarks'))
+
+    def init_with_context(self, context):
+        """
+        Please refer to the ``MenuItem::init_with_context()`` documentation.
+        """
+        try:
+            bookmarks = get_menu_bookmarks(context['request'])
+        except Exception, exc:
+            warning_item = MenuItem(
+                title='Bookmark menu item requires the simplejson module'
+            )
+            warning_item.css_classes.append('warning')
+            self.children.append(warning_item)
+            return
+
+        for b in bookmarks:
+            self.children.append(MenuItem(
+                url=b['url'],
+                title=mark_safe(b['title'])
+            ))
+        if not len(self.children):
+            self.enabled = False
+        if 'bookmark' not in self.css_classes:
+            self.css_classes.append('bookmark')
+
+
 class DefaultMenu(Menu):
     """
     The default menu displayed by django-admin-tools.
             title=_('Dashboard'),
             url=reverse('admin:index')
         ))
+        self.children.append(BookmarkMenuItem())
         self.children.append(AppListMenuItem(
             title=_('Applications'),
             exclude_list=('django.contrib',)

admin_tools/menu/templates/menu/item.html

 {% load admin_tools_menu_tags %}
 {% spaceless %}
-<li class="menu-item{% ifequal index 1 %} first{% endifequal %}{% if item.css_classes %} {{ item.css_classes|join:' ' }}{% endif %}">
-    <a href="{{ item.url }}"{% if item.description %} title="{{ item.description }}"{% endif %}{% if item.accesskey %} accesskey="{{ item.accesskey }}"{% endif %}>{% if item.children %}<span class="icon"></span>{% endif %}{{ item.title }}</a>
+<li class="menu-item{% ifequal index 1 %} first{% endifequal %}{% if not item.enabled %} disabled{% endif %}{% if item.css_classes %} {{ item.css_classes|join:' ' }}{% endif %}">
+    <a{% if item.url and item.enabled %} href="{{ item.url }}"{% endif %}{% if item.description %} title="{{ item.description }}"{% endif %}{% if item.accesskey %} accesskey="{{ item.accesskey }}"{% endif %}>{% if item.children %}<span class="icon"></span>{% endif %}{{ item.title }}</a>
     {% if item.children %}
     <ul>
         {% for child_item in item.children %}

admin_tools/menu/templates/menu/menu.html

-{% load admin_tools_menu_tags %}
+{% load i18n admin_tools_menu_tags %}
 {% if menu.children %}
+<script type="text/javascript" charset="utf-8">
+    // load jquery if it's not loaded yet
+    if (typeof jQuery == 'undefined') {
+        var jquery_url = '{{ media_url }}/admin_tools/js/jquery/jquery-1.4.1.min.js';
+        document.write(unescape('%3Cscript src="' + jquery_url + '" type="text/javascript"%3E%3C/script%3E'));
+    }
+</script>
+<script type="text/javascript" charset="utf-8">
+    // load json if it's not loaded yet
+    if (typeof JSON == 'undefined') {
+        var script_url = '{{ media_url }}/admin_tools/js/json.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
+    }
+</script>
+<script type="text/javascript" charset="utf-8">
+    // load jquery cookie if it's not loaded yet
+    if (typeof jQuery.cookie == 'undefined') {
+        var script_url = '{{ media_url }}/admin_tools/js/jquery/jquery.cookie.min.js';
+        document.write(unescape('%3Cscript src="' + script_url + '" type="text/javascript"%3E%3C/script%3E'));
+    }
+</script>
+<script type="text/javascript" charset="utf-8" src="{{ media_url }}/admin_tools/js/menu.js"></script>
 <!--[if IE 6]>
 <script type="text/javascript">
-var hover_ie6 = function() {
-    var children = document.getElementById('navigation-menu').getElementsByTagName('li');
-    for (i=0; i<children.length; i++) {
-        children[i].onmouseover = function() { this.className += ' over'; };
-        children[i].onmouseout = function() { this.className = this.className.replace(new RegExp(' over\\b'), ''); };
-    }
-}
-window.onload = hover_ie6;
+$(document).ready(function() {
+    $('#navigation-menu').children('li').hover(
+        function() { $(this).addClass('over'); },
+        function() { $(this).removeClass('over'); }
+    });
+});
 </script>
 <![endif]-->
-
+{% if has_bookmark_item %}
+<script type="text/javascript">
+$(document).ready(function() {
+    process_bookmarks("{{ request.get_full_path }}", "{{ title }}", "{% trans 'Please enter a name for the bookmark' %}");
+});
+</script>
+<a id="bookmark-button" href="#" title="{% trans "Bookmark this page" %}"{% if page_is_bookmarked %} class="bookmarked"{% endif %}></a>
+{% endif %}
 <ul id="navigation-menu">
     {% for item in menu.children %}{% admin_tools_render_menu_item item forloop.counter %}{% endfor %}
 </ul>

admin_tools/menu/templatetags/admin_tools_menu_tags.py

 from django import template
 from django.conf import settings
 from django.http import HttpRequest
-from admin_tools.menu.utils import get_admin_menu
+from admin_tools.menu.models import BookmarkMenuItem
+from admin_tools.menu.utils import get_admin_menu, get_menu_bookmarks
 
 register = template.Library()
 tag_func = register.inclusion_tag('menu/dummy.html', takes_context=True)
         menu = get_admin_menu()
 
     menu.init_with_context(context)
+    has_bookmark_item = False
+    page_is_bookmarked = False
+    if len([c for c in menu.children if isinstance(c, BookmarkMenuItem)]) > 0:
+        has_bookmark_item = True
+        try:
+            request = context['request']
+            page_is_bookmarked = len([b for b in get_menu_bookmarks(request) \
+                if b['url'] == request.get_full_path()]) > 0
+        except Exception, exc:
+            pass
 
     context.update({
         'template': menu.template,
         'menu': menu,
         'media_url': settings.MEDIA_URL.rstrip('/'),
+        'has_bookmark_item': has_bookmark_item,
+        'page_is_bookmarked': page_is_bookmarked,
     })
     return context
 admin_tools_render_menu = tag_func(admin_tools_render_menu)

admin_tools/menu/utils.py

             'cannot be imported: %s' % exc.message
         ))
     return getattr(mod, inst)()
+
+
+def get_menu_bookmarks(request):
+    """
+    Returns the bookmarked items or raise an exception.
+    """
+    import urllib
+    try:
+        import json
+    except ImportError:
+        # python < 2.6 
+        import simplejson as json
+    json_str = urllib.unquote(request.COOKIES.get('menu.bookmarks'))
+    if json_str is not None:
+        return json.loads(json_str)
+    return []