From 24662e903fc0a61299e35d323e46eb2fe11824c6 Mon Sep 17 00:00:00 2001 From: David Jean Louis Date: Thu, 4 Feb 2010 14:15:56 +0100 Subject: [PATCH 001/378] initial import --- .hgignore | 11 + AUTHORS | 5 + CHANGELOG | 10 + INSTALL | 22 + LICENSE | 19 + MANIFEST.in | 3 + README | 19 + admin_tools/__init__.py | 1 + admin_tools/dashboard/__init__.py | 0 admin_tools/dashboard/models.py | 336 ++++++++++++++++ .../dashboard/templates/admin/app_index.html | 18 + .../dashboard/templates/admin/index.html | 20 + .../templates/dashboard/dashboard.html | 34 ++ .../dashboard/templates/dashboard/dummy.html | 1 + .../dashboard/templates/dashboard/module.html | 20 + .../templates/dashboard/modules/app_list.html | 22 + .../templates/dashboard/modules/feed.html | 13 + .../dashboard/modules/link_list.html | 15 + .../dashboard/modules/model_list.html | 19 + .../dashboard/modules/recent_actions.html | 18 + .../dashboard/templatetags/__init__.py | 0 .../dashboard/templatetags/dashboard_tags.py | 79 ++++ admin_tools/dashboard/tests.py | 23 ++ admin_tools/dashboard/utils.py | 133 +++++++ admin_tools/dashboard/views.py | 1 + .../media/admin_tools/css/dashboard.css | 209 ++++++++++ admin_tools/media/admin_tools/css/menu.css | 198 +++++++++ admin_tools/media/admin_tools/css/theming.css | 36 ++ .../media/admin_tools/images/admin.png | Bin 0 -> 23314 bytes .../media/admin_tools/images/dashboard.png | Bin 0 -> 2310 bytes .../media/admin_tools/images/django.png | Bin 0 -> 2947 bytes admin_tools/media/admin_tools/images/menu.png | Bin 0 -> 3262 bytes .../admin_tools/js/jquery/jquery-1.4.1.min.js | 152 +++++++ .../js/jquery/jquery-ui-1.8rc1.custom.min.js | 375 ++++++++++++++++++ .../js/jquery/jquery.cookie.min.js | 7 + .../admin_tools/js/jquery/jquery.dashboard.js | 201 ++++++++++ admin_tools/media/admin_tools/js/json.min.js | 29 ++ admin_tools/menu/__init__.py | 0 admin_tools/menu/models.py | 133 +++++++ .../menu/templates/admin/base_site.html | 16 + admin_tools/menu/templates/menu/dummy.html | 1 + admin_tools/menu/templates/menu/item.html | 13 + admin_tools/menu/templates/menu/menu.html | 8 + admin_tools/menu/templatetags/__init__.py | 0 admin_tools/menu/templatetags/menu_tags.py | 76 ++++ admin_tools/menu/tests.py | 23 ++ admin_tools/menu/utils.py | 35 ++ admin_tools/menu/views.py | 1 + admin_tools/models.py | 1 + admin_tools/theming/__init__.py | 0 admin_tools/theming/models.py | 3 + admin_tools/theming/tests.py | 23 ++ admin_tools/theming/views.py | 1 + admin_tools/utils.py | 84 ++++ admin_tools/views.py | 1 + docs/Makefile | 89 +++++ docs/conf.py | 194 +++++++++ docs/customization.rst | 26 ++ docs/index.rst | 26 ++ docs/installation.rst | 110 +++++ docs/make.bat | 113 ++++++ docs/quickstart.rst | 110 +++++ setup.py | 59 +++ 63 files changed, 3195 insertions(+) create mode 100644 .hgignore create mode 100644 AUTHORS create mode 100644 CHANGELOG create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README create mode 100644 admin_tools/__init__.py create mode 100644 admin_tools/dashboard/__init__.py create mode 100644 admin_tools/dashboard/models.py create mode 100644 admin_tools/dashboard/templates/admin/app_index.html create mode 100644 admin_tools/dashboard/templates/admin/index.html create mode 100644 admin_tools/dashboard/templates/dashboard/dashboard.html create mode 100644 admin_tools/dashboard/templates/dashboard/dummy.html create mode 100644 admin_tools/dashboard/templates/dashboard/module.html create mode 100644 admin_tools/dashboard/templates/dashboard/modules/app_list.html create mode 100644 admin_tools/dashboard/templates/dashboard/modules/feed.html create mode 100644 admin_tools/dashboard/templates/dashboard/modules/link_list.html create mode 100644 admin_tools/dashboard/templates/dashboard/modules/model_list.html create mode 100644 admin_tools/dashboard/templates/dashboard/modules/recent_actions.html create mode 100644 admin_tools/dashboard/templatetags/__init__.py create mode 100644 admin_tools/dashboard/templatetags/dashboard_tags.py create mode 100644 admin_tools/dashboard/tests.py create mode 100644 admin_tools/dashboard/utils.py create mode 100644 admin_tools/dashboard/views.py create mode 100644 admin_tools/media/admin_tools/css/dashboard.css create mode 100644 admin_tools/media/admin_tools/css/menu.css create mode 100644 admin_tools/media/admin_tools/css/theming.css create mode 100644 admin_tools/media/admin_tools/images/admin.png create mode 100644 admin_tools/media/admin_tools/images/dashboard.png create mode 100644 admin_tools/media/admin_tools/images/django.png create mode 100644 admin_tools/media/admin_tools/images/menu.png create mode 100644 admin_tools/media/admin_tools/js/jquery/jquery-1.4.1.min.js create mode 100644 admin_tools/media/admin_tools/js/jquery/jquery-ui-1.8rc1.custom.min.js create mode 100644 admin_tools/media/admin_tools/js/jquery/jquery.cookie.min.js create mode 100644 admin_tools/media/admin_tools/js/jquery/jquery.dashboard.js create mode 100644 admin_tools/media/admin_tools/js/json.min.js create mode 100644 admin_tools/menu/__init__.py create mode 100644 admin_tools/menu/models.py create mode 100644 admin_tools/menu/templates/admin/base_site.html create mode 100644 admin_tools/menu/templates/menu/dummy.html create mode 100644 admin_tools/menu/templates/menu/item.html create mode 100644 admin_tools/menu/templates/menu/menu.html create mode 100644 admin_tools/menu/templatetags/__init__.py create mode 100644 admin_tools/menu/templatetags/menu_tags.py create mode 100644 admin_tools/menu/tests.py create mode 100644 admin_tools/menu/utils.py create mode 100644 admin_tools/menu/views.py create mode 100644 admin_tools/models.py create mode 100644 admin_tools/theming/__init__.py create mode 100644 admin_tools/theming/models.py create mode 100644 admin_tools/theming/tests.py create mode 100644 admin_tools/theming/views.py create mode 100644 admin_tools/utils.py create mode 100644 admin_tools/views.py create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/customization.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/quickstart.rst create mode 100644 setup.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..85f438f --- /dev/null +++ b/.hgignore @@ -0,0 +1,11 @@ +syntax: glob +*.pyc +*.*~ +*.orig +*.old +.*.swp +build +dist +MANIFEST +_build +_static diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..efe0772 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,5 @@ +The primary author of django-admin-tools is David Jean Louis . + + +Others who have contributed to the application: +None at the moment. diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..78883cf --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,10 @@ +========================= +django-admin-tools changelog +========================= + + +Version 0.1.0, 30 January 2010: +------------------------------- + +* Initial import of the project to Bitbucket: + http://bitbucket.org/izi/django-admin-tools/ diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..e567c06 --- /dev/null +++ b/INSTALL @@ -0,0 +1,22 @@ +Thanks for downloading django-admin-tools. + +This application requires Python 2.4 or later and Django 1.1.0 or newer. +Optionally the following dependencies can be installed: +* feedparser:http://www.feedparser.org/ + +To install django-admin-tools, run the following command inside this directory:: + + python setup.py install + +If you have the Python ``easy_install`` utility available, you can also type +the following to download and install in one step:: + + easy_install django-admin-tools + +Or if you're using ``pip``:: + + pip install django-admin-tools + +Or if you'd prefer you can simply place the included ``admin_tools`` directory +somewhere on your Python path, or symlink to it from somewhere on your Python +path; this is useful if you're working from a Mercurial checkout. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f954173 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +copyright (c) 2010 David JEAN LOUIS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..98191c4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGELOG INSTALL LICENSE MANIFEST.in README AUTHORS +recursive-include docs * +recursive-include admin_tools/locale * diff --git a/README b/README new file mode 100644 index 0000000..303c45a --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +================== +django-admin-tools +================== + +django-admin-tools is a collection of extensions/tools for the default django +administration interface, it includes: + +* a full featured and customizable dashboard, +* a customizable menu bar, +* tools to make admin theming easier. + +It was originally developed for django-cms, and then extracted to this +pluggable app. + +For installation instructions, see the file "INSTALL" in this directory; for +instructions on how to use this application, and on what it provides, see the +file "quickstart.rst" in the "docs/" directory. + +todo: write a better README diff --git a/admin_tools/__init__.py b/admin_tools/__init__.py new file mode 100644 index 0000000..1f6518e --- /dev/null +++ b/admin_tools/__init__.py @@ -0,0 +1 @@ +VERSION = '0.1.0' diff --git a/admin_tools/dashboard/__init__.py b/admin_tools/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin_tools/dashboard/models.py b/admin_tools/dashboard/models.py new file mode 100644 index 0000000..99224e1 --- /dev/null +++ b/admin_tools/dashboard/models.py @@ -0,0 +1,336 @@ +""" +This module contains the base classes for the dashboard and dashboard modules. +""" + +from django.contrib import admin +from django.core.urlresolvers import reverse +from django.utils.text import capfirst +from django.utils.translation import ugettext_lazy as _ +from admin_tools.utils import AppListElementMixin + + +class Dashboard(list): + """ + Base class for dashboards. + The Dashboard class is a simple python list that takes three optional + keywords arguments ``title``, ``template`` and ``columns``. + + >>> d = Dashboard(template='foo.html', columns=3) + >>> d.template + 'foo.html' + >>> d.columns + 3 + >>> d.append(DashboardModule()) + >>> d.append(DashboardModule()) + >>> len(d) + 2 + >>> d.pop().__class__.__name__ + 'DashboardModule' + >>> len(d) + 1 + """ + + class Media: + css = {'all': 'dashboard.css'} + js = ( + 'jquery/jquery-1.4.1.min.js', + 'jquery/jquery-ui-1.8rc1.custom.min.js', + 'jquery/jquery.cookie.min.js', + 'json.min.js', + 'jquery/jquery.dashboard.js', + ) + + def __init__(self, *args, **kwargs): + """ + Dashboard constructor, keyword argument: + + ``title`` + the title to display for your dashboard. + Default value: 'Dashboard'. + + ``template`` + the path to the dashboard template. + Default value: 'dashboard/dashboard.html'. + + ``columns`` + The number of columns for the dashboard. Default value: 2. + """ + super(Dashboard, self).__init__() + self.title = kwargs.get('title', _('Dashboard')) + self.template = kwargs.get('template', 'dashboard/dashboard.html') + self.columns = kwargs.get('columns', 2) + + +class DashboardModule(object): + """ + Base class for all dashboard modules. + """ + def __init__(self, *args, **kwargs): + """ + Dashboard module constructor, keywords arguments (all are optional): + + ``enabled`` + Boolean that determines whether the module should be enabled in + the dashboard by default or not. Default value: True. + + ``draggable`` + Boolean that determines whether the module can be draggable or not. + Draggable modules can be re-arranged by users. Default value: True. + + ``collapsible`` + Boolean that determines whether the module is collapsible, this + allows users to show/hide module content. Default: True. + + ``deletable`` + Boolean that determines whether the module can be removed from the + dashboard by users or not. Default: True. + + ``title`` + String that contains the module title, make sure you use the django + gettext functions if your application is multilingual. + Default value: ''. + + ``title_url`` + String that contains the module title URL. If given the module + title will be a link to this URL. Default value: None. + + ``css_classes`` + A list of css classes to be added to the module ``div`` class + attribute. Default value: None. + + ``pre_content`` + Text or HTML content to display above the module content. + Default value: None. + + ``content`` + The module text or HTML content. Default value: None. + + ``post_content`` + Text or HTML content to display under the module content. + Default value: None. + + ``template`` + The template to use to render the module. + Default value: 'dashboard/module.html'. + """ + self.enabled = kwargs.get('enabled', True) + self.draggable = kwargs.get('draggable', True) + self.collapsible = kwargs.get('collapsible', True) + self.deletable = kwargs.get('deletable', True) + self.title = kwargs.get('title', '') + self.title_url = kwargs.get('title_url', None) + self.css_classes = kwargs.get('css_classes', []) + self.pre_content = kwargs.get('pre_content') + self.post_content = kwargs.get('post_content') + self.template = kwargs.get('template', 'dashboard/module.html') + self.entries = [] + + def render(self, request): + pass + + def is_empty(self): + """ + Return True if the module has no content and False otherwise. + + >>> mod = DashboardModule() + >>> mod.is_empty() + True + >>> mod.pre_content = 'foo' + >>> mod.is_empty() + False + >>> mod.pre_content = None + >>> mod.is_empty() + True + >>> mod.entries.append('foo') + >>> mod.is_empty() + False + >>> mod.entries = [] + >>> mod.is_empty() + True + """ + return self.pre_content is None and \ + self.post_content is None and \ + len(self.entries) == 0 + + def render_css_classes(self): + """ + Return a string containing the css classes for the module. + + >>> mod = DashboardModule(enabled=False, draggable=True, + ... collapsible=True, deletable=True) + >>> mod.render_css_classes() + 'dashboard-module disabled draggable collapsible deletable' + >>> mod.css_classes.append('foo') + >>> mod.render_css_classes() + 'dashboard-module disabled draggable collapsible deletable foo' + >>> mod.enabled = True + >>> mod.render_css_classes() + 'dashboard-module draggable collapsible deletable foo' + """ + ret = ['dashboard-module'] + if not self.enabled: + ret.append('disabled') + if self.draggable: + ret.append('draggable') + if self.collapsible: + ret.append('collapsible') + if self.deletable: + ret.append('deletable') + ret += self.css_classes + return ' '.join(ret) + + +class TextDashboardModule(DashboardModule): + """ + Dashboard module that displays a list of links. + """ + + def __init__(self, *args, **kwargs): + super(TextDashboardModule, self).__init__(*args, **kwargs) + self.entries.append(kwargs.get('text', '')) + + +class LinkListDashboardModule(DashboardModule): + """ + Dashboard module that displays a list of links. + """ + + def __init__(self, *args, **kwargs): + super(LinkListDashboardModule, self).__init__(*args, **kwargs) + self.title = kwargs.get('title', _('Links')) + self.template = kwargs.get('template', + 'dashboard/modules/link_list.html') + self.layout = kwargs.get('layout', 'stacked') + self.entries = kwargs.get('link_list', []) + + +class AppListDashboardModule(DashboardModule, AppListElementMixin): + """ + Class that represents a dashboard module that lists installed apps. + """ + + def __init__(self, *args, **kwargs): + super(AppListDashboardModule, self).__init__(*args, **kwargs) + self.title = kwargs.get('title', _('Applications')) + self.include_list = kwargs.get('include_list', []) + self.exclude_list = kwargs.get('exclude_list', []) + self.template = kwargs.get('template', + 'dashboard/modules/app_list.html') + + def render(self, request): + apps = {} + for model, model_admin in admin.site._registry.items(): + perms = self._check_perms(request, model, model_admin) + if not perms: + continue + app_label = model._meta.app_label + if app_label not in apps: + apps[app_label] = { + 'title': capfirst(app_label.title()), + 'url': reverse('admin:app_list', args=(app_label,)), + 'models': [] + } + model_dict = {} + model_dict['title'] = capfirst(model._meta.verbose_name_plural) + if perms['change']: + model_dict['change_url'] = self._get_admin_change_url(model) + if perms['add']: + model_dict['add_url'] = self._get_admin_add_url(model) + apps[app_label]['models'].append(model_dict) + + apps_sorted = apps.keys() + apps_sorted.sort() + for app in apps_sorted: + # sort model list alphabetically + apps[app]['models'].sort(lambda x, y: cmp(x['title'], y['title'])) + self.entries.append(apps[app]) + + +class ModelListDashboardModule(DashboardModule, AppListElementMixin): + + def __init__(self, *args, **kwargs): + super(ModelListDashboardModule, self).__init__(*args, **kwargs) + self.title = kwargs.get('title', '') + self.include_list = kwargs.get('include_list', []) + self.exclude_list = kwargs.get('exclude_list', []) + self.template = kwargs.get('template', + 'dashboard/modules/model_list.html') + + def render(self, request): + for model, model_admin in admin.site._registry.items(): + perms = self._check_perms(request, model, model_admin) + if not perms: + continue + model_dict = {} + model_dict['title'] = capfirst(model._meta.verbose_name_plural) + if perms['change']: + model_dict['change_url'] = self._get_admin_change_url(model) + if perms['add']: + model_dict['add_url'] = self._get_admin_add_url(model) + self.entries.append(model_dict) + + # sort model list alphabetically + self.entries.sort(lambda x, y: cmp(x['title'], y['title'])) + + +class RecentActionsDashboardModule(DashboardModule): + """ + Module that lists the recent actions for the current user. + """ + + def __init__(self, *args, **kwargs): + super(RecentActionsDashboardModule, self).__init__(*args, **kwargs) + self.title = kwargs.get('title', _('Recent Actions')) + self.include_list = kwargs.get('include_list', []) + self.exclude_list = kwargs.get('exclude_list', []) + self.limit = kwargs.get('limit', []) + self.template = kwargs.get('template', + 'dashboard/modules/recent_actions.html') + + def render(self, request): + from django.contrib.admin.models import LogEntry + if request.user is None: + qs = LogEntry.objects.all() + else: + qs = LogEntry.objects.filter(user__id__exact=request.user.id) + # todo: RecentActionsDashboardModule: filter by contenttype + if self.include_list: + pass + if self.exclude_list: + pass + self.entries = qs.select_related('content_type', 'user')[:self.limit] + + +class FeedDashboardModule(DashboardModule): + """ + Class that represents a feed dashboard module. + """ + def __init__(self, *args, **kwargs): + super(FeedDashboardModule, self).__init__(*args, **kwargs) + self.title = kwargs.get('title', _('RSS Feed')) + self.template = kwargs.get('template', 'dashboard/modules/feed.html') + self.feed_url = kwargs.get('feed_url') + self.limit = kwargs.get('limit') + + def render(self, request): + import datetime + if self.feed_url is None: + raise ValueError('You must provide a valid feed URL') + try: + import feedparser + except ImportError: + raise ImportError('You must install the feedparser python module') + + feed = feedparser.parse(self.feed_url) + if self.limit is not None: + entries = feed['entries'][:self.limit] + else: + entries = feed['entries'] + for entry in entries: + entry.url = entry.link + try: + entry.date = datetime.date(*entry.updated_parsed[0:3]) + except: + # no date for certain feeds + pass + self.entries.append(entry) diff --git a/admin_tools/dashboard/templates/admin/app_index.html b/admin_tools/dashboard/templates/admin/app_index.html new file mode 100644 index 0000000..cc15209 --- /dev/null +++ b/admin_tools/dashboard/templates/admin/app_index.html @@ -0,0 +1,18 @@ +{% extends "admin/index.html" %} +{% load i18n dashboard_tags %} + +{% if not is_popup %} + +{% block breadcrumbs %} +{% endblock %} + +{% endif %} + +{% block content %} +{% render_dashboard %} +{% endblock %} +{% block sidebar %}{% endblock %} diff --git a/admin_tools/dashboard/templates/admin/index.html b/admin_tools/dashboard/templates/admin/index.html new file mode 100644 index 0000000..63d21f0 --- /dev/null +++ b/admin_tools/dashboard/templates/admin/index.html @@ -0,0 +1,20 @@ +{% extends "admin/base_site.html" %} +{% load i18n dashboard_tags %} + +{% block extrahead %} +{{ block.super }} +{% render_dashboard_js %} +{% endblock %} + +{% block extrastyle %} +{{ block.super }} +{% render_dashboard_css %} +{% endblock %} + +{% block bodyclass %}dashboard{% endblock %} + +{% block breadcrumbs %}{% endblock %} +{% block content_title %}{% endblock %} +{% block content %} +{% render_dashboard %} +{% endblock %} diff --git a/admin_tools/dashboard/templates/dashboard/dashboard.html b/admin_tools/dashboard/templates/dashboard/dashboard.html new file mode 100644 index 0000000..e8bb177 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/dashboard.html @@ -0,0 +1,34 @@ +{% load i18n dashboard_tags %} +{% if dashboard.title %} +

