summaryrefslogtreecommitdiff
path: root/demo/grappelli/dashboard
diff options
context:
space:
mode:
authorgnowgi <nagarjun@gnowledge.org>2012-03-15 16:19:20 +0530
committergnowgi <nagarjun@gnowledge.org>2012-03-15 16:19:20 +0530
commit7a4f561e851fdc7246d804c3abb6748b8a4199a6 (patch)
treed2afc3463fd49625a9be482012f5c3bfcf7c42b9 /demo/grappelli/dashboard
downloadgnowsys-7a4f561e851fdc7246d804c3abb6748b8a4199a6.tar.gz
master trunk of gnowsys-studio
Diffstat (limited to 'demo/grappelli/dashboard')
-rw-r--r--demo/grappelli/dashboard/__init__.py2
-rw-r--r--demo/grappelli/dashboard/dashboards.py257
-rw-r--r--demo/grappelli/dashboard/management/__init__.py0
-rw-r--r--demo/grappelli/dashboard/management/commands/__init__.py0
-rw-r--r--demo/grappelli/dashboard/management/commands/customdashboard.py93
-rw-r--r--demo/grappelli/dashboard/modules.py456
-rw-r--r--demo/grappelli/dashboard/registry.py124
-rw-r--r--demo/grappelli/dashboard/templates/admin/index.html30
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.html27
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.txt114
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/dummy.html1
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/module.html32
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/app_list.html34
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/feed.html14
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/group.html7
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/link_list.html13
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/model_list.html15
-rw-r--r--demo/grappelli/dashboard/templates/grappelli/dashboard/modules/recent_actions.html15
-rw-r--r--demo/grappelli/dashboard/templatetags/__init__.py0
-rw-r--r--demo/grappelli/dashboard/templatetags/grp_dashboard_tags.py129
-rw-r--r--demo/grappelli/dashboard/utils.py229
21 files changed, 1592 insertions, 0 deletions
diff --git a/demo/grappelli/dashboard/__init__.py b/demo/grappelli/dashboard/__init__.py
new file mode 100644
index 00000000..a88a662d
--- /dev/null
+++ b/demo/grappelli/dashboard/__init__.py
@@ -0,0 +1,2 @@
+from grappelli.dashboard.dashboards import *
+from grappelli.dashboard.registry import *
diff --git a/demo/grappelli/dashboard/dashboards.py b/demo/grappelli/dashboard/dashboards.py
new file mode 100644
index 00000000..8c2952fa
--- /dev/null
+++ b/demo/grappelli/dashboard/dashboards.py
@@ -0,0 +1,257 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Module where admin tools dashboard classes are defined.
+"""
+
+from django.template.defaultfilters import slugify
+from django.utils.importlib import import_module
+from django.utils.translation import ugettext_lazy as _
+from django.core.urlresolvers import reverse
+from django.contrib.contenttypes.models import ContentType
+
+from grappelli.dashboard import modules
+from grappelli.dashboard.utils import get_admin_site_name
+from django import forms
+
+class Dashboard(object):
+ """
+ Base class for dashboards.
+ The Dashboard class is a simple python list that has three additional
+ properties:
+
+ ``title``
+ The dashboard title, by default, it is displayed above the dashboard
+ in a ``h2`` tag. Default value: 'Dashboard'.
+
+ ``template``
+ The template to use to render the dashboard.
+ Default value: 'admin_tools/dashboard/dashboard.html'
+
+ ``columns``
+ An integer that represents the number of columns for the dashboard.
+ Default value: 2.
+
+ If you want to customize the look of your dashboard and it's modules, you
+ can declare css stylesheets and/or javascript files to include when
+ rendering the dashboard (these files should be placed in your
+ media path), for example::
+
+ from admin_tools.dashboard import Dashboard
+
+ class MyDashboard(Dashboard):
+ class Media:
+ css = {
+ 'all': (
+ 'css/mydashboard.css',
+ 'css/mystyles.css',
+ ),
+ }
+ js = (
+ 'js/mydashboard.js',
+ 'js/myscript.js',
+ )
+
+ Here's an example of a custom dashboard::
+
+ from django.core.urlresolvers import reverse
+ from django.utils.translation import ugettext_lazy as _
+ from admin_tools.dashboard import modules, Dashboard
+
+ class MyDashboard(Dashboard):
+
+ # we want a 3 columns layout
+ columns = 3
+
+ def __init__(self, **kwargs):
+
+ # append an app list module for "Applications"
+ self.children.append(modules.AppList(
+ title=_('Applications'),
+ exclude=('django.contrib.*',),
+ ))
+
+ # append an app list module for "Administration"
+ self.children.append(modules.AppList(
+ title=_('Administration'),
+ models=('django.contrib.*',),
+ ))
+
+ # append a recent actions module
+ self.children.append(modules.RecentActions(
+ title=_('Recent Actions'),
+ limit=5
+ ))
+
+ """
+
+ # Using Django's Media meta class
+ __metaclass__ = forms.MediaDefiningClass
+ def _media(self):
+ return forms.Media()
+ media = property(_media)
+
+ title = _('Dashboard')
+ template = 'grappelli/dashboard/dashboard.html'
+ columns = 2
+ children = None
+
+ def __init__(self, **kwargs):
+ for key in kwargs:
+ if hasattr(self.__class__, key):
+ setattr(self, key, kwargs[key])
+ self.children = self.children or []
+
+ def init_with_context(self, context):
+ """
+ Sometimes you may need to access context or request variables to build
+ your dashboard, this is what the ``init_with_context()`` method is for.
+ This method is called just before the display with a
+ ``django.template.RequestContext`` as unique argument, so you can
+ access to all context variables and to the ``django.http.HttpRequest``.
+ """
+ pass
+
+ def get_id(self):
+ """
+ Internal method used to distinguish different dashboards in js code.
+ """
+ return 'dashboard'
+
+
+class DefaultIndexDashboard(Dashboard):
+ """
+ The default dashboard displayed on the admin index page.
+ To change the default dashboard you'll have to type the following from the
+ commandline in your project root directory::
+
+ python manage.py customdashboard
+
+ And then set the `GRAPPELLI_INDEX_DASHBOARD`` settings variable to
+ point to your custom index dashboard class.
+ """
+
+ def init_with_context(self, context):
+ site_name = get_admin_site_name(context)
+ # append a link list module for "quick links"
+ self.children.append(modules.LinkList(
+ _('Quick links'),
+ layout='inline',
+ draggable=False,
+ deletable=False,
+ collapsible=False,
+ children=[
+ [_('Return to site'), '/'],
+ [_('Change password'),
+ reverse('%s:password_change' % site_name)],
+ [_('Log out'), reverse('%s:logout' % site_name)],
+ ]
+ ))
+
+ # append an app list module for "Applications"
+ self.children.append(modules.AppList(
+ _('Applications'),
+ exclude=('django.contrib.*',),
+ ))
+
+ # append an app list module for "Administration"
+ self.children.append(modules.AppList(
+ _('Administration'),
+ models=('django.contrib.*',),
+ ))
+
+ # append a recent actions module
+ self.children.append(modules.RecentActions(_('Recent Actions'), 5))
+
+ # append a feed module
+ self.children.append(modules.Feed(
+ _('Latest Django News'),
+ feed_url='http://www.djangoproject.com/rss/weblog/',
+ limit=5
+ ))
+
+ # append another link list module for "support".
+ self.children.append(modules.LinkList(
+ _('Support'),
+ children=[
+ {
+ '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,
+ },
+ ]
+ ))
+
+
diff --git a/demo/grappelli/dashboard/management/__init__.py b/demo/grappelli/dashboard/management/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/demo/grappelli/dashboard/management/__init__.py
diff --git a/demo/grappelli/dashboard/management/commands/__init__.py b/demo/grappelli/dashboard/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/demo/grappelli/dashboard/management/commands/__init__.py
diff --git a/demo/grappelli/dashboard/management/commands/customdashboard.py b/demo/grappelli/dashboard/management/commands/customdashboard.py
new file mode 100644
index 00000000..d22eba98
--- /dev/null
+++ b/demo/grappelli/dashboard/management/commands/customdashboard.py
@@ -0,0 +1,93 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+# coding: utf-8
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#-----------------------------------------------------------------------------
+
+# PYTHON IMPORTS
+import os
+
+# DJANGO IMPORTS
+from django.core.management.base import BaseCommand, CommandError
+from django.template.loader import render_to_string
+
+DEFAULT_FILE = 'dashboard.py'
+
+
+class Command(BaseCommand):
+ help = ('Creates a template file containing the base code to get you '
+ 'started with your custom dashboard.')
+ args = '[file]'
+ label = 'application name'
+
+ def handle(self, file=None, **options):
+ context = {}
+ context['project'] = os.path.basename(os.getcwd())
+ tpl = ['dashboard/dashboard.txt', 'grappelli/dashboard/dashboard.txt']
+ dst = file is not None and file or DEFAULT_FILE
+ if os.path.exists(dst):
+ raise CommandError('file "%s" already exists' % dst)
+ context['file'] = os.path.basename(dst).split('.')[0]
+ open(dst, 'w').write(render_to_string(tpl, context))
+ print '"%s" written.' % os.path.join(dst)
+
+
diff --git a/demo/grappelli/dashboard/modules.py b/demo/grappelli/dashboard/modules.py
new file mode 100644
index 00000000..466cfbd0
--- /dev/null
+++ b/demo/grappelli/dashboard/modules.py
@@ -0,0 +1,456 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Module where grappelli dashboard modules classes are defined.
+"""
+
+# DJANGO IMPORTS
+from django.utils.text import capfirst
+from django.core.urlresolvers import reverse
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext_lazy as _
+from django.utils.itercompat import is_iterable
+
+# GRAPPELL IMPORTS
+from grappelli.dashboard.utils import AppListElementMixin
+
+
+class DashboardModule(object):
+ """
+ Base class for all dashboard modules.
+ Dashboard modules have the following properties:
+
+ ``collapsible``
+ Boolean that determines whether the module is collapsible, this
+ allows users to show/hide module content. Default: ``True``.
+
+ ``column``
+ Integer that corresponds to the column.
+ Default: None.
+
+ ``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: 'grappelli/dashboard/module.html'.
+ """
+
+ template = 'grappelli/dashboard/module.html'
+ collapsible = True
+ column = None
+ show_title = True
+ title = ''
+ title_url = None
+ css_classes = None
+ pre_content = None
+ post_content = None
+ children = None
+
+ def __init__(self, title=None, **kwargs):
+ if title is not None:
+ self.title = title
+ for key in kwargs:
+ if hasattr(self.__class__, key):
+ setattr(self, key, kwargs[key])
+ self.children = self.children or []
+ self.css_classes = self.css_classes or []
+ # boolean flag to ensure that the module is initialized only once
+ self._initialized = False
+
+ def init_with_context(self, context):
+ """
+ Like for the :class:`~grappelli.dashboard.Dashboard` class, dashboard
+ modules have a ``init_with_context`` method that is called with a
+ ``django.template.RequestContext`` instance as unique argument.
+
+ This gives you enough flexibility to build complex modules, for
+ example, let's build a "history" dashboard module, that will list the
+ last ten visited pages::
+
+ from grappelli.dashboard import modules
+
+ class HistoryDashboardModule(modules.LinkList):
+ title = 'History'
+
+ def init_with_context(self, context):
+ request = context['request']
+ # we use sessions to store the visited pages stack
+ history = request.session.get('history', [])
+ for item in history:
+ self.children.append(item)
+ # add the current page to the history
+ history.insert(0, {
+ 'title': context['title'],
+ 'url': request.META['PATH_INFO']
+ })
+ if len(history) > 10:
+ history = history[:10]
+ request.session['history'] = history
+
+ """
+ pass
+
+ def is_empty(self):
+ """
+ Return True if the module has no content and False otherwise.
+ """
+
+ return self.pre_content is None and \
+ self.post_content is None and \
+ len(self.children) == 0
+
+ def render_css_classes(self):
+ """
+ Return a string containing the css classes for the module.
+ """
+
+ ret = ['dashboard-module']
+ if self.collapsible:
+ ret.append('collapsible')
+ ret += self.css_classes
+ return ' '.join(ret)
+
+
+class Group(DashboardModule):
+ """
+ Represents a group of modules.
+
+ Here's an example of modules group::
+
+ from grappelli.dashboard import modules, Dashboard
+
+ class MyDashboard(Dashboard):
+ def __init__(self, **kwargs):
+ Dashboard.__init__(self, **kwargs)
+ self.children.append(modules.Group(
+ title="My group",
+ children=[
+ modules.AppList(
+ title='Administration',
+ models=('django.contrib.*',)
+ ),
+ modules.AppList(
+ title='Applications',
+ exclude=('django.contrib.*',)
+ )
+ ]
+ ))
+
+ """
+
+ template = 'grappelli/dashboard/modules/group.html'
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ for module in self.children:
+ module.init_with_context(context)
+ self._initialized = True
+
+ def is_empty(self):
+ """
+ A group of modules is considered empty if it has no children or if
+ all its children are empty.
+ """
+
+ if super(Group, self).is_empty():
+ return True
+ for child in self.children:
+ if not child.is_empty():
+ return False
+ return True
+
+
+class LinkList(DashboardModule):
+ """
+ A module that displays a list of links.
+ """
+
+ title = _('Links')
+ template = 'grappelli/dashboard/modules/link_list.html'
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ new_children = []
+ for link in self.children:
+ if isinstance(link, (tuple, list,)):
+ link_dict = {'title': link[0], 'url': link[1]}
+ if len(link) >= 3:
+ link_dict['external'] = link[2]
+ if len(link) >= 4:
+ link_dict['description'] = link[3]
+ new_children.append(link_dict)
+ else:
+ new_children.append(link)
+ self.children = new_children
+ self._initialized = True
+
+
+class AppList(DashboardModule, AppListElementMixin):
+ """
+ Module that lists installed apps and their models.
+ """
+
+ title = _('Applications')
+ template = 'grappelli/dashboard/modules/app_list.html'
+ models = None
+ exclude = None
+
+ def __init__(self, title=None, **kwargs):
+ self.models = list(kwargs.pop('models', []))
+ self.exclude = list(kwargs.pop('exclude', []))
+ super(AppList, self).__init__(title, **kwargs)
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ items = self._visible_models(context['request'])
+ apps = {}
+ for model, perms in items:
+ app_label = model._meta.app_label
+ if app_label not in apps:
+ apps[app_label] = {
+ 'title': capfirst(app_label.title()),
+ 'url': self._get_admin_app_list_url(model, context),
+ '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, context)
+ if perms['add']:
+ model_dict['add_url'] = self._get_admin_add_url(model, context)
+ 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.children.append(apps[app])
+ self._initialized = True
+
+
+class ModelList(DashboardModule, AppListElementMixin):
+ """
+ Module that lists a set of models.
+ """
+
+ template = 'grappelli/dashboard/modules/model_list.html'
+ models = None
+ exclude = None
+
+ def __init__(self, title=None, models=None, exclude=None, **kwargs):
+ self.models = list(models or [])
+ self.exclude = list(exclude or [])
+ super(ModelList, self).__init__(title, **kwargs)
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ items = self._visible_models(context['request'])
+ if not items:
+ return
+ for model, perms in items:
+ model_dict = {}
+ model_dict['title'] = capfirst(model._meta.verbose_name_plural)
+ if perms['change']:
+ model_dict['change_url'] = self._get_admin_change_url(model, context)
+ if perms['add']:
+ model_dict['add_url'] = self._get_admin_add_url(model, context)
+ self.children.append(model_dict)
+ self._initialized = True
+
+
+class RecentActions(DashboardModule):
+ """
+ Module that lists the recent actions for the current user.
+ """
+
+ title = _('Recent Actions')
+ template = 'grappelli/dashboard/modules/recent_actions.html'
+ limit = 10
+ include_list = None
+ exclude_list = None
+
+ def __init__(self, title=None, limit=10, include_list=None,
+ exclude_list=None, **kwargs):
+ self.include_list = include_list or []
+ self.exclude_list = exclude_list or []
+ kwargs.update({'limit': limit})
+ super(RecentActions, self).__init__(title, **kwargs)
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ from django.db.models import Q
+ from django.contrib.admin.models import LogEntry
+
+ request = context['request']
+
+ def get_qset(list):
+ qset = None
+ for contenttype in list:
+ if isinstance(contenttype, ContentType):
+ current_qset = Q(content_type__id=contenttype.id)
+ else:
+ try:
+ app_label, model = contenttype.split('.')
+ except:
+ raise ValueError('Invalid contenttype: "%s"' % contenttype)
+ current_qset = Q(
+ content_type__app_label=app_label,
+ content_type__model=model
+ )
+ if qset is None:
+ qset = current_qset
+ else:
+ qset = qset | current_qset
+ return qset
+
+ if request.user is None:
+ qs = LogEntry.objects.all()
+ else:
+ qs = LogEntry.objects.filter(user__id__exact=request.user.id)
+
+ if self.include_list:
+ qs = qs.filter(get_qset(self.include_list))
+ if self.exclude_list:
+ qs = qs.exclude(get_qset(self.exclude_list))
+
+ self.children = qs.select_related('content_type', 'user')[:self.limit]
+ if not len(self.children):
+ self.pre_content = _('No recent actions.')
+ self._initialized = True
+
+
+class Feed(DashboardModule):
+ """
+ Class that represents a feed dashboard module.
+ """
+
+ title = _('RSS Feed')
+ template = 'grappelli/dashboard/modules/feed.html'
+ feed_url = None
+ limit = None
+
+ def __init__(self, title=None, feed_url=None, limit=None, **kwargs):
+ kwargs.update({'feed_url': feed_url, 'limit': limit})
+ super(Feed, self).__init__(title, **kwargs)
+
+ def init_with_context(self, context):
+ if self._initialized:
+ return
+ import datetime
+ if self.feed_url is None:
+ raise ValueError('You must provide a valid feed URL')
+ try:
+ import feedparser
+ except ImportError:
+ self.children.append({
+ 'title': ('You must install the FeedParser python module'),
+ 'warning': True,
+ })
+ return
+
+ 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.children.append(entry)
+ self._initialized = True
+
+
diff --git a/demo/grappelli/dashboard/registry.py b/demo/grappelli/dashboard/registry.py
new file mode 100644
index 00000000..fd12d266
--- /dev/null
+++ b/demo/grappelli/dashboard/registry.py
@@ -0,0 +1,124 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+# coding: utf-8
+
+class Registry(object):
+ """
+ Registry for application dashboards.
+ """
+ registry = {}
+
+ def register(cls, klass, app_name):
+ from grappelli.dashboard.dashboards import Dashboard
+ if not issubclass(klass, Dashboard):
+ raise ValueError('%s is not an instance of Dashboard' % klass)
+ if app_name in cls.registry:
+ raise ValueError('A dashboard has already been registered for '
+ 'the application "%s"', app_name)
+ cls.registry[app_name] = klass
+ register = classmethod(register)
+
+
+def register(cls, *args, **kwargs):
+ """
+ Register a custom dashboard into the global registry.
+ """
+ Registry.register(cls, *args, **kwargs)
+
+
+def autodiscover(blacklist=[]):
+ """
+ Automagically discover custom dashboards and menus for installed apps.
+ Optionally you can pass a ``blacklist`` of apps that you don't want to
+ provide their own app index dashboard.
+ """
+ import imp
+ from django.conf import settings
+ from django.utils.importlib import import_module
+
+ blacklist.append('grappelli')
+ blacklist.append('grappelli.dashboard')
+
+ for app in settings.INSTALLED_APPS:
+ # skip blacklisted apps
+ if app in blacklist:
+ continue
+
+ # try to import the app
+ try:
+ app_path = import_module(app).__path__
+ except AttributeError:
+ continue
+
+ # try to find a app.dashboard module
+ try:
+ imp.find_module('dashboard', app_path)
+ except ImportError:
+ continue
+
+ # looks like we found it so import it !
+ import_module('%s.dashboard' % app)
+
+
diff --git a/demo/grappelli/dashboard/templates/admin/index.html b/demo/grappelli/dashboard/templates/admin/index.html
new file mode 100644
index 00000000..157aea51
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/admin/index.html
@@ -0,0 +1,30 @@
+{% extends "admin/base_site.html" %}
+
+<!-- LOADING -->
+{% load i18n adminmedia grp_tags log grp_dashboard_tags %}
+
+<!-- JAVASCRIPTS -->
+{% block javascripts %}
+ {{ block.super }}
+ <script type="text/javascript" charset="utf-8">
+ (function($) {
+ $(document).ready(function() {
+ $("div#content .collapse").grp_collapsible();
+ });
+ })(django.jQuery);
+ </script>
+{% endblock %}
+
+<!-- COLTYPE/BODYCLASS-- >
+{% block bodyclass %}dashboard{% endblock %}
+{% block content-class %}content-grid{% endblock %}
+
+<!-- BREADCRUMBS -->
+{% block breadcrumbs %}<div id="breadcrumbs">{% trans "Home" %}</div>{% endblock %}
+{% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
+
+<!-- CONTENT -->
+{% block content %}
+{% grp_render_dashboard %}
+{% endblock %}
+
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.html
new file mode 100644
index 00000000..839d3ccb
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.html
@@ -0,0 +1,27 @@
+{% load i18n grp_dashboard_tags %}
+
+{{ dashboard.media }}
+
+<div class="container-grid">
+ <div class="column span-12" id="column_1">
+ {% for module in dashboard.children %}
+ {% if module.column == 1 %}
+ {% grp_render_dashboard_module module forloop.counter %}
+ {% endif %}
+ {% endfor %}
+ </div>
+ <div class="column span-6" id="column_2">
+ {% for module in dashboard.children %}
+ {% if module.column == 2 %}
+ {% grp_render_dashboard_module module forloop.counter %}
+ {% endif %}
+ {% endfor %}
+ </div>
+ <div class="column span-6 last" id="column_3">
+ {% for module in dashboard.children %}
+ {% if module.column == 3 %}
+ {% grp_render_dashboard_module module forloop.counter %}
+ {% endif %}
+ {% endfor %}
+ </div>
+</div>
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.txt b/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.txt
new file mode 100644
index 00000000..5bdf42d6
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/dashboard.txt
@@ -0,0 +1,114 @@
+"""
+This file was generated with the customdashboard management command and
+contains the class for the main dashboard.
+
+To activate your index dashboard add the following to your settings.py::
+ GRAPPELLI_INDEX_DASHBOARD = '{{ project }}.{{ file }}.CustomIndexDashboard'
+"""
+
+from django.utils.translation import ugettext_lazy as _
+from django.core.urlresolvers import reverse
+
+from grappelli.dashboard import modules, Dashboard
+from grappelli.dashboard.utils import get_admin_site_name
+
+
+class CustomIndexDashboard(Dashboard):
+ """
+ Custom index dashboard for www.
+ """
+
+ def init_with_context(self, context):
+ site_name = get_admin_site_name(context)
+
+ # append a group for "Administration" & "Applications"
+ self.children.append(modules.Group(
+ _('Group: Administration & Applications'),
+ column=1,
+ collapsible=True,
+ children = [
+ modules.AppList(
+ _('Administration'),
+ column=1,
+ collapsible=False,
+ models=('django.contrib.*',),
+ ),
+ modules.AppList(
+ _('Applications'),
+ column=1,
+ css_classes=('collapse closed',),
+ exclude=('django.contrib.*',),
+ )
+ ]
+ ))
+
+ # append an app list module for "Applications"
+ self.children.append(modules.AppList(
+ _('AppList: Applications'),
+ collapsible=True,
+ column=1,
+ css_classes=('collapse closed',),
+ exclude=('django.contrib.*',),
+ ))
+
+ # append an app list module for "Administration"
+ self.children.append(modules.ModelList(
+ _('ModelList: Administration'),
+ column=1,
+ collapsible=False,
+ models=('django.contrib.*',),
+ ))
+
+ # append another link list module for "support".
+ self.children.append(modules.LinkList(
+ _('Media Management'),
+ column=2,
+ children=[
+ {
+ 'title': _('FileBrowser'),
+ 'url': '/admin/filebrowser/browse/',
+ 'external': False,
+ },
+ ]
+ ))
+
+ # append another link list module for "support".
+ self.children.append(modules.LinkList(
+ _('Support'),
+ column=2,
+ children=[
+ {
+ 'title': _('Django Documentation'),
+ 'url': 'http://docs.djangoproject.com/',
+ 'external': True,
+ },
+ {
+ 'title': _('Grappelli Documentation'),
+ 'url': 'http://packages.python.org/django-grappelli/',
+ 'external': True,
+ },
+ {
+ 'title': _('Grappelli Google-Code'),
+ 'url': 'http://code.google.com/p/django-grappelli/',
+ 'external': True,
+ },
+ ]
+ ))
+
+ # append a feed module
+ self.children.append(modules.Feed(
+ _('Latest Django News'),
+ column=2,
+ feed_url='http://www.djangoproject.com/rss/weblog/',
+ limit=5
+ ))
+
+ # append a recent actions module
+ self.children.append(modules.RecentActions(
+ _('Recent Actions'),
+ limit=5,
+ collapsible=False,
+ column=3,
+ ))
+
+
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/dummy.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/dummy.html
new file mode 100644
index 00000000..f04fcf55
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/dummy.html
@@ -0,0 +1 @@
+{% extends template %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/module.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/module.html
new file mode 100644
index 00000000..7fdf18a4
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/module.html
@@ -0,0 +1,32 @@
+{% load grp_tags i18n %}
+{% if not module.is_empty %}
+
+{% if dashboard.app_title %}
+ <div{% if index %} id="module_{{ index }}{% if subindex %}_{{ subindex }}{% endif %}"{% endif %} class="{% if module|classname:"group" %}group {% else %}module {% endif %}{% if module|classname:"feed" %}feed {% endif %}{% if module|classname:"linklist" %}link-list {% endif %}{% if module|classname:"recentactions" %}actions {% endif %}{{ module.render_css_classes|cut:"deletable"|cut:"draggable"|join:"" }}{% if module.collapsible %} collapse{% if not "open" in module.css_classes and not "closed" in module.css_classes %} open{% endif %}{% endif %}">
+{% else %}
+ <div{% if index %} id="module_{{ index }}{% if subindex %}_{{ subindex }}{% endif %}"{% endif %} class="{% if module|classname:"group" %}group {% else %}module {% endif %}{% if module|classname:"feed" %}feed {% endif %}{% if module|classname:"linklist" %}link-list {% endif %}{% if module|classname:"recentActions" %}actions {% endif %}{{ module.render_css_classes }}{% if module.collapsible %} collapse{% if not "open" in module.css_classes and not "closed" in module.css_classes %} open{% endif %}{% endif %}">
+{% endif %}
+
+ {% if module.title %}
+ {% if module|classname:"group" %}
+ <h2 class="module_title{% if module.collapsible %} collapse-handler{% endif %}">{{ module.title }}</h2>
+ {% else %}
+ <h{% if subindex %}3{% else %}2{% endif %} class="module_title{% if module.collapsible %} collapse-handler{% endif %}">{{ module.title }}</h{% if subindex %}3{% else %}2{% endif %}>
+ {% endif %}
+ {% endif %}
+
+ {# TODO: need to know in what dom we wrap the pre_content/post_content #}
+ {# if module.pre_content %}{{ module.pre_content }}{% endif #}
+
+ {% block module_content %}
+ {% for child in module.children %}
+ {{ child }}
+ {% endfor %}
+ {% endblock %}
+
+ {# TODO: need to know in what dom we wrap the pre_content/post_content #}
+ {# if module.post_content %}{{ module.post_content }}{% endif #}
+
+</div>
+
+{% endif %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/app_list.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/app_list.html
new file mode 100644
index 00000000..fd36b40a
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/app_list.html
@@ -0,0 +1,34 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n %}
+{% block module_content %}
+ {% spaceless %}
+ {% for child in module.children %}
+ <div class="module">
+ <h{% if subindex %}4{% else %}3{% endif %}><a href="{{ child.url }}">{{ child.title }}</a></h{% if subindex %}4{% else %}3{% endif %}>
+ {% for model in child.models %}
+ <div class="row">
+ {% if model.change_url %}
+ <a href="{{ model.change_url }}">{{ model.title }}</a>
+ {% else %}
+ {{ model.title }}
+ {% endif %}
+ {% if model.add_url or model.change_url %}
+ <ul class="actions">
+ {% if model.add_url %}
+ <li class="add-link">
+ <a href="{{ model.add_url }}">{% trans "Add" %}</a>
+ </li>
+ {% endif %}
+ {% if model.change_url %}
+ <li class="change-link">
+ <a href="{{ model.change_url }}">{% trans "Change" %}</a>
+ </li>
+ {% endif %}
+ </ul>
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
+ {% endfor %}
+ {% endspaceless %}
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/feed.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/feed.html
new file mode 100644
index 00000000..86d4e49e
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/feed.html
@@ -0,0 +1,14 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n %}
+{% block module_content %}
+ <ul>
+ {% spaceless %}
+ {% for child in module.children %}
+ <li>
+ {% if child.date %}<span class="date mini quiet">{{ child.date|date }}&nbsp;</span>{% endif %}
+ {% if child.warning %}<span class="warning">{{ child.title }}</span>{% else %}<a class="external" href="{{ child.url }}">{{ child.title }}</a>{% endif %}
+ </li>
+ {% endfor %}
+ {% endspaceless %}
+ </ul>
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/group.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/group.html
new file mode 100644
index 00000000..caa4b880
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/group.html
@@ -0,0 +1,7 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n grp_dashboard_tags %}
+{% block module_content %}
+ {% for sub_module in module.children %}
+ {% grp_render_dashboard_module sub_module index forloop.counter %}
+ {% endfor %}
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/link_list.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/link_list.html
new file mode 100644
index 00000000..f52ad133
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/link_list.html
@@ -0,0 +1,13 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n %}
+{% block module_content %}
+ <ul>
+ {% spaceless %}
+ {% for child in module.children %}
+ <li>
+ <a class="{% if child.external %}external{% else %}internal{% endif %}" href="{{ child.url }}" {% if child.description %} title="{{ child.description }}"{% endif %}>{{ child.title }}</a>
+ </li>
+ {% endfor %}
+ {% endspaceless %}
+ </ul>
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/model_list.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/model_list.html
new file mode 100644
index 00000000..8a6abdb0
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/model_list.html
@@ -0,0 +1,15 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n %}
+{% block module_content %}
+ {% for child in module.children %}
+ <div class="row">
+ {% if child.change_url %}<a href="{{ child.change_url }}">{{ child.title }}</a>{% else %}{{ child.title }}{% endif %}
+ {% if child.add_url or child.change_url %}
+ <ul class="actions">
+ {% if child.add_url %}<li class="add-link"><a href="{{ child.add_url }}">{% trans "Add" %}</a></li>{% endif %}
+ {% if child.change_url %}<li class="change-link"><a href="{{ child.change_url }}">{% trans "Change" %}</a></li>{% endif %}
+ </ul>
+ {% endif %}
+ </div>
+ {% endfor %}
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/recent_actions.html b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/recent_actions.html
new file mode 100644
index 00000000..428e1733
--- /dev/null
+++ b/demo/grappelli/dashboard/templates/grappelli/dashboard/modules/recent_actions.html
@@ -0,0 +1,15 @@
+{% extends "grappelli/dashboard/module.html" %}
+{% load i18n %}
+{% block module_content %}
+ <div class="module">
+ {% if module.children %}
+ <ul>
+ {% for entry in module.children %}
+ <li class="{% if entry.is_addition %}add-link{% endif %}{% if entry.is_change %}change-link{% endif %}{% if entry.is_deletion %}delete-link{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p>{% trans 'None Available' %}</p>
+ {% endif %}
+ </div>
+{% endblock %}
diff --git a/demo/grappelli/dashboard/templatetags/__init__.py b/demo/grappelli/dashboard/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/demo/grappelli/dashboard/templatetags/__init__.py
diff --git a/demo/grappelli/dashboard/templatetags/grp_dashboard_tags.py b/demo/grappelli/dashboard/templatetags/grp_dashboard_tags.py
new file mode 100644
index 00000000..acd4066e
--- /dev/null
+++ b/demo/grappelli/dashboard/templatetags/grp_dashboard_tags.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Dashboard template tags, the following dashboard tags are available:
+ * ``{% grp_render_dashboard %}``
+ * ``{% grp_render_dashboard_module %}``
+
+To load the dashboard tags: ``{% load grp_dashboard_tags %}``.
+"""
+
+import math
+
+from django import template
+from django.core.urlresolvers import reverse
+
+from grappelli.dashboard.utils import get_admin_site_name, get_index_dashboard
+
+register = template.Library()
+tag_func = register.inclusion_tag('grappelli/dashboard/dummy.html', takes_context=True)
+
+
+def grp_render_dashboard(context, location='index', dashboard=None):
+ """
+ Template tag that renders the dashboard, it takes two optional arguments:
+
+ ``location``
+ The location of the dashboard, it can be 'index' (for the admin index
+ dashboard) or 'app_index' (for the app index dashboard), the default
+ value is 'index'.
+
+ ``dashboard``
+ An instance of ``Dashboard``, if not given, the dashboard is retrieved
+ with the ``get_index_dashboard`` or ``get_app_index_dashboard``
+ functions, depending on the ``location`` argument.
+ """
+ if dashboard is None:
+ dashboard = get_index_dashboard(context)
+
+ dashboard.init_with_context(context)
+
+ context.update({
+ 'template': dashboard.template,
+ 'dashboard': dashboard,
+ 'admin_url': reverse('%s:index' % get_admin_site_name(context)),
+ })
+ return context
+grp_render_dashboard = tag_func(grp_render_dashboard)
+
+
+def grp_render_dashboard_module(context, module, index=None, subindex=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.init_with_context(context)
+ context.update({
+ 'template': module.template,
+ 'module': module,
+ 'index': index,
+ 'subindex': subindex,
+ 'admin_url': reverse('%s:index' % get_admin_site_name(context)),
+ })
+ return context
+grp_render_dashboard_module = tag_func(grp_render_dashboard_module)
+
+
diff --git a/demo/grappelli/dashboard/utils.py b/demo/grappelli/dashboard/utils.py
new file mode 100644
index 00000000..7cc1ff4a
--- /dev/null
+++ b/demo/grappelli/dashboard/utils.py
@@ -0,0 +1,229 @@
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This project incorporates work covered by the following copyright and permission notice:
+
+# Copyright (c) 2009, Julien Fache
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of the author nor the names of other
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Copyright (c) 2011, 2012 Free Software Foundation
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+"""
+Admin ui common utilities.
+"""
+
+# PYTHON IMPORTS
+import types
+import warnings
+from fnmatch import fnmatch
+
+# DJANGO IMPORTS
+from django.conf import settings
+from django.contrib import admin
+from django.core.urlresolvers import reverse
+from django.utils.importlib import import_module
+
+
+def _get_dashboard_cls(dashboard_cls, context):
+ if type(dashboard_cls) is types.DictType:
+ curr_url = context.get('request').META['PATH_INFO']
+ for key in dashboard_cls:
+ admin_site_mod, admin_site_inst = key.rsplit('.', 1)
+ admin_site_mod = import_module(admin_site_mod)
+ admin_site = getattr(admin_site_mod, admin_site_inst)
+ admin_url = reverse('%s:index' % admin_site.name)
+ if curr_url.startswith(admin_url):
+ mod, inst = dashboard_cls[key].rsplit('.', 1)
+ mod = import_module(mod)
+ return getattr(mod, inst)
+ else:
+ mod, inst = dashboard_cls.rsplit('.', 1)
+ mod = import_module(mod)
+ return getattr(mod, inst)
+ raise ValueError('Dashboard matching "%s" not found' % dashboard_cls)
+
+
+def get_index_dashboard(context):
+ """
+ Returns the admin dashboard defined in settings (or the default one).
+ """
+
+ return _get_dashboard_cls(getattr(
+ settings,
+ 'GRAPPELLI_INDEX_DASHBOARD',
+ 'grappelli.dashboard.dashboards.DefaultIndexDashboard'
+ ), context)()
+
+
+def get_admin_site(context=None, request=None):
+ dashboard_cls = getattr(
+ settings,
+ 'GRAPPELLI_INDEX_DASHBOARD',
+ 'admin_tools.dashboard.dashboards.DefaultIndexDashboard'
+ )
+
+ if type(dashboard_cls) is types.DictType:
+ if context:
+ request = context.get('request')
+ curr_url = request.META['PATH_INFO']
+ for key in dashboard_cls:
+ mod, inst = key.rsplit('.', 1)
+ mod = import_module(mod)
+ admin_site = getattr(mod, inst)
+ admin_url = reverse('%s:index' % admin_site.name)
+ if curr_url.startswith(admin_url):
+ return admin_site
+ else:
+ return admin.site
+ raise ValueError('Admin site matching "%s" not found' % dashboard_cls)
+
+
+def get_admin_site_name(context):
+ return get_admin_site(context).name
+
+
+def get_avail_models(request):
+ """ Returns (model, perm,) for all models user can possibly see """
+ items = []
+ admin_site = get_admin_site(request=request)
+
+ for model, model_admin in admin_site._registry.items():
+ perms = model_admin.get_model_perms(request)
+ if True not in perms.values():
+ continue
+ items.append((model, perms,))
+ return items
+
+
+def filter_models(request, models, exclude):
+ """
+ Returns (model, perm,) for all models that match models/exclude patterns
+ and are visible by current user.
+ """
+ items = get_avail_models(request)
+ included = []
+ full_name = lambda model: '%s.%s' % (model.__module__, model.__name__)
+
+ # I beleive that that implemented
+ # O(len(patterns)*len(matched_patterns)*len(all_models))
+ # algorythm is fine for model lists because they are small and admin
+ # performance is not a bottleneck. If it is not the case then the code
+ # should be optimized.
+
+ if len(models) == 0:
+ included = items
+ else:
+ for pattern in models:
+ pattern_items = []
+ for item in items:
+ model, perms = item
+ if fnmatch(full_name(model), pattern) and item not in included:
+ pattern_items.append(item)
+ pattern_items.sort(key=lambda x:x[0]._meta.verbose_name_plural)
+ included.extend(pattern_items)
+
+ result = included[:]
+ for pattern in exclude:
+ for item in included:
+ model, perms = item
+ if fnmatch(full_name(model), pattern):
+ result.remove(item)
+ return result
+
+
+class AppListElementMixin(object):
+ """
+ Mixin class used by both the AppListDashboardModule and the
+ AppListMenuItem (to honor the DRY concept).
+ """
+
+ def _visible_models(self, request):
+
+ included = self.models[:]
+ excluded = self.exclude[:]
+ if not self.models and not self.exclude:
+ included = ["*"]
+ return filter_models(request, included, excluded)
+
+ def _get_admin_app_list_url(self, model, context):
+ """
+ Returns the admin change url.
+ """
+ app_label = model._meta.app_label
+ return reverse('%s:app_list' % get_admin_site_name(context),
+ args=(app_label,))
+
+ def _get_admin_change_url(self, model, context):
+ """
+ Returns the admin change url.
+ """
+ app_label = model._meta.app_label
+ return reverse('%s:%s_%s_changelist' % (get_admin_site_name(context),
+ app_label,
+ model.__name__.lower()))
+
+ def _get_admin_add_url(self, model, context):
+ """
+ Returns the admin add url.
+ """
+ app_label = model._meta.app_label
+ return reverse('%s:%s_%s_add' % (get_admin_site_name(context),
+ app_label,
+ model.__name__.lower()))
+
+