{{ dashboard.title }}

+{% endif %} + + + + + + +
+

{% trans "Add modules to the dashboard" %}

+
    + {% spaceless %} + {% for module in dashboard %} + {% if not module.enabled %} +
  • {{ module.title }}
  • + {% endif %} + {% endfor %} + {% endspaceless %} +
+
+
+ {% for module in dashboard %} +{% render_dashboard_module module forloop.counter %}{% endfor %} +
diff --git a/admin_tools/dashboard/templates/dashboard/dummy.html b/admin_tools/dashboard/templates/dashboard/dummy.html new file mode 100644 index 0000000..f04fcf5 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/dummy.html @@ -0,0 +1 @@ +{% extends template %} diff --git a/admin_tools/dashboard/templates/dashboard/module.html b/admin_tools/dashboard/templates/dashboard/module.html new file mode 100644 index 0000000..8e61772 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/module.html @@ -0,0 +1,20 @@ +{% if not module.is_empty %} + +

{{ module.title }}

+
+ {% spaceless %} + {% if module.pre_content %} + {{ module.pre_content }} + {% endif %} + {% block module_content %} + {% for entry in module.entries %} + {{ entry }} + {% endfor %} + {% endblock %} + {% if module.post_content %} + {{ module.post_content }} + {% endif %} + {% endspaceless %} +
+ +{% endif %} diff --git a/admin_tools/dashboard/templates/dashboard/modules/app_list.html b/admin_tools/dashboard/templates/dashboard/modules/app_list.html new file mode 100644 index 0000000..fb5bd61 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/modules/app_list.html @@ -0,0 +1,22 @@ +{% extends "dashboard/module.html" %} +{% load i18n %} +{% block module_content %} + {% for entry in module.entries %} +

{{ entry.title }}

+
    + {% for model in entry.models %} + {% spaceless %} +
  • + {% if model.change_url %}{{ model.title }}{% else %}{{ model.title }}{% endif %} + {% if model.add_url or model.change_url %} + + {% endif %} +
  • + {% endspaceless %} + {% endfor %} +
+ {% endfor %} +{% endblock %} diff --git a/admin_tools/dashboard/templates/dashboard/modules/feed.html b/admin_tools/dashboard/templates/dashboard/modules/feed.html new file mode 100644 index 0000000..f822d5b --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/modules/feed.html @@ -0,0 +1,13 @@ +{% extends "dashboard/module.html" %} +{% block module_content %} +
    + {% spaceless %} + {% for entry in module.entries %} +
  • + {% if entry.date %}{{ entry.date|date }} {% endif %} + {{ entry.title }} +
  • + {% endfor %} + {% endspaceless %} +
+{% endblock %} diff --git a/admin_tools/dashboard/templates/dashboard/modules/link_list.html b/admin_tools/dashboard/templates/dashboard/modules/link_list.html new file mode 100644 index 0000000..2863af0 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/modules/link_list.html @@ -0,0 +1,15 @@ +{% extends "dashboard/module.html" %} +{% block module_content %} +
    + {% spaceless %} + {% for entry in module.entries %} +
  • + {{ entry.title }} + {% if entry.description %} + + {% endif %} +
  • + {% endfor %} + {% endspaceless %} +
+{% endblock %} diff --git a/admin_tools/dashboard/templates/dashboard/modules/model_list.html b/admin_tools/dashboard/templates/dashboard/modules/model_list.html new file mode 100644 index 0000000..1453713 --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/modules/model_list.html @@ -0,0 +1,19 @@ +{% extends "dashboard/module.html" %} +{% load i18n %} +{% block module_content %} +
    + {% for entry in module.entries %} + {% spaceless %} +
  • + {% if entry.change_url %}{{ entry.title }}{% else %}{{ entry.title }}{% endif %} + {% if entry.add_url or entry.change_url %} + + {% endif %} +
  • + {% endspaceless %} + {% endfor %} +
+{% endblock %} diff --git a/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html b/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html new file mode 100644 index 0000000..91cca8e --- /dev/null +++ b/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html @@ -0,0 +1,18 @@ +{% extends "dashboard/module.html" %} +{% load i18n %} +{% block module_content %} + +{% endblock %} diff --git a/admin_tools/dashboard/templatetags/__init__.py b/admin_tools/dashboard/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin_tools/dashboard/templatetags/dashboard_tags.py b/admin_tools/dashboard/templatetags/dashboard_tags.py new file mode 100644 index 0000000..49c044f --- /dev/null +++ b/admin_tools/dashboard/templatetags/dashboard_tags.py @@ -0,0 +1,79 @@ +""" +Dashboard template tags, the following dashboard tags are available: +* ``{% render_dashboard %}`` +* ``{% render_dashboard_module %}`` +* ``{% render_dashboard_js %}`` +* ``{% render_dashboard_css %}`` + +To load the dashboard tags just do: ``{% load dashboard_tags %}``. +""" + +import math +from django import template +from admin_tools.utils import render_media +from admin_tools.dashboard.utils import get_dashboard_from_context + +register = template.Library() + + +def render_dashboard(context, dashboard=None): + """ + Template tag that renders the dashboard, it takes an optional ``Dashboard`` + instance as unique argument, if not given, the dashboard is retrieved with + the ``get_dashboard_from_context`` function. + """ + if not dashboard: + dashboard = get_dashboard_from_context(context) + context.update({ + 'template': dashboard.template, + 'dashboard': dashboard, + 'split_at': math.ceil(float(len(dashboard))/float(dashboard.columns)) + }) + return context +render_dashboard = register.inclusion_tag( + 'dashboard/dummy.html', + takes_context=True +)(render_dashboard) + + +def render_dashboard_module(context, module, index=None): + """ + Template tag that renders a given dashboard module, it takes a + ``DashboardModule`` instance as first parameter and an integer ``index`` as + second parameter, that is the index of the module in the dashboard. + """ + module.render(context['request']) + context.update({ + 'template': module.template, + 'module': module, + 'index': index, + }) + return context +render_dashboard_module = register.inclusion_tag( + 'dashboard/dummy.html', + takes_context=True +)(render_dashboard_module) + + +def render_dashboard_js(dashboard=None): + """ + Template tag that renders the needed js files for the dashboard. + It relies on the ``Media`` inner class of the ``Dashboard`` instance. + """ + if dashboard is None: + dashboard = get_dashboard_from_context({}) + tpl = '' + return render_media('js', tpl, dashboard) +register.simple_tag(render_dashboard_js) + + +def render_dashboard_css(dashboard=None): + """ + Template tag that renders the needed css files for the dashboard. + It relies on the ``Media`` inner class of the ``Dashboard`` instance. + """ + if dashboard is None: + dashboard = get_dashboard_from_context({}) + tpl = '' + return render_media('css', tpl, dashboard) +register.simple_tag(render_dashboard_css) diff --git a/admin_tools/dashboard/tests.py b/admin_tools/dashboard/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/admin_tools/dashboard/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/admin_tools/dashboard/utils.py b/admin_tools/dashboard/utils.py new file mode 100644 index 0000000..eb3705c --- /dev/null +++ b/admin_tools/dashboard/utils.py @@ -0,0 +1,133 @@ +""" +Dashboard utilities. +""" + +from django.conf import settings +from django.contrib import admin +from django.core.urlresolvers import reverse +from django.http import HttpRequest +from django.utils.text import capfirst +from django.utils.translation import ugettext_lazy as _ +from admin_tools.dashboard.models import * + + +def get_dashboard_from_context(context): + try: + request = context['request'] + except KeyError: + request = HttpRequest() + if request.META.get('REQUEST_URI') == reverse('admin:index'): + return get_index_dashboard(request) + try: + app = context['app_list'][0] + model_list = [] + for model, model_admin in admin.site._registry.items(): + if app['name'] == model._meta.app_label.title(): + for m in app['models']: + if m['name'] == capfirst(model._meta.verbose_name_plural): + mod = '%s.%s' % (model.__module__, model.__name__) + model_list.append(mod) + return get_app_index_dashboard(request, app['name'], model_list) + except KeyError: + return get_app_index_dashboard(request) + + +def get_index_dashboard(request): + """ + Returns the admin dashboard defined by the user or the default one. + """ + provider = getattr(settings, 'ADMIN_TOOLS_INDEX_DASHBOARD_PROVIDER', False) + if provider: + from django.utils.importlib import import_module + mod, inst = provider.rsplit('.', 1) + mod = import_module(mod) + return getattr(mod, inst)() + + index_dashboard = Dashboard() + index_dashboard.append(LinkListDashboardModule( + title=_('Quick links'), + layout='inline', + draggable=False, + deletable=False, + collapsible=False, + link_list=[ + { + 'title': _('Return to site'), + 'url': '/', + }, + { + 'title': _('Change password'), + 'url': reverse('admin:password_change'), + }, + { + 'title': _('Log out'), + 'url': reverse('admin:logout') + }, + ] + )) + index_dashboard.append(AppListDashboardModule( + title=_('Applications'), + exclude_list=('django.contrib',), + )) + index_dashboard.append(AppListDashboardModule( + title=_('Administration'), + include_list=('django.contrib',), + )) + index_dashboard.append(RecentActionsDashboardModule( + enabled=False, + title=_('Recent Actions'), + limit=5 + )) + index_dashboard.append(FeedDashboardModule( + enabled=False, + title=_('Latest Django News'), + feed_url='http://www.djangoproject.com/rss/weblog/', + limit=5 + )) + index_dashboard.append(LinkListDashboardModule( + title=_('Support'), + link_list=[ + { + 'title': _('Django documentation'), + 'url': 'http://docs.djangoproject.com/', + 'external': True, + }, + { + 'title': _('Django "django-users" mailing list'), + 'url': 'http://groups.google.com/group/django-users', + 'external': True, + }, + { + 'title': _('Django irc channel'), + 'url': 'irc://irc.freenode.net/django', + 'external': True, + }, + ] + )) + return index_dashboard + + +def get_app_index_dashboard(request, app_title='', model_list=[]): + """ + Returns the admin dashboard defined by the user or the default one. + """ + provider = getattr(settings, 'ADMIN_TOOLS_APP_INDEX_DASHBOARD_PROVIDER', False) + if provider: + from django.utils.importlib import import_module + mod, inst = provider.rsplit('.', 1) + mod = import_module(mod) + return getattr(mod, inst)(app_name) + + import logging + logging.warn(model_list) + app_index_dashboard = Dashboard(title='') + app_index_dashboard.append(ModelListDashboardModule( + title=app_title, + include_list=model_list, + )) + app_index_dashboard.append(RecentActionsDashboardModule( + title=_('Recent Actions'), + include_list=model_list, + limit=5 + )) + return app_index_dashboard diff --git a/admin_tools/dashboard/views.py b/admin_tools/dashboard/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/admin_tools/dashboard/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/admin_tools/media/admin_tools/css/dashboard.css b/admin_tools/media/admin_tools/css/dashboard.css new file mode 100644 index 0000000..3b4b3aa --- /dev/null +++ b/admin_tools/media/admin_tools/css/dashboard.css @@ -0,0 +1,209 @@ +/* Utilities */ + +.float-right { + float: right; +} + +/* }}} */ +/* Dashboard general styles {{{ */ + +.dashboard #content { + width: auto; +} + +#dashboard { + clear: both; +} + +#dashboard-panel { + float: right; + margin-top: -35px; +} + +#dashboard-panel ul { + display: none; + position: absolute; +} + +#dashboard-panel ul li { + padding: 5px; + border: 1px solid #e5e5e5; + background-color: white; +} + +#dashboard-panel ul li a { + display: block; +} + +#dashboard-panel:hover ul { + display: block; +} + +.dashboard ul { + margin: 0; + padding: 0; + list-style: none; +} + +.dashboard ul li { + list-style: none; +} + +.dashboard-column { + width: 49.9%; + float: left; +} + +.dashboard-module { + margin: 10px 10px 5px 10px; + padding: 1px; + border: 1px solid #e5e5e5; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; +} + +.dashboard-placeholder { + border: 2px dashed #cbe0ff; + background-color: #fafafa; + margin: 10px 10px 5px 10px; +} + +.dashboard-module h2 { + margin: 0; + padding: 7px 5px 8px 8px; + text-align: left; + font-weight: normal; + background-image: url(../images/dashboard.png); + background-repeat: repeat-x; + background-position: 0 0px; + height: 20px; + color: #555; +} + +.dashboard-module.draggable h2 { + cursor: move; +} + +.dashboard-module h2 a.toggle-icon, +.dashboard-module h2 a.close-icon { + text-indent: -9999px; + display: block; + float: right; + height: 20px; + width: 17px; + margin: 0 0 0 5px; + cursor: pointer; + background-image: url(../images/dashboard.png); + background-repeat: no-repeat; +} + +.dashboard-module h2 a.toggle-icon { + background-position: 0 -35px; +} + +.dashboard-module h2 a.toggle-icon:hover { + background-position: 0 -55px; +} + +.dashboard-module h2 a.toggle-icon.collapsed { + background-position: 0 -75px; +} + +.dashboard-module h2 a.toggle-icon.collapsed:hover { + background-position: 0 -95px; +} + +.dashboard-module h2 a.close-icon { + background-position: 0 -115px; +} + +.dashboard-module h2 a.close-icon:hover { + background-position: 0 -135px; +} + +.fixed h2 span.toggle-icon, +.fixed h2 span.close-icon { + display: none; +} + +.dashboard-module h3 { + padding: 5px 10px; + margin: 0; + background-color: #f4f4f4; + background-image: url(../images/dashboard.png); + background-repeat: no-repeat; + background-position: 0 -175px; + text-indent: 12px; +} + +.dashboard-module h3 a { + color: #808080; + font-size: 13px; + font-weight: 600; +} + +.dashboard-module-content { + border-top: 1px solid #ececec; +} + +.dashboard-module ul li { + vertical-align: top; + padding: 6px 8px 4px 8px; + line-height: 22px; +} + +.dashboard-module ul li.odd { + background-color: #f4f4f4; +} + +.dashboard-module ul li.even { + background-color: #ffffff; +} + +.dashboard-module ul li a.external-link { + background-image: url(../images/dashboard.png); + background-repeat: no-repeat; + background-position: -5px -158px; + padding-left: 15px; +} + +.dashboard-module ul li ul { + float: right; +} + +.dashboard-module ul li ul li { + display: block; + float: left; + border: 0; + padding: 0 5px; + vertical-align: top; + height: auto; + line-height: auto; +} + +.dashboard-module ul li:hover { + background-color: #fffff4; +} + +/* }}} */ +/* link list specific styles {{{ */ + + +.dashboard-module ul.inline { + display: block; + height: auto; + padding: 15px; + text-align: center; +} + +.dashboard-module ul.inline li { + display: inline; + margin: 10px auto; +} + +.dashboard-module ul.inline li.odd, +.dashboard-module ul.inline li.even { + background: none; +} + +/* }}} */ diff --git a/admin_tools/media/admin_tools/css/menu.css b/admin_tools/media/admin_tools/css/menu.css new file mode 100644 index 0000000..8e9b409 --- /dev/null +++ b/admin_tools/media/admin_tools/css/menu.css @@ -0,0 +1,198 @@ +/** + * theming styles: + * todo: menu.css externalize this in the theming app + */ + +#header { + overflow: hidden; + height: auto; + background: url(../images/menu.png) 0 -135px repeat-x; +} + +#header h1 { + text-indent: -9999px; + margin: 5px 10px; + background: url(../images/django.png) 0 0 no-repeat; + height: 31px; + width: 93px; +} + +#header #user-tools { + padding-right: 10px; +} + +#container .breadcrumbs { + display: block; + padding: 10px 15px; + border: 0; + background-position: 0 -8px; + border-bottom: 1px solid #ededed; +} + +#container .breadcrumbs a { + position: relative; + float: none; + margin: 0; + padding: 0; +} + +/* menu styles */ + +#header #navigation-wrapper { + border: 1px solid #ededed; + border-width: 1px 0 0 0; + background: url(../images/menu.png) 0 0 repeat-x; + display: block; + clear: both; + height: 35px; +} + +#header #navigation-wrapper ul { + list-style: none; + margin: 0; + padding: 0; + z-index: 9999; +} + +#header #navigation-wrapper ul li { + list-style: none; +} + +#header #navigation-wrapper ul.menu { + position: absolute; +} + +#header #navigation-wrapper ul.menu:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +#header #navigation-wrapper ul.menu li { + float: left; + position: relative; +} + +#header #navigation-wrapper ul.menu li a { + float: left; + text-transform: uppercase; + text-decoration: none; + margin: 0; + padding: 9px 15px; + border-right: 1px solid #ededed; + color: #666; +} + +#header #navigation-wrapper ul.menu li.first a { + margin-left: 12px; + background: url(../images/menu.png) 0 -95px no-repeat; + text-indent: 14px; +} + + +#header #navigation-wrapper ul.menu li:hover, +#header #navigation-wrapper ul.menu li.hover { + background: url(../images/menu.png) repeat-x 0 -40px; +} + +#header #navigation-wrapper ul.menu li a.active { + color: #6389b8; + background: url(../images/admin.png) -320px -358px no-repeat; +} + +#header #navigation-wrapper ul li span.icon { + display: block; + float: right; + width: 20px; + height: 10px; + background: url(../images/menu.png) no-repeat 0 -75px; +} + +#header #navigation-wrapper ul.menu li ul { + display: none; + position: absolute; + top: 100%; + left: 0; + background: #fff; +} + +#header #navigation-wrapper ul.menu li ul li ul { + left: 100%; + top: -1px; +} + +#header #navigation-wrapper ul li ul li a em { + display: none; +} + +#header #navigation-wrapper ul.menu li ul li { + position: relative; + display: block; + float: none; + background: none; + border: 1px solid #ededed; + border-width: 0 1px 1px 1px; + min-width: 170px; +} + +#header #navigation-wrapper ul.menu li ul li ul { + border-top: 1px solid #ededed; +} + +#header #navigation-wrapper ul.menu li ul li.first { + margin: 0; +} + +#header #navigation-wrapper ul.menu li ul li a { + float: none; + display: block; + color: #666 !important; + border: 0; + padding: 7px 20px 7px 16px; + white-space: nowrap; + text-transform: none; + background-color: #fafafa; + background-image: none; +} + +#header #navigation-wrapper ul li ul li span.icon { + background-position: 0 -85px; + margin: -14px -10px 0 0; +} + +#header #navigation-wrapper ul.menu li:hover ul, +#header #navigation-wrapper ul.menu li.hover ul { + display: block; +} + +#header #navigation-wrapper ul.menu li:hover ul li ul, +#header #navigation-wrapper ul.menu li.hover ul li ul { + display: none; +} + +#header #navigation-wrapper ul.menu li ul li:hover ul, +#header #navigation-wrapper ul.menu li ul li.hover ul { + display: block; +} + +#header #navigation-wrapper ul.menu li:hover ul li, +#header #navigation-wrapper ul.menu li.hover ul li { + background: none; +} + +#header #navigation-wrapper ul.menu li ul li:hover a, +#header #navigation-wrapper ul.menu li ul li.hover a { + background: #f3f3f4; +} + +#header #navigation-wrapper ul.menu li ul li:hover ul li a, +#header #navigation-wrapper ul.menu li ul li.hover ul li a { + background: none; +} + +#header #navigation-wrapper ul.menu li ul li ul li:hover a, +#header #navigation-wrapper ul.menu li ul li ul li.hover a { + background: #f3f3f4; +} diff --git a/admin_tools/media/admin_tools/css/theming.css b/admin_tools/media/admin_tools/css/theming.css new file mode 100644 index 0000000..0478793 --- /dev/null +++ b/admin_tools/media/admin_tools/css/theming.css @@ -0,0 +1,36 @@ +/* header logo and user tools */ + +#header { + overflow: visible; + height: auto; + background-color: #fff; +} + +#header #branding { + border: 1px solid #ededed; + border-width: 1px 0; + margin: 0; + background: url(../images/admin.png) 0 0 repeat-x; +} + +#header #branding a { + display: block; + height: 32px; + width: 105px; + background: transparent url(../images/admin.png) 0 -122px no-repeat; +} + +#header #branding a h1 span { + display: none; +} + +#header #user-tools { + color: #656565; + font-size: 12px; + padding: 10px; +} + +#header #user-tools a:link, #header #user-tools a:visited { + color: #5b80b2; + text-decoration: none; +} diff --git a/admin_tools/media/admin_tools/images/admin.png b/admin_tools/media/admin_tools/images/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa9e9f5df3670ce6b90ba8590a143378c36c847 GIT binary patch literal 23314 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 literal 0 HcmV?d00001 diff --git a/admin_tools/media/admin_tools/images/dashboard.png b/admin_tools/media/admin_tools/images/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..2a55cfc50968faf5dcf4a56dd86f2e0c34e470e2 GIT binary patch literal 2310 zcmV+h3HkPkP)Px#24YJ`L;zI)RRC3Z#W10k4%4_I5Vp!N{9)OOvkxOCp<%3e%4YB~>LL@U7Sb8A4bvu&u||}Ptr!-z zq;XVDUT`uea%4$&A9U~2eb4VnPi{I|qGPc1?!7wqoc}rJf9`vdSXfw)+(QZJH$r$X z5S|ypcU`EaNPrX|1VT*;QF|u^=uv?{2(`JWuSW>9n_<)x z))$AccX!z@^jlF=7{a#F-b5=G!rnY&TW?O*?NAm7r0t2c#}N|Yf;5a;R8u4z5i(;S z0Hm@+x+m%%3)TGgkD7KF?UoP@NKBmq2-jVe#;~kKU8AosmugB-i>i(v%0nq-&1KUj zX$Hb&f^fVyUNuBQc`FQLO^@vCv)wiE6>hD7sJU#Xk@86zQI|+G8g(DkxapNj%&?>E z1j};L?WXjoLNg~qj)-BDl=9I`S}B|W3RlV~3+vb+mH9^QYMTVQK9!qB{z^k>*j;iV z>KTJs+s=%30Ao)HReIWOJAN)Mt3_J6=u0{<*Up4%6eMkpQc62j& zn6xdo?GM=;XqHk)^SrV+w2W11szb}_I*dX8QaVZ0Ne(J#MnrY6Z1$FtkTh`-CRS!E zF!E2y*@bz zvpKrl6U)1ei>}-R9Tu*R?#wFbZfwSBLJ_T0IV@CeRIAl{=(2DZrZAXCI8w;Nc_-QG z43M%ak;(yGVg^%5eL8S70bvxMbVfi*`N5nIKeP(lazDtjdg|%v`BBghXEK?ey2rvE z>#hJ#1-i2Gc$818jW5Dc6dzxJd?@% zqa}q(>Gfzdx(;~M#4VG_Os$w4TzLB8)+*Z^1ODC!x3;vUE%!lBiN#`pdrCH&4b)TS zfL#Ify%ChYeiNjpsCCU1X0zElz-W`6GOE@EYXP}j?q(zsnFqFeO#eEY&0cD`r{r?E zE0IW~6WDCxmd$2Qt+)kbv)NIzr>N_8PiaeA+HwziipPCRC-6M*%Sn*vga7*}nCZXnA!Y~P} z^O;-M2+vhbaPt#iJiSg1n^g~KCY#$;*Os>2$2}z$i`hLTo6Ua6dP*!7O8{Te92kj2 zI&-<)TPso+i^X;U-*j_eBoZm)a=9xlxg3kdHhVNEEq!V}n8H{rwoWyuMm;5=)&;V# zF95y!df?Sl9t_x19&A@#TiWtLG4>|lAz-aqTLf~zHOw!a11Map4xJl(=Kl-)1-KoA zZB|qJ4q(HcJ$pXko>XHUz)oOI5Evcz4i3Ixt^p#=qG~SgK7aoFt5V6ibLU=h&jX)n6qJ&Akxr+d+_7WF zGv#u5v0N@M?%cWa+v#-rN!MrYHnMOv@O7Yje0=;!wOXx|N~POFLqmgx`<3zW@gvH* zZ>SV&9_4sE{@nch{MDhMp+OVV`T6;)@p$|>zbFeID<20w-eluN;7#?~@Z?Zc<<-$Z z>3>wE6kDz3PXQZM2?}7I+C$&jHVwAr|JYL=1G-d_hk#q^sCP*fKwyRIfo~}~^O6EE z0o(}EQ`Q2{0-L(Jx*l*(shMgO8j#b*g{;`t=KE&z^k`U}R)u z>)6=XcNP{F-n($&!fge`JB@nET1`oFb8~mA)v8P;lTX`sK*{9OrBbOhJ3Fh}=NExL z05|vA9^NRI0^z>igm`KC<~$O24FP0bbx zg}MIz{*x0E6SET&6SMvO{U-~B!rau3;`a_zm8>DT(tJG(F4yF{fs}K&$Eq-WZ%8fILdCfjVK?r zENd{I&pYNV%i<#20JxZXz9pAe=kxg^zz@B!1=RoGE{E0Q(Zi;74Wr!1b+zsmwI{$2+U={as$*7ut!Yl*%F1_l}` gS}`BG1L&6j0TR{{2Epi2hX4Qo07*qoM6N<$f-@9WRR910 literal 0 HcmV?d00001 diff --git a/admin_tools/media/admin_tools/images/django.png b/admin_tools/media/admin_tools/images/django.png new file mode 100644 index 0000000000000000000000000000000000000000..9433cc809f1d3155c9ff9a3f56826a67903030a4 GIT binary patch literal 2947 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 literal 0 HcmV?d00001 diff --git a/admin_tools/media/admin_tools/js/jquery/jquery-1.4.1.min.js b/admin_tools/media/admin_tools/js/jquery/jquery-1.4.1.min.js new file mode 100644 index 0000000..0c7294c --- /dev/null +++ b/admin_tools/media/admin_tools/js/jquery/jquery-1.4.1.min.js @@ -0,0 +1,152 @@ +/*! + * jQuery JavaScript Library v1.4.1 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 25 19:43:33 2010 -0500 + */ +(function(z,v){function la(){if(!c.isReady){try{r.documentElement.doScroll("left")}catch(a){setTimeout(la,1);return}c.ready()}}function Ma(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var n in b)X(a,n,b[n],f,e,d);return a}if(d!==v){f=!i&&f&&c.isFunction(d);for(n=0;n-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete x[o]}i=c(a.target).closest(f, +a.currentTarget);m=0;for(s=i.length;m)[^>]*$|^#([\w-]+)$/,Qa=/^.[^:#\[\.,]*$/,Ra=/\S/,Sa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Ta=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,O=navigator.userAgent, +va=false,P=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,Q=Array.prototype.slice,wa=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Pa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:r;if(a=Ta.exec(a))if(c.isPlainObject(b)){a=[r.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ra([d[1]], +[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=r.getElementById(d[2])){if(b.id!==d[2])return S.find(a);this.length=1;this[0]=b}this.context=r;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=r;a=r.getElementsByTagName(a)}else return!b||b.jquery?(b||S).find(a):c(b).find(a);else if(c.isFunction(a))return S.ready(a);if(a.selector!==v){this.selector=a.selector;this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a, +this)},selector:"",jquery:"1.4.1",length:0,size:function(){return this.length},toArray:function(){return Q.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=0;ba.apply(this,a);return this},each:function(a,b){return c.each(this, +a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(r,c);else P&&P.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(Q.apply(this,arguments),"slice",Q.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice}; +c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,n;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support= +{leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:r.createElement("select").appendChild(r.createElement("option")).selected,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null}; +b.type="text/javascript";try{b.appendChild(r.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b,a.firstChild);if(z[f]){c.support.scriptEval=true;delete z[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function n(){c.support.noCloneEvent=false;d.detachEvent("onclick",n)});d.cloneNode(true).fireEvent("onclick")}d=r.createElement("div");d.innerHTML="";a=r.createDocumentFragment();a.appendChild(d.firstChild); +c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=r.createElement("div");n.style.width=n.style.paddingLeft="1px";r.body.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;r.body.removeChild(n).style.display="none"});a=function(n){var o=r.createElement("div");n="on"+n;var m=n in o;if(!m){o.setAttribute(n,"return;");m=typeof o[n]==="function"}return m};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props= +{"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ua=0,xa={},Va={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var f=a[G],e=c.cache;if(!b&&!f)return null;f||(f=++Ua);if(typeof b==="object"){a[G]=f;e=e[f]=c.extend(true, +{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Va:(e[f]={});if(d!==v){a[G]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[G]}catch(i){a.removeAttribute&&a.removeAttribute(G)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this, +a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===v){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===v&&this.length)f=c.data(this[0],a);return f===v&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d); +return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===v)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]|| +a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var ya=/[\n\t]/g,ca=/\s+/,Wa=/\r/g,Xa=/href|src|style/,Ya=/(button|input)/i,Za=/(button|input|object|select|textarea)/i,$a=/^(a|area)$/i,za=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(o){var m= +c(this);m.addClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===v){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value|| +{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i=0;else if(c.nodeName(this,"select")){var x=c.makeArray(s);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),x)>=0});if(!x.length)this.selectedIndex=-1}else this.value=s}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return v;if(f&&b in c.attrFn)return c(a)[b](d); +f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==v;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Xa.test(b);if(b in a&&f&&!i){if(e){b==="type"&&Ya.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Za.test(a.nodeName)||$a.test(a.nodeName)&&a.href?0:v;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText= +""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?v:a}return c.style(a,b,d)}});var ab=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==z&&!a.frameElement)a=z;if(!d.guid)d.guid=c.guid++;if(f!==v){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j= +function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):v};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var n,o=0;n=b[o++];){var m=n.split(".");n=m.shift();if(o>1){d=c.proxy(d);if(f!==v)d.data=f}d.type=m.slice(0).sort().join(".");var s=e[n],x=this.special[n]||{};if(!s){s=e[n]={};if(!x.setup||x.setup.call(a,f,m,d)===false)if(a.addEventListener)a.addEventListener(n,i,false);else a.attachEvent&&a.attachEvent("on"+n,i)}if(x.add)if((m=x.add.call(a, +d,f,m,s))&&c.isFunction(m)){m.guid=m.guid||d.guid;m.data=m.data||d.data;m.type=m.type||d.type;d=m}s[d.guid]=d;this.global[n]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===v||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/);for(var n=0;i=b[n++];){var o=i.split(".");i=o.shift();var m=!o.length,s=c.map(o.slice(0).sort(),ab);s=new RegExp("(^|\\.)"+ +s.join("\\.(?:.*\\.)?")+"(\\.|$)");var x=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var A in f[i])if(m||s.test(f[i][A].type))delete f[i][A];x.remove&&x.remove.call(a,o,j);for(e in f[i])break;if(!e){if(!x.teardown||x.teardown.call(a,o)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(A=c.data(a,"handle"))A.elem=null;c.removeData(a, +"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return v;a.result=v;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d, +b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(i){}if(!a.isPropagationStopped()&&f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){d=a.target;var j;if(!(c.nodeName(d,"a")&&e==="click")&&!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){try{if(d[e]){if(j=d["on"+e])d["on"+e]=null;this.triggered=true;d[e]()}}catch(n){}if(j)d["on"+e]=j;this.triggered=false}}},handle:function(a){var b, +d;a=arguments[0]=c.event.fix(a||z.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==v){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), +fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||r;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=r.documentElement;d=r.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop|| +d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==v)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;b.liveProxy=a;c.event.add(this,b.live,na,b)},remove:function(a){if(a.length){var b= +0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],na)}},special:{}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true}; +c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y};var Aa=function(a){for(var b= +a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ba=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ba:Aa,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ba:Aa)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!== +"form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return ma("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return ma("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this, +"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var da=/textarea|input|select/i;function Ca(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ea(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Ca(d);if(a.type!=="focusout"|| +d.type!=="radio")c.data(d,"_change_data",e);if(!(f===v||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}}c.event.special.change={filters:{focusout:ea,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ea.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ea.call(this,a)},beforeactivate:function(a){a= +a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Ca(a))}},setup:function(a,b,d){for(var f in T)c.event.add(this,f+".specialChange."+d.guid,T[f]);return da.test(this.nodeName)},remove:function(a,b){for(var d in T)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),T[d]);return da.test(this.nodeName)}};var T=c.event.special.change.filters}r.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this, +f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){e=f;f=v}var j=b==="one"?c.proxy(e,function(n){c(this).unbind(n,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a, +b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d0){y=t;break}}t=t[g]}l[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,i=Object.prototype.toString,j=false,n=true;[0,0].sort(function(){n=false;return 0});var o=function(g,h,k,l){k=k||[];var q=h=h||r;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g|| +typeof g!=="string")return k;for(var p=[],u,t,y,R,H=true,M=w(h),I=g;(f.exec(""),u=f.exec(I))!==null;){I=u[3];p.push(u[1]);if(u[2]){R=u[3];break}}if(p.length>1&&s.exec(g))if(p.length===2&&m.relative[p[0]])t=fa(p[0]+p[1],h);else for(t=m.relative[p[0]]?[h]:o(p.shift(),h);p.length;){g=p.shift();if(m.relative[g])g+=p.shift();t=fa(g,t)}else{if(!l&&p.length>1&&h.nodeType===9&&!M&&m.match.ID.test(p[0])&&!m.match.ID.test(p[p.length-1])){u=o.find(p.shift(),h,M);h=u.expr?o.filter(u.expr,u.set)[0]:u.set[0]}if(h){u= +l?{expr:p.pop(),set:A(l)}:o.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=u.expr?o.filter(u.expr,u.set):u.set;if(p.length>0)y=A(t);else H=false;for(;p.length;){var D=p.pop();u=D;if(m.relative[D])u=p.pop();else D="";if(u==null)u=h;m.relative[D](y,u,M)}}else y=[]}y||(y=t);y||o.error(D||g);if(i.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))k.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&& +y[g].nodeType===1&&k.push(t[g]);else k.push.apply(k,y);else A(y,k);if(R){o(R,q,k,l);o.uniqueSort(k)}return k};o.uniqueSort=function(g){if(C){j=n;g.sort(C);if(j)for(var h=1;h":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var l=0,q=g.length;l=0))k||l.push(u);else if(k)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&& +"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,l,q,p){h=g[1].replace(/\\/g,"");if(!p&&m.attrMap[h])g[1]=m.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,l,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=o(g[3],null,null,h);else{g=o.filter(g[3],h,k,true^q);k||l.push.apply(l,g);return false}else if(m.match.POS.test(g[0])||m.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true); +return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!o(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"=== +g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,h){return h===0},last:function(g,h,k,l){return h===l.length-1},even:function(g,h){return h%2=== +0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return hk[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,l){var q=h[1],p=m.filters[q];if(p)return p(g,k,h,l);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=h[3];k=0;for(l=h.length;k= +0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=m.attrHandle[k]?m.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var l=h[2];h=h[4];return g==null?l==="!=":l==="="?k===h:l==="*="?k.indexOf(h)>=0:l==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:l==="!="?k!==h:l==="^="? +k.indexOf(h)===0:l==="$="?k.substr(k.length-h.length)===h:l==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,l){var q=m.setFilters[h[2]];if(q)return q(g,k,h,l)}}},s=m.match.POS;for(var x in m.match){m.match[x]=new RegExp(m.match[x].source+/(?![^\[]*\])(?![^\(]*\))/.source);m.leftMatch[x]=new RegExp(/(^(?:.|\r|\n)*?)/.source+m.match[x].source.replace(/\\(\d+)/g,function(g,h){return"\\"+(h-0+1)}))}var A=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g}; +try{Array.prototype.slice.call(r.documentElement.childNodes,0)}catch(B){A=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,l=g.length;k";var k=r.documentElement;k.insertBefore(g,k.firstChild);if(r.getElementById(h)){m.find.ID=function(l,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(l[1]))?q.id===l[1]||typeof q.getAttributeNode!=="undefined"&&q.getAttributeNode("id").nodeValue===l[1]?[q]:v:[]};m.filter.ID=function(l,q){var p=typeof l.getAttributeNode!=="undefined"&&l.getAttributeNode("id"); +return l.nodeType===1&&p&&p.nodeValue===q}}k.removeChild(g);k=g=null})();(function(){var g=r.createElement("div");g.appendChild(r.createComment(""));if(g.getElementsByTagName("*").length>0)m.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var l=0;k[l];l++)k[l].nodeType===1&&h.push(k[l]);k=h}return k};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")m.attrHandle.href=function(h){return h.getAttribute("href", +2)};g=null})();r.querySelectorAll&&function(){var g=o,h=r.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){o=function(l,q,p,u){q=q||r;if(!u&&q.nodeType===9&&!w(q))try{return A(q.querySelectorAll(l),p)}catch(t){}return g(l,q,p,u)};for(var k in g)o[k]=g[k];h=null}}();(function(){var g=r.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== +0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){m.order.splice(1,0,"CLASS");m.find.CLASS=function(h,k,l){if(typeof k.getElementsByClassName!=="undefined"&&!l)return k.getElementsByClassName(h[1])};g=null}}})();var E=r.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g,h){return g!==h&&(g.contains?g.contains(h):true)},w=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},fa=function(g,h){var k=[], +l="",q;for(h=h.nodeType?[h]:h;q=m.match.PSEUDO.exec(g);){l+=q[0];g=g.replace(m.match.PSEUDO,"")}g=m.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var i=d;i0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i={},j;if(f&&a.length){e=0;for(var n=a.length;e +-1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var o=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(m,s){for(;s&&s.ownerDocument&&s!==b;){if(o?o.index(s)>-1:c(s).is(a))return s;s=s.parentNode}return null})},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(), +a);return this.pushStack(pa(a[0])||pa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")}, +nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);bb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e): +e;if((this.length>1||db.test(f))&&cb.test(a))e=e.reverse();return this.pushStack(e,a,Q.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===v||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!== +b&&d.push(a);return d}});var Fa=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ga=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"], +col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==v)return this.empty().append((this[0]&&this[0].ownerDocument||r).createTextNode(a));return c.getText(this)}, +wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length? +d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments, +false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&& +!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Fa,"").replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){qa(this,b);qa(this.find("*"),b.find("*"))}return b},html:function(a){if(a===v)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Fa,""):null;else if(typeof a==="string"&&!/' + return render_media('js', tpl, menu) +register.simple_tag(render_menu_js) + + +def render_menu_css(menu=None): + """ + Template tag that renders the needed css files for the menu. + It relies on the ``Media`` inner class of the ``Menu`` instance. + """ + if menu is None: + menu = get_admin_menu(None) + tpl = '' + return render_media('css', tpl, menu) +register.simple_tag(render_menu_css) diff --git a/admin_tools/menu/tests.py b/admin_tools/menu/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/admin_tools/menu/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/admin_tools/menu/utils.py b/admin_tools/menu/utils.py new file mode 100644 index 0000000..6eeeb4a --- /dev/null +++ b/admin_tools/menu/utils.py @@ -0,0 +1,35 @@ +""" +Menu utilities. +""" + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from admin_tools.menu.models import Menu, MenuItem, AppListMenuItem + + +def get_admin_menu(request): + """ + Returns the admin menu defined by the user or the default one. + """ + provider = getattr(settings, 'ADMIN_TOOLS_INDEX_MENU_PROVIDER', False) + if provider: + from django.utils.importlib import import_module + mod, inst = provider.rsplit('.', 1) + mod = import_module(mod) + return getattr(mod, inst)() + + admin_menu = Menu() + admin_menu.append(MenuItem( + title=_('Dashboard'), + url=reverse('admin:index') + )) + admin_menu.append(AppListMenuItem( + title=_('Applications'), + exclude_list=('django.contrib',), + )) + admin_menu.append(AppListMenuItem( + title=_('Administration'), + include_list=('django.contrib',), + )) + return admin_menu diff --git a/admin_tools/menu/views.py b/admin_tools/menu/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/admin_tools/menu/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/admin_tools/models.py b/admin_tools/models.py new file mode 100644 index 0000000..6b20219 --- /dev/null +++ b/admin_tools/models.py @@ -0,0 +1 @@ +# Create your models here. diff --git a/admin_tools/theming/__init__.py b/admin_tools/theming/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin_tools/theming/models.py b/admin_tools/theming/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/admin_tools/theming/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/admin_tools/theming/tests.py b/admin_tools/theming/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/admin_tools/theming/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/admin_tools/theming/views.py b/admin_tools/theming/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/admin_tools/theming/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/admin_tools/utils.py b/admin_tools/utils.py new file mode 100644 index 0000000..64f6a92 --- /dev/null +++ b/admin_tools/utils.py @@ -0,0 +1,84 @@ +""" +Admin ui common utilities. +""" + +from django.conf import settings +from django.core.urlresolvers import reverse +from django.utils.text import capfirst + +class AppListElementMixin(object): + """ + Mixin class used by both the AppListDashboardModule and the + AppListMenuItem (to honor the DRY concept). + """ + + def _check_perms(self, request, model, model_admin): + mod = '%s.%s' % (model.__module__, model.__name__) + + # check that the app is not in the exclude list + for pattern in self.exclude_list: + if mod.startswith(pattern): + return False + + # check that the app is in the app list (if not empty) + if len(self.include_list): + found = False + for pattern in self.include_list: + if mod.startswith(pattern): + found = True + if not found: + return False + + # check that the user has module perms + if not request.user.has_module_perms(model._meta.app_label): + return False + + # check whether user has any perm for this module + perms = model_admin.get_model_perms(request) + if True not in perms.values(): + return False + return perms + + def _get_app_title(self, model): + app_name = model._meta.app_label.title() + model_name = unicode(model._meta.verbose_name_plural) + if app_name.rstrip('s').lower() == model_name.rstrip('s').lower(): + return capfirst(model_name) + return '%s > %s' % (app_name, capfirst(model_name)) + + def _get_admin_change_url(self, model): + app_label = model._meta.app_label + return reverse('admin:%s_%s_changelist' % (app_label, + model.__name__.lower())) + + def _get_admin_add_url(self, model): + app_label = model._meta.app_label + return reverse('admin:%s_%s_add' % (app_label, model.__name__.lower())) + + +# todo: refactor how menu and dashboard media are rendered +def render_media(type, tpl, obj): + """ + Helper method used by the render_menu_css/js and render_dashboard_css/js + template tags. + """ + p = getattr(settings, 'ADMIN_TOOLS_MEDIA_URL', getattr(settings, 'MEDIA_URL')) + cache = [] + + def get_css_include(o): + ret = [] + for t, f in o.Media.css.items(): + if (t,f) not in cache: + ret.append(tpl % (t, p, f)) + cache.append((t,f)) + return ret + + def get_js_include(o): + ret = [] + for f in o.Media.js: + if f not in cache: + ret.append(tpl % (p, f)) + cache.append(f) + return ret + + return "\n".join(locals()['get_%s_include' % type](obj)) diff --git a/admin_tools/views.py b/admin_tools/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/admin_tools/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..47d1ce4 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-admin-tools.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-admin-tools.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..e623ebc --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# django-admin-tools documentation build configuration file, created by +# sphinx-quickstart on Sat Jan 30 12:28:43 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-admin-tools' +copyright = u'2010, David Jean Louis' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1.0' +# The full version, including alpha/beta/rc tags. +release = '0.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = True + +# If false, no index is generated. +html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-admin-toolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-admin-tools.tex', u'django-admin-tools Documentation', + u'David Jean Louis', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/customization.rst b/docs/customization.rst new file mode 100644 index 0000000..6d0b01e --- /dev/null +++ b/docs/customization.rst @@ -0,0 +1,26 @@ +.. _customization: + +Customization of the django-admin-tools modules +=============================================== + +Introduction +------------ + +todo: write docs for "Customizing introduction" + + +Customizing the navigation menu +------------------------------- + +todo: write docs for "Customizing the navigation menu" + +Customizing the dashboards +-------------------------- + +todo: write docs for "Customizing the dashboards" + + +Customizing the look and feel +----------------------------- + +todo: write docs for "Customizing the look and feel" diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..9b84e40 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,26 @@ +.. django-admin-tools documentation master file, created by + sphinx-quickstart on Sat Jan 30 12:28:43 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to django-admin-tools's documentation! +============================================== + +This documentation covers the latest release of django-admin-tools, a +collection of extensions and tools for the +`Django `_ administration interface. + +To get up and running quickly, consult the :ref:`quick-start guide +`, which describes all the necessary steps to install +django-admin-tools and configure it for the default setup. +For more detailed information about how to install and how to customize +django-admin-tools, read through the documentation listed below. + +Contents: + +.. toctree:: + :maxdepth: 1 + + quickstart + installation + customization diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..745bcb4 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,110 @@ +.. _installation: + +Installation guide +================== + +Requirements +------------ + +Before installing django-admin-tools, you'll need to have a copy of +`Django `_ already installed. For the +|version| release, Django 1.1 or newer is required. + +For further information, consult the `Django download page +`_, which offers convenient +packaged downloads and installation instructions. + +If you want to display feeds in the admin dashboard, you'll also need +the `Universal Feed Parser module `_. + + +Installing django-admin-tools +----------------------------- + +There are several ways to install django-admin-tools: + +* Automatically, via a package manager. + +* Manually, by downloading a copy of the release package and + installing it yourself. + +* Manually, by performing a Mercurial checkout of the latest code. + +It is also highly recommended that you learn to use `virtualenv +`_ for development and +deployment of Python software; ``virtualenv`` provides isolated Python +environments into which collections of software (e.g., a copy of +Django, and the necessary settings and applications for deploying a +site) can be installed, without conflicting with other installed +software. This makes installation, testing, management and deployment +far simpler than traditional site-wide installation of Python +packages. + + +Automatic installation via a package manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Several automatic package-installation tools are available for Python; +the most popular are `easy_install +`_ and `pip +`_. Either can be used to install +django-admin-tools. + +Using ``easy_install``, type:: + + easy_install -Z django-admin-tools + +Note that the ``-Z`` flag is required, to tell ``easy_install`` not to +create a zipped package; zipped packages prevent certain features of +Django from working properly. + +Using ``pip``, type:: + + pip install django-admin-tools + +It is also possible that your operating system distributor provides a +packaged version of django-admin-tools. Consult your operating system's +package list for details, but be aware that third-party distributions +may be providing older versions of django-admin-tools, and so you +should consult the documentation which comes with your operating +system's package. + + +Manual installation from a downloaded package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you prefer not to use an automated package installer, you can +download a copy of django-admin-tools and install it manually. The +latest release package can be downloaded from `django-admin-tools's +listing on the Python Package Index +`_. + +Once you've downloaded the package, unpack it (on most operating +systems, simply double-click; alternately, type ``tar zxvf +django-admin-tools-X-Y-Z.tar.gz`` at a command line on Linux, Mac OS X +or other Unix-like systems). This will create the directory +``django-admin-tools-X-Y-Z``, which contains the ``setup.py`` +installation script. From a command line in that directory, type:: + + python setup.py install + +Note that on some systems you may need to execute this with +administrative privileges (e.g., ``sudo python setup.py install``). + + +Manual installation from a Mercurial checkout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you'd like to try out the latest in-development code, you can +obtain it from the django-admin-tools repository, which is hosted at +`Bitbucket `_ and uses `Mercurial +`_ for version control. To +obtain the latest code and documentation, you'll need to have +Mercurial installed, at which point you can type:: + + hg clone http://bitbucket.org/izi/django-admin-tools/ + +This will create a copy of the django-admin-tools Mercurial repository +on your computer; you can then add the ``django-admin-tools`` directory +to your Python import path, or use the ``setup.py`` script to install +as a package. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..ed5b1ee --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-admin-tools.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-admin-tools.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..c5687bb --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,110 @@ +.. _quickstart: + +Quick start guide +================= + +Before installing django-admin-tools, you'll need to have a copy of +`Django `_ already installed. For the +|version| release, Django 1.1 or newer is required. + + +Installing django-admin-tools +----------------------------- + +There are several ways to install django-admin-tools, this is explained +in :ref:`the installation section `. + +For the impatient, the easier method is to install django-admin-tools via +`easy_install `_ +or `pip `_. + +Using ``easy_install``, type:: + + easy_install -Z django-admin-tools + +Note that the ``-Z`` flag is required, to tell ``easy_install`` not to +create a zipped package; zipped packages prevent certain features of +Django from working properly. + +Using ``pip``, type:: + + pip install django-admin-tools + + +Basic configuration +------------------- + +Once installed, you can add django-admin-tools to any Django-based +project you're developing. + +django-admin-tools is composed of several modules: + +* admin_tools.theming: an app that makes it easy to customize the look + and feel of the admin interface; + +* admin_tools.menu: a customizable navigation menu that sits on top of + every django administration index page; + +* admin_tools.dashboard: a customizable dashboard that replaces the django + administration index page. + +Required settings +~~~~~~~~~~~~~~~~~ + +You must add the django-admin-tools modules to the ``INSTALLED_APPS`` +setting of your project like this:: + + INSTALLED_APPS = ( + 'admin_tools.theming', + 'admin_tools.menu', + 'admin_tools.dashboard', + 'django.contrib.auth', + 'django.contrib.sites', + 'django.contrib.admin' + # ...other installed applications... + ) + +django-admin-tools is modular, so if you want to disable a particular +module, just remove or comment it in your ``INSTALLED_APPS``. +For example, if you just want to use the dashboard:: + + INSTALLED_APPS = ( + 'admin_tools.dashboard', + 'django.contrib.auth', + 'django.contrib.sites', + 'django.contrib.admin' + # ...other installed applications... + ) + +Note: it is very important that you put the admin_tools modules **before** +the ``django.contrib.admin module``, because django-admin-tools overrides +the default django admin templates, and this will not work otherwise. + + +Setting up the django-admin-tools media files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To do this you have two options: + +* create a symbolic link to the django-admin-tools media files to your + ``MEDIA_ROOT`` directory, for example:: + + ln -s /usr/local/lib/python2.6/dist-packages/admin_tools/media/admin_tools /path/to/yourproject/media/ + +* copy the django-admin-tools media files to your ``MEDIA_ROOT`` directory, + for example:: + + cp -r /usr/local/lib/python2.6/dist-packages/admin_tools/media/admin_tools /path/to/yourproject/media/ + + +Testing your new shiny admin interface +-------------------------------------- + +Congrats ! at this point you should have a working installation of +django-admin-tools, now you can just login to your admin site and see what +changed. + +django-admin-tools if fully customizable, but this is out of the scope of +this quickstart, to learn how to customize django-admin-tools modules +please read :ref:`the customization section`. + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5c1bfd5 --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +from distutils.core import setup +import os +from admin_tools import VERSION + + +# Compile the list of packages available, because distutils doesn't have +# an easy way to do this. +root_dir = os.path.dirname(__file__) +if root_dir: + os.chdir(root_dir) + +def get_packages(): + packages = [] + for dirpath, dirnames, filenames in os.walk('admin_tools'): + # Ignore dirnames that start with '.' + for i, dirname in enumerate(dirnames): + if dirname.startswith('.'): del dirnames[i] + if '__init__.py' in filenames: + pkg = dirpath.replace(os.path.sep, '.') + if os.path.altsep: + pkg = pkg.replace(os.path.altsep, '.') + packages.append(pkg) + return packages + +bitbucket_url = 'http://www.bitbucket.org/izi/django-admin-tools/' + +setup( + name='django-admin-tools', + version=VERSION.replace(' ', '-'), + description='Some ponies for the django administration interface', + author='David Jean Louis', + author_email='izimobil@gmail.com', + url=bitbucket_url, + download_url='%sdownloads/django-admin-tools-%s.tar.gz' % (bitbucket_url, VERSION), + package_dir={'admin_tools': 'admin_tools'}, + packages=get_packages(), + package_data={ + 'admin_tools': [ + 'media/admin_tools/css/*.css', + 'media/admin_tools/js/*.js', + 'media/admin_tools/images/*.png', + 'media/admin_tools/images/*.gif', + 'templates/admin_tools/*.html', + 'templates/admin_tools/*/*.html', + '*/templates/*/*.html', + '*/templates/*/*/*.html' + ] + }, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], +) From c647e990913f1853093fbd8ed96a034d721de47c Mon Sep 17 00:00:00 2001 From: David Jean Louis Date: Fri, 5 Feb 2010 00:11:59 +0100 Subject: [PATCH 002/378] cleaned menu css, separated theming to its own app, images are all in the same file now --- .../media/admin_tools/css/dashboard.css | 41 ++--- admin_tools/media/admin_tools/css/menu.css | 170 ++++++------------ admin_tools/media/admin_tools/css/theming.css | 43 ++--- .../media/admin_tools/images/admin-tools.png | Bin 0 -> 5487 bytes .../media/admin_tools/images/admin.png | Bin 23314 -> 0 bytes .../media/admin_tools/images/dashboard.png | Bin 2310 -> 0 bytes admin_tools/media/admin_tools/images/menu.png | Bin 3262 -> 0 bytes admin_tools/theming/templates/admin/base.html | 81 +++++++++ admin_tools/theming/templatetags/__init__.py | 0 .../theming/templatetags/theming_tags.py | 28 +++ 10 files changed, 196 insertions(+), 167 deletions(-) create mode 100644 admin_tools/media/admin_tools/images/admin-tools.png delete mode 100644 admin_tools/media/admin_tools/images/admin.png delete mode 100644 admin_tools/media/admin_tools/images/dashboard.png delete mode 100644 admin_tools/media/admin_tools/images/menu.png create mode 100644 admin_tools/theming/templates/admin/base.html create mode 100644 admin_tools/theming/templatetags/__init__.py create mode 100644 admin_tools/theming/templatetags/theming_tags.py diff --git a/admin_tools/media/admin_tools/css/dashboard.css b/admin_tools/media/admin_tools/css/dashboard.css index 3b4b3aa..ef37cf5 100644 --- a/admin_tools/media/admin_tools/css/dashboard.css +++ b/admin_tools/media/admin_tools/css/dashboard.css @@ -8,7 +8,8 @@ /* Dashboard general styles {{{ */ .dashboard #content { - width: auto; + display: block; + width: auto; !important; } #dashboard { @@ -73,9 +74,7 @@ padding: 7px 5px 8px 8px; text-align: left; font-weight: normal; - background-image: url(../images/dashboard.png); - background-repeat: repeat-x; - background-position: 0 0px; + background: url(../images/admin-tools.png) repeat-x 0 -245px; height: 20px; color: #555; } @@ -93,32 +92,32 @@ width: 17px; margin: 0 0 0 5px; cursor: pointer; - background-image: url(../images/dashboard.png); + background-image: url(../images/admin-tools.png); background-repeat: no-repeat; } .dashboard-module h2 a.toggle-icon { - background-position: 0 -35px; + background-position: 0 -45px; } .dashboard-module h2 a.toggle-icon:hover { - background-position: 0 -55px; + background-position: 0 -65px; } .dashboard-module h2 a.toggle-icon.collapsed { - background-position: 0 -75px; + background-position: 0 -85px; } .dashboard-module h2 a.toggle-icon.collapsed:hover { - background-position: 0 -95px; + background-position: 0 -105px; } .dashboard-module h2 a.close-icon { - background-position: 0 -115px; + background-position: 0 -125px; } .dashboard-module h2 a.close-icon:hover { - background-position: 0 -135px; + background-position: 0 -145px; } .fixed h2 span.toggle-icon, @@ -129,11 +128,9 @@ .dashboard-module h3 { padding: 5px 10px; margin: 0; - background-color: #f4f4f4; - background-image: url(../images/dashboard.png); - background-repeat: no-repeat; - background-position: 0 -175px; + background: transparent url(../images/admin-tools.png) repeat-x 0 -325px; text-indent: 12px; + height: 22px; } .dashboard-module h3 a { @@ -148,22 +145,22 @@ .dashboard-module ul li { vertical-align: top; - padding: 6px 8px 4px 8px; - line-height: 22px; + padding: 5px 8px; + line-height: 20px; } .dashboard-module ul li.odd { - background-color: #f4f4f4; + background: transparent url(../images/admin-tools.png) repeat-x 0 -325px; } .dashboard-module ul li.even { - background-color: #ffffff; + background: transparent url(../images/admin-tools.png) repeat-x 0 -285px; } .dashboard-module ul li a.external-link { - background-image: url(../images/dashboard.png); + background-image: url(../images/admin-tools.png); background-repeat: no-repeat; - background-position: -5px -158px; + background-position: -5px -168px; padding-left: 15px; } @@ -182,7 +179,7 @@ } .dashboard-module ul li:hover { - background-color: #fffff4; + background: #fffff4; } /* }}} */ diff --git a/admin_tools/media/admin_tools/css/menu.css b/admin_tools/media/admin_tools/css/menu.css index 8e9b409..6260762 100644 --- a/admin_tools/media/admin_tools/css/menu.css +++ b/admin_tools/media/admin_tools/css/menu.css @@ -1,50 +1,20 @@ -/** - * theming styles: - * todo: menu.css externalize this in the theming app - */ - -#header { - overflow: hidden; - height: auto; - background: url(../images/menu.png) 0 -135px repeat-x; -} - -#header h1 { - text-indent: -9999px; - margin: 5px 10px; - background: url(../images/django.png) 0 0 no-repeat; - height: 31px; - width: 93px; -} - -#header #user-tools { - padding-right: 10px; -} - -#container .breadcrumbs { - display: block; - padding: 10px 15px; - border: 0; - background-position: 0 -8px; - border-bottom: 1px solid #ededed; -} - -#container .breadcrumbs a { - position: relative; - float: none; - margin: 0; - padding: 0; -} - /* menu styles */ + +#header { + overflow: visible; +} + +#header #branding h1 { + margin: 0; + padding: 5px 10px; + height: 31px; +} + #header #navigation-wrapper { - border: 1px solid #ededed; - border-width: 1px 0 0 0; - background: url(../images/menu.png) 0 0 repeat-x; - display: block; - clear: both; + background: red url(../images/admin-tools.png) 0 -205px repeat-x; height: 35px; + z-index: 9999; } #header #navigation-wrapper ul { @@ -56,58 +26,40 @@ #header #navigation-wrapper ul li { list-style: none; -} - -#header #navigation-wrapper ul.menu { - position: absolute; -} - -#header #navigation-wrapper ul.menu:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; + position: relative; } #header #navigation-wrapper ul.menu li { float: left; - position: relative; } #header #navigation-wrapper ul.menu li a { - float: left; + display: block; text-transform: uppercase; text-decoration: none; - margin: 0; padding: 9px 15px; border-right: 1px solid #ededed; - color: #666; + color: #555; } #header #navigation-wrapper ul.menu li.first a { margin-left: 12px; - background: url(../images/menu.png) 0 -95px no-repeat; + background: transparent url(../images/admin-tools.png) 0 -380px no-repeat; text-indent: 14px; } #header #navigation-wrapper ul.menu li:hover, -#header #navigation-wrapper ul.menu li.hover { - background: url(../images/menu.png) repeat-x 0 -40px; -} - -#header #navigation-wrapper ul.menu li a.active { - color: #6389b8; - background: url(../images/admin.png) -320px -358px no-repeat; +#header #navigation-wrapper ul.menu li.hover, +#header #navigation-wrapper ul.menu li.active { + background: url(../images/admin-tools.png) repeat-x 0 -245px; } #header #navigation-wrapper ul li span.icon { - display: block; float: right; width: 20px; height: 10px; - background: url(../images/menu.png) no-repeat 0 -75px; + background: transparent url(../images/admin-tools.png) no-repeat 0 -360px; } #header #navigation-wrapper ul.menu li ul { @@ -115,56 +67,38 @@ position: absolute; top: 100%; left: 0; - background: #fff; -} - -#header #navigation-wrapper ul.menu li ul li ul { - left: 100%; - top: -1px; -} - -#header #navigation-wrapper ul li ul li a em { - display: none; + margin-left: -1px; + margin-top: -1px; } #header #navigation-wrapper ul.menu li ul li { - position: relative; - display: block; + padding: 0; float: none; background: none; - border: 1px solid #ededed; - border-width: 0 1px 1px 1px; - min-width: 170px; -} - -#header #navigation-wrapper ul.menu li ul li ul { - border-top: 1px solid #ededed; -} - -#header #navigation-wrapper ul.menu li ul li.first { - margin: 0; } #header #navigation-wrapper ul.menu li ul li a { float: none; display: block; - color: #666 !important; - border: 0; - padding: 7px 20px 7px 16px; - white-space: nowrap; + border: 1px solid #ededed; + border-width: 0 1px 1px 1px; text-transform: none; - background-color: #fafafa; - background-image: none; + min-width: 170px; + margin: 0; +} + +#header #navigation-wrapper ul.menu li ul li ul { + left: 100%; + top: 0; + border-top: 1px solid #ededed; +} + +#header #navigation-wrapper ul.menu li ul li ul li a { + white-space: nowrap; } #header #navigation-wrapper ul li ul li span.icon { - background-position: 0 -85px; - margin: -14px -10px 0 0; -} - -#header #navigation-wrapper ul.menu li:hover ul, -#header #navigation-wrapper ul.menu li.hover ul { - display: block; + background-position: 0 -370px; } #header #navigation-wrapper ul.menu li:hover ul li ul, @@ -172,27 +106,23 @@ display: none; } +#header #navigation-wrapper ul.menu li:hover ul, +#header #navigation-wrapper ul.menu li.hover ul, #header #navigation-wrapper ul.menu li ul li:hover ul, #header #navigation-wrapper ul.menu li ul li.hover ul { display: block; } #header #navigation-wrapper ul.menu li:hover ul li, -#header #navigation-wrapper ul.menu li.hover ul li { - background: none; +#header #navigation-wrapper ul.menu li.hover ul li, +#header #navigation-wrapper ul.menu li ul li:hover ul li, +#header #navigation-wrapper ul.menu li ul li.hover ul li { + background: transparent url(../images/admin-tools.png) repeat-x 0 -285px; } -#header #navigation-wrapper ul.menu li ul li:hover a, -#header #navigation-wrapper ul.menu li ul li.hover a { - background: #f3f3f4; -} - -#header #navigation-wrapper ul.menu li ul li:hover ul li a, -#header #navigation-wrapper ul.menu li ul li.hover ul li a { - background: none; -} - -#header #navigation-wrapper ul.menu li ul li ul li:hover a, -#header #navigation-wrapper ul.menu li ul li ul li.hover a { - background: #f3f3f4; +#header #navigation-wrapper ul.menu li ul li:hover, +#header #navigation-wrapper ul.menu li ul li.hover, +#header #navigation-wrapper ul.menu li ul li ul li:hover, +#header #navigation-wrapper ul.menu li ul li ul li.hover { + background: transparent url(../images/admin-tools.png) repeat-x 0 -245px; } diff --git a/admin_tools/media/admin_tools/css/theming.css b/admin_tools/media/admin_tools/css/theming.css index 0478793..c3ad7ad 100644 --- a/admin_tools/media/admin_tools/css/theming.css +++ b/admin_tools/media/admin_tools/css/theming.css @@ -1,36 +1,29 @@ -/* header logo and user tools */ +/** + * theming styles + * + */ #header { - overflow: visible; - height: auto; - background-color: #fff; + background: url(../images/admin-tools.png) 0 0 repeat-x; } -#header #branding { - border: 1px solid #ededed; - border-width: 1px 0; +#header #branding h1 { margin: 0; - background: url(../images/admin.png) 0 0 repeat-x; + padding: 5px 10px; + text-indent: -9999px; + background: transparent url(../images/django.png) 10px 5px no-repeat; + height: 31px; + width: 93px; } -#header #branding a { +div.breadcrumbs { display: block; - height: 32px; - width: 105px; - background: transparent url(../images/admin.png) 0 -122px no-repeat; + padding: 10px 15px; + border: 0; + background-position: 0 -8px; + border-bottom: 1px solid #ededed; } -#header #branding a h1 span { - display: none; -} - -#header #user-tools { - color: #656565; - font-size: 12px; - padding: 10px; -} - -#header #user-tools a:link, #header #user-tools a:visited { - color: #5b80b2; - text-decoration: none; +div.breadcrumbs a { + display: inline; } diff --git a/admin_tools/media/admin_tools/images/admin-tools.png b/admin_tools/media/admin_tools/images/admin-tools.png new file mode 100644 index 0000000000000000000000000000000000000000..8b1fc4b87aa89b09cf6f81918b116098b0495099 GIT binary patch literal 5487 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 diff --git a/admin_tools/media/admin_tools/images/dashboard.png b/admin_tools/media/admin_tools/images/dashboard.png deleted file mode 100644 index 2a55cfc50968faf5dcf4a56dd86f2e0c34e470e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2310 zcmV+h3HkPkP)Px#24YJ`L;zI)RRC3Z#W10k4%4_I5Vp!N{9)OOvkxOCp<%3e%4YB~>LL@U7Sb8A4bvu&u||}Ptr!-z zq;XVDUT`uea%4$&A9U~2eb4VnPi{I|qGPc1?!7wqoc}rJf9`vdSXfw)+(QZJH$r$X z5S|ypcU`EaNPrX|1VT*;QF|u^=uv?{2(`JWuSW>9n_<)x z))$AccX!z@^jlF=7{a#F-b5=G!rnY&TW?O*?NAm7r0t2c#}N|Yf;5a;R8u4z5i(;S z0Hm@+x+m%%3)TGgkD7KF?UoP@NKBmq2-jVe#;~kKU8AosmugB-i>i(v%0nq-&1KUj zX$Hb&f^fVyUNuBQc`FQLO^@vCv)wiE6>hD7sJU#Xk@86zQI|+G8g(DkxapNj%&?>E z1j};L?WXjoLNg~qj)-BDl=9I`S}B|W3RlV~3+vb+mH9^QYMTVQK9!qB{z^k>*j;iV z>KTJs+s=%30Ao)HReIWOJAN)Mt3_J6=u0{<*Up4%6eMkpQc62j& zn6xdo?GM=;XqHk)^SrV+w2W11szb}_I*dX8QaVZ0Ne(J#MnrY6Z1$FtkTh`-CRS!E zF!E2y*@bz zvpKrl6U)1ei>}-R9Tu*R?#wFbZfwSBLJ_T0IV@CeRIAl{=(2DZrZAXCI8w;Nc_-QG z43M%ak;(yGVg^%5eL8S70bvxMbVfi*`N5nIKeP(lazDtjdg|%v`BBghXEK?ey2rvE z>#hJ#1-i2Gc$818jW5Dc6dzxJd?@% zqa}q(>Gfzdx(;~M#4VG_Os$w4TzLB8)+*Z^1ODC!x3;vUE%!lBiN#`pdrCH&4b)TS zfL#Ify%ChYeiNjpsCCU1X0zElz-W`6GOE@EYXP}j?q(zsnFqFeO#eEY&0cD`r{r?E zE0IW~6WDCxmd$2Qt+)kbv)NIzr>N_8PiaeA+HwziipPCRC-6M*%Sn*vga7*}nCZXnA!Y~P} z^O;-M2+vhbaPt#iJiSg1n^g~KCY#$;*Os>2$2}z$i`hLTo6Ua6dP*!7O8{Te92kj2 zI&-<)TPso+i^X;U-*j_eBoZm)a=9xlxg3kdHhVNEEq!V}n8H{rwoWyuMm;5=)&;V# zF95y!df?Sl9t_x19&A@#TiWtLG4>|lAz-aqTLf~zHOw!a11Map4xJl(=Kl-)1-KoA zZB|qJ4q(HcJ$pXko>XHUz)oOI5Evcz4i3Ixt^p#=qG~SgK7aoFt5V6ibLU=h&jX)n6qJ&Akxr+d+_7WF zGv#u5v0N@M?%cWa+v#-rN!MrYHnMOv@O7Yje0=;!wOXx|N~POFLqmgx`<3zW@gvH* zZ>SV&9_4sE{@nch{MDhMp+OVV`T6;)@p$|>zbFeID<20w-eluN;7#?~@Z?Zc<<-$Z z>3>wE6kDz3PXQZM2?}7I+C$&jHVwAr|JYL=1G-d_hk#q^sCP*fKwyRIfo~}~^O6EE z0o(}EQ`Q2{0-L(Jx*l*(shMgO8j#b*g{;`t=KE&z^k`U}R)u z>)6=XcNP{F-n($&!fge`JB@nET1`oFb8~mA)v8P;lTX`sK*{9OrBbOhJ3Fh}=NExL z05|vA9^NRI0^z>igm`KC<~$O24FP0bbx zg}MIz{*x0E6SET&6SMvO{U-~B!rau3;`a_zm8>DT(tJG(F4yF{fs}K&$Eq-WZ%8fILdCfjVK?r zENd{I&pYNV%i<#20JxZXz9pAe=kxg^zz@B!1=RoGE{E0Q(Zi;74Wr!1b+zsmwI{$2+U={as$*7ut!Yl*%F1_l}` gS}`BG1L&6j0TR{{2Epi2hX4Qo07*qoM6N<$f-@9WRR910 diff --git a/admin_tools/media/admin_tools/images/menu.png b/admin_tools/media/admin_tools/images/menu.png deleted file mode 100644 index 8e554ebb19032a25e560226fb1690bab5d731dbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3262 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 diff --git a/admin_tools/theming/templates/admin/base.html b/admin_tools/theming/templates/admin/base.html new file mode 100644 index 0000000..a36eb70 --- /dev/null +++ b/admin_tools/theming/templates/admin/base.html @@ -0,0 +1,81 @@ +{% load theming_tags %} + + + +{% block title %}{% endblock %} + +{% block extrastyle %}{% endblock %} + +{% if LANGUAGE_BIDI %}{% endif %} +{% render_theming_css %} +{% block extrahead %}{% endblock %} +{% block blockbots %}{% endblock %} + +{% load i18n %} + + + + +
+ + {% if not is_popup %} + + + + {% block breadcrumbs %}{% endblock %} + {% endif %} + + {% if messages %} +
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
+ {% endif %} + + +
+ {% block pretitle %}{% endblock %} + {% block content_title %}{% if title %}

{{ title }}

{% endif %}{% endblock %} + {% block content %} + {% block object-tools %}{% endblock %} + {{ content }} + {% endblock %} + {% block sidebar %}{% endblock %} +
+
+ + + {% block footer %}{% endblock %} +
+ + + + diff --git a/admin_tools/theming/templatetags/__init__.py b/admin_tools/theming/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin_tools/theming/templatetags/theming_tags.py b/admin_tools/theming/templatetags/theming_tags.py new file mode 100644 index 0000000..ca2fbc4 --- /dev/null +++ b/admin_tools/theming/templatetags/theming_tags.py @@ -0,0 +1,28 @@ +""" +Theming template tags. + +To load the theming tags just do: ``{% load theming_tags %}``. +""" + +import os.path +from django import template +from django.conf import settings + +register = template.Library() + +def render_theming_css(): + """ + Template tag that renders the needed css files for the theming app. + """ + css = getattr(settings, 'ADMIN_TOOLS_THEMING_CSS', False) + if css: + css = os.path.join(settings.MEDIA_URL, css) + else: + css = os.path.join( + getattr(settings, 'ADMIN_TOOLS_MEDIA_URL', settings.MEDIA_URL), + 'admin_tools', + 'css', + 'theming.css' + ) + return '' % css +register.simple_tag(render_theming_css) From 31fc172856b57de243907737c89f6d16c823dbf4 Mon Sep 17 00:00:00 2001 From: David Jean Louis Date: Fri, 5 Feb 2010 13:51:05 +0100 Subject: [PATCH 003/378] Some IE6/7 fixes, but there are still some bugs :( --- admin_tools/dashboard/models.py | 5 +- .../dashboard/modules/recent_actions.html | 2 +- .../media/admin_tools/css/dashboard-ie.css | 18 ++++ .../media/admin_tools/css/dashboard.css | 3 + admin_tools/media/admin_tools/css/menu-ie.css | 10 ++ admin_tools/media/admin_tools/css/menu.css | 95 +++++++++---------- admin_tools/menu/models.py | 5 +- admin_tools/menu/templates/menu/item.html | 2 +- admin_tools/menu/templates/menu/menu.html | 18 +++- admin_tools/utils.py | 13 ++- 10 files changed, 114 insertions(+), 57 deletions(-) create mode 100644 admin_tools/media/admin_tools/css/dashboard-ie.css create mode 100644 admin_tools/media/admin_tools/css/menu-ie.css diff --git a/admin_tools/dashboard/models.py b/admin_tools/dashboard/models.py index 99224e1..f807366 100644 --- a/admin_tools/dashboard/models.py +++ b/admin_tools/dashboard/models.py @@ -31,7 +31,10 @@ class Dashboard(list): """ class Media: - css = {'all': 'dashboard.css'} + css = { + 'all': 'dashboard.css', + 'ie': 'dashboard-ie.css', + } js = ( 'jquery/jquery-1.4.1.min.js', 'jquery/jquery-ui-1.8rc1.custom.min.js', diff --git a/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html b/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html index 91cca8e..dae9859 100644 --- a/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html +++ b/admin_tools/dashboard/templates/dashboard/modules/recent_actions.html @@ -5,12 +5,12 @@ {% spaceless %} {% for entry in module.entries %}
  • + {{ entry.action_time|date }} {% if entry.is_deletion %} {% if entry.content_type %}{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %} {% endif %}{{ entry.object_repr }} {% else %} {% if entry.content_type %}{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %} {% endif %}{{ entry.object_repr }} {% endif %} - {{ entry.action_time|date }}
  • {% endfor %} {% endspaceless %} diff --git a/admin_tools/media/admin_tools/css/dashboard-ie.css b/admin_tools/media/admin_tools/css/dashboard-ie.css new file mode 100644 index 0000000..45b1b09 --- /dev/null +++ b/admin_tools/media/admin_tools/css/dashboard-ie.css @@ -0,0 +1,18 @@ +/* todo: this fucker of ie6 doesn't want the dashboard to take full width... */ + +#content { + *min-width: 98%; +} + +.dashboard-module h2 a.toggle-icon, +.dashboard-module h2 a.close-icon { + margin-top: -16px; +} + +.dashboard-module ul li ul { + margin-top: -25px; +} + +.dashboard-module h3 { + clear: both; +} diff --git a/admin_tools/media/admin_tools/css/dashboard.css b/admin_tools/media/admin_tools/css/dashboard.css index ef37cf5..fd1a75b 100644 --- a/admin_tools/media/admin_tools/css/dashboard.css +++ b/admin_tools/media/admin_tools/css/dashboard.css @@ -24,6 +24,9 @@ #dashboard-panel ul { display: none; position: absolute; + top: auto; + left: auto; + margin-top: -1px; } #dashboard-panel ul li { diff --git a/admin_tools/media/admin_tools/css/menu-ie.css b/admin_tools/media/admin_tools/css/menu-ie.css new file mode 100644 index 0000000..4b9e26f --- /dev/null +++ b/admin_tools/media/admin_tools/css/menu-ie.css @@ -0,0 +1,10 @@ +#header #navigation-menu li ul li { + margin: 0; + height: 34px; + /* IE6 only */ + _width: 200px; +} + +#header #navigation-menu li ul li a { + height: 20px; +} diff --git a/admin_tools/media/admin_tools/css/menu.css b/admin_tools/media/admin_tools/css/menu.css index 6260762..81596d4 100644 --- a/admin_tools/media/admin_tools/css/menu.css +++ b/admin_tools/media/admin_tools/css/menu.css @@ -11,29 +11,29 @@ height: 31px; } -#header #navigation-wrapper { - background: red url(../images/admin-tools.png) 0 -205px repeat-x; - height: 35px; - z-index: 9999; -} - -#header #navigation-wrapper ul { +#header ul { list-style: none; margin: 0; padding: 0; z-index: 9999; } -#header #navigation-wrapper ul li { - list-style: none; +#header ul li { position: relative; + list-style: none; } -#header #navigation-wrapper ul.menu li { +#header #navigation-menu { + background: transparent url(../images/admin-tools.png) 0 -205px repeat-x; + height: 35px; + z-index: 9999; +} + +#header ul#navigation-menu li { float: left; } -#header #navigation-wrapper ul.menu li a { +#header ul#navigation-menu li a { display: block; text-transform: uppercase; text-decoration: none; @@ -42,87 +42,86 @@ color: #555; } -#header #navigation-wrapper ul.menu li.first a { + +#header ul#navigation-menu li.first a { margin-left: 12px; background: transparent url(../images/admin-tools.png) 0 -380px no-repeat; text-indent: 14px; } - -#header #navigation-wrapper ul.menu li:hover, -#header #navigation-wrapper ul.menu li.hover, -#header #navigation-wrapper ul.menu li.active { - background: url(../images/admin-tools.png) repeat-x 0 -245px; -} - -#header #navigation-wrapper ul li span.icon { +#header ul#navigation-menu li span { float: right; - width: 20px; height: 10px; + width: 20px; background: transparent url(../images/admin-tools.png) no-repeat 0 -360px; } -#header #navigation-wrapper ul.menu li ul { +#header ul#navigation-menu li:hover, +#header ul#navigation-menu li.over, +#header ul#navigation-menu li.active { + background: url(../images/admin-tools.png) repeat-x 0 -245px; +} + +#header ul#navigation-menu li ul { display: none; position: absolute; - top: 100%; - left: 0; + top: auto; + left: auto; margin-left: -1px; margin-top: -1px; } -#header #navigation-wrapper ul.menu li ul li { +#header ul#navigation-menu li ul li { padding: 0; float: none; background: none; + z-index: 9999; } -#header #navigation-wrapper ul.menu li ul li a { - float: none; +#header ul#navigation-menu li ul li a { display: block; border: 1px solid #ededed; border-width: 0 1px 1px 1px; text-transform: none; min-width: 170px; - margin: 0; } -#header #navigation-wrapper ul.menu li ul li ul { +#header ul#navigation-menu li ul li span { + background-position: 0 -370px; +} + +#header ul#navigation-menu li ul li ul { left: 100%; top: 0; border-top: 1px solid #ededed; } -#header #navigation-wrapper ul.menu li ul li ul li a { +#header ul#navigation-menu li ul li ul li a { white-space: nowrap; } -#header #navigation-wrapper ul li ul li span.icon { - background-position: 0 -370px; -} - -#header #navigation-wrapper ul.menu li:hover ul li ul, -#header #navigation-wrapper ul.menu li.hover ul li ul { +#header ul#navigation-menu li:hover ul li ul, +#header ul#navigation-menu li.over ul li ul { display: none; } -#header #navigation-wrapper ul.menu li:hover ul, -#header #navigation-wrapper ul.menu li.hover ul, -#header #navigation-wrapper ul.menu li ul li:hover ul, -#header #navigation-wrapper ul.menu li ul li.hover ul { +#header ul#navigation-menu li:hover ul, +#header ul#navigation-menu li.over ul, +#header ul#navigation-menu li ul li:hover ul, +#header ul#navigation-menu li ul li.over ul { display: block; } -#header #navigation-wrapper ul.menu li:hover ul li, -#header #navigation-wrapper ul.menu li.hover ul li, -#header #navigation-wrapper ul.menu li ul li:hover ul li, -#header #navigation-wrapper ul.menu li ul li.hover ul li { +#header ul#navigation-menu li:hover ul li, +#header ul#navigation-menu li.over ul li, +#header ul#navigation-menu li ul li:hover ul li, +#header ul#navigation-menu li ul li.over ul li { background: transparent url(../images/admin-tools.png) repeat-x 0 -285px; } -#header #navigation-wrapper ul.menu li ul li:hover, -#header #navigation-wrapper ul.menu li ul li.hover, -#header #navigation-wrapper ul.menu li ul li ul li:hover, -#header #navigation-wrapper ul.menu li ul li ul li.hover { +#header ul#navigation-menu li ul li:hover, +#header ul#navigation-menu li ul li.over, +#header ul#navigation-menu li ul li ul li:hover, +#header ul#navigation-menu li ul li ul li.over { background: transparent url(../images/admin-tools.png) repeat-x 0 -245px; } diff --git a/admin_tools/menu/models.py b/admin_tools/menu/models.py index bcec39d..5636925 100644 --- a/admin_tools/menu/models.py +++ b/admin_tools/menu/models.py @@ -28,7 +28,10 @@ class Menu(list): """ class Media: - css = {'all': 'menu.css'} + css = { + 'all': 'menu.css', + 'ie' : 'menu-ie.css' + } js = () def __init__(self, *args, **kwargs): diff --git a/admin_tools/menu/templates/menu/item.html b/admin_tools/menu/templates/menu/item.html index 1fce109..34631d8 100644 --- a/admin_tools/menu/templates/menu/item.html +++ b/admin_tools/menu/templates/menu/item.html @@ -1,7 +1,7 @@ {% load menu_tags %} {% spaceless %}