From 45a24c6d794bc719542431dca2e2d64215a47c4c Mon Sep 17 00:00:00 2001 From: Nic Date: Sat, 1 Aug 2015 19:22:04 -0400 Subject: [PATCH 1/2] Added dashboard app modified code from grappelli.dashboard (https://github.com/sehmaschine/django-grappelli/tree/master/grappelli/dashboard) to work with wp-admin --- wpadmin/dashboard/__init__.py | 4 + wpadmin/dashboard/apps.py | 10 + wpadmin/dashboard/dashboards.py | 192 +++++++++ wpadmin/dashboard/management/__init__.py | 0 .../dashboard/management/commands/__init__.py | 0 .../management/commands/customdashboard.py | 28 ++ wpadmin/dashboard/modules.py | 387 ++++++++++++++++++ wpadmin/dashboard/registry.py | 59 +++ wpadmin/dashboard/templates/admin/index.html | 29 ++ .../wpadmin/dashboard/dashboard.html | 18 + .../templates/wpadmin/dashboard/dashboard.txt | 114 ++++++ .../templates/wpadmin/dashboard/dummy.html | 1 + .../templates/wpadmin/dashboard/module.html | 20 + .../wpadmin/dashboard/modules/app_list.html | 33 ++ .../wpadmin/dashboard/modules/feed.html | 17 + .../wpadmin/dashboard/modules/group.html | 7 + .../wpadmin/dashboard/modules/link_list.html | 16 + .../wpadmin/dashboard/modules/model_list.html | 30 ++ .../dashboard/modules/recent_actions.html | 29 ++ wpadmin/dashboard/templatetags/__init__.py | 0 .../templatetags/wpadmin_dashboard_tags.py | 67 +++ wpadmin/dashboard/utils.py | 165 ++++++++ wpadmin/templatetags/wpadmin_tags.py | 29 ++ 23 files changed, 1255 insertions(+) create mode 100644 wpadmin/dashboard/__init__.py create mode 100644 wpadmin/dashboard/apps.py create mode 100644 wpadmin/dashboard/dashboards.py create mode 100644 wpadmin/dashboard/management/__init__.py create mode 100644 wpadmin/dashboard/management/commands/__init__.py create mode 100644 wpadmin/dashboard/management/commands/customdashboard.py create mode 100644 wpadmin/dashboard/modules.py create mode 100644 wpadmin/dashboard/registry.py create mode 100644 wpadmin/dashboard/templates/admin/index.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.txt create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/dummy.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/module.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/app_list.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/feed.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/group.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/link_list.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/model_list.html create mode 100644 wpadmin/dashboard/templates/wpadmin/dashboard/modules/recent_actions.html create mode 100644 wpadmin/dashboard/templatetags/__init__.py create mode 100644 wpadmin/dashboard/templatetags/wpadmin_dashboard_tags.py create mode 100644 wpadmin/dashboard/utils.py diff --git a/wpadmin/dashboard/__init__.py b/wpadmin/dashboard/__init__.py new file mode 100644 index 0000000..c356529 --- /dev/null +++ b/wpadmin/dashboard/__init__.py @@ -0,0 +1,4 @@ +from wpadmin.dashboard.dashboards import * +from wpadmin.dashboard.registry import * + +default_app_config = "wpadmin.dashboard.apps.DashboardConfig" diff --git a/wpadmin/dashboard/apps.py b/wpadmin/dashboard/apps.py new file mode 100644 index 0000000..f59a061 --- /dev/null +++ b/wpadmin/dashboard/apps.py @@ -0,0 +1,10 @@ +# coding: utf-8 + +# DJANGO IMPORTS +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + name = "wpadmin.dashboard" + label = "wpadmin.dashboard" + verbose_name = "WP Admin Dashboard" diff --git a/wpadmin/dashboard/dashboards.py b/wpadmin/dashboard/dashboards.py new file mode 100644 index 0000000..7b7d26d --- /dev/null +++ b/wpadmin/dashboard/dashboards.py @@ -0,0 +1,192 @@ +# coding: utf-8 + +""" +Module where wpadmin dashboard classes are defined. +""" + +# DJANGO IMPORTS +from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse +from django import forms + +# WPADMIN IMPORTS +from wpadmin.dashboard import modules +from wpadmin.dashboard.utils import get_admin_site_name + + +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): + super(MyDashboard, self).__init__(**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 = 'wpadmin/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 'wpadmin-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 `WPADMIN_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'), + 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/wpadmin/dashboard/management/__init__.py b/wpadmin/dashboard/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wpadmin/dashboard/management/commands/__init__.py b/wpadmin/dashboard/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wpadmin/dashboard/management/commands/customdashboard.py b/wpadmin/dashboard/management/commands/customdashboard.py new file mode 100644 index 0000000..224cf6e --- /dev/null +++ b/wpadmin/dashboard/management/commands/customdashboard.py @@ -0,0 +1,28 @@ +# coding: utf-8 + +# 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', 'wpadmin/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/wpadmin/dashboard/modules.py b/wpadmin/dashboard/modules.py new file mode 100644 index 0000000..4578acd --- /dev/null +++ b/wpadmin/dashboard/modules.py @@ -0,0 +1,387 @@ +# coding: utf-8 + +""" +Module where wpadmin dashboard modules classes are defined. +""" + +# DJANGO IMPORTS +from django.utils.text import capfirst +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ +from django.apps import apps as django_apps + +# WPADMIN IMPORTS +from wpadmin.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``. + + ``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: 'wpadmin/dashboard/module.html'. + """ + + template = 'wpadmin/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:`~wpadmin.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 wpadmin.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 = ['wpadmin-dashboard-module'] + if self.collapsible: + ret.append('wpadmin-collapse') + if "wpadmin-open" not in self.css_classes and "wpadmin-closed" not in self.css_classes: + ret.append('wpadmin-open') + ret += self.css_classes + return ' '.join(ret) + + +class Group(DashboardModule): + """ + Represents a group of modules. + + Here's an example of modules group:: + + from wpadmin.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 = 'wpadmin/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 = 'wpadmin/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 = 'wpadmin/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] = { + 'name': django_apps.get_app_config(app_label).verbose_name, + '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['admin_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 = list(apps.keys()) + apps_sorted.sort() + for app in apps_sorted: + # sort model list alphabetically + apps[app]['models'].sort(key=lambda i: i['title']) + self.children.append(apps[app]) + self._initialized = True + + +class ModelList(DashboardModule, AppListElementMixin): + """ + Module that lists a set of models. + """ + + template = 'wpadmin/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['admin_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 = 'wpadmin/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__pk__exact=request.user.pk) + + 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] + self._initialized = True + + +class Feed(DashboardModule): + """ + Class that represents a feed dashboard module. + """ + + title = _('RSS Feed') + template = 'wpadmin/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/wpadmin/dashboard/registry.py b/wpadmin/dashboard/registry.py new file mode 100644 index 0000000..8826c44 --- /dev/null +++ b/wpadmin/dashboard/registry.py @@ -0,0 +1,59 @@ +# coding: utf-8 + + +class Registry(object): + """ + Registry for application dashboards. + """ + registry = {} + + def register(cls, klass, app_name): + from wpadmin.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 importlib import import_module + from django.conf import settings + + blacklist.append('wpadmin') + blacklist.append('wpadmin.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/wpadmin/dashboard/templates/admin/index.html b/wpadmin/dashboard/templates/admin/index.html new file mode 100644 index 0000000..494ef5a --- /dev/null +++ b/wpadmin/dashboard/templates/admin/index.html @@ -0,0 +1,29 @@ +{% extends "admin/base_site.html" %} + + +{% load i18n wpadmin_tags log wpadmin_dashboard_tags admin_static %} +{% block extrastyle %}{{ block.super }} +{% endblock %} + +{% block javascripts %} + {{ block.super }} + +{% endblock %} + + +{% block content %} +{% wpadmin_render_dashboard %} +{% endblock %} + diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.html b/wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.html new file mode 100644 index 0000000..591c711 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.html @@ -0,0 +1,18 @@ +{% load i18n wpadmin_dashboard_tags %} + +{{ dashboard.media }} + +
+ {% for module in dashboard.children %} + {% if module.column == 1 %} + {% wpadmin_render_dashboard_module module forloop.counter %} + {% endif %} + {% endfor %} +
+ diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.txt b/wpadmin/dashboard/templates/wpadmin/dashboard/dashboard.txt new file mode 100644 index 0000000..5a6ffae --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/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:: + WPADMIN_INDEX_DASHBOARD = '{{ project }}.{{ file }}.CustomIndexDashboard' +""" + +from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse + +from wpadmin.dashboard import modules, Dashboard +from wpadmin.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=2, + 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( + _('Applications'), + collapsible=True, + column=1, + css_classes=('collapse closed',), + exclude=('django.contrib.*',), + )) + + # append an app list module for "Administration" + self.children.append(modules.ModelList( + _('Administration'), + column=2, + collapsible=False, + models=('django.contrib.*',), + )) + + # append a recent actions module + self.children.append(modules.RecentActions( + _('Recent Actions'), + limit=5, + collapsible=False, + column=2, + )) + + # 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://django-wp-admin.readthedocs.org/en/master/', + 'external': True, + }, + { + 'title': _('WP Admin Github'), + 'url': 'https://github.com/barszczmm/django-wpadmin/', + '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 + )) \ No newline at end of file diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/dummy.html b/wpadmin/dashboard/templates/wpadmin/dashboard/dummy.html new file mode 100644 index 0000000..f04fcf5 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/dummy.html @@ -0,0 +1 @@ +{% extends template %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/module.html b/wpadmin/dashboard/templates/wpadmin/dashboard/module.html new file mode 100644 index 0000000..5aecac9 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/module.html @@ -0,0 +1,20 @@ +{% load wpadmin_tags i18n %} +{% if not module.is_empty %} + +{% if dashboard.app_title %} + +{% else %} + +{% endif %} + + {% if module.pre_content %}{{ module.pre_content|safe }}{% endif %} + {% block module_content %} + {% for child in module.children %} + {{ child }} + {% endfor %} + {% endblock %} + {% if module.post_content %}{{ module.post_content|safe }}{% endif %} + + + +{% endif %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/app_list.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/app_list.html new file mode 100644 index 0000000..45385e5 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/app_list.html @@ -0,0 +1,33 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n %} +{% block module_content %} + {% for child in module.children %} +
+ + + {% for model in child.models %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% if model.add_url %} + + {% else %} + + {% endif %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% endfor %} +
{% trans child.name %} +
{{ model.title }}{{ model.title }}{% trans 'Add' %} {% trans 'Change' %} 
+
+ {% endfor %} +{% endblock %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/feed.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/feed.html new file mode 100644 index 0000000..3c708fe --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/feed.html @@ -0,0 +1,17 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n %} +{% block module_content %} +
+ + + {% for child in module.children %} + + + + {% endfor %} +
{% trans module.title %}
+ {% if child.date %}

{{ child.date|date }} 

{% endif %} + {% if child.warning %}

{{ child.title }}

{% else %}

{{ child.title }}

{% endif %} +
+
+{% endblock %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/group.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/group.html new file mode 100644 index 0000000..4074a63 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/group.html @@ -0,0 +1,7 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n wpadmin_dashboard_tags %} +{% block module_content %} + {% for sub_module in module.children %} + {% wpadmin_render_dashboard_module sub_module index forloop.counter %} + {% endfor %} +{% endblock %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/link_list.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/link_list.html new file mode 100644 index 0000000..49fc6db --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/link_list.html @@ -0,0 +1,16 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n %} +{% block module_content %} +
+ + + {% for child in module.children %} + + + + {% endfor %} +
{% trans module.title %}
+ {{ child.title }} +
+
+{% endblock %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/model_list.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/model_list.html new file mode 100644 index 0000000..6683569 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/model_list.html @@ -0,0 +1,30 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n %} +{% block module_content %} +
+ + + {% for model in module.children %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% if model.add_url %} + + {% else %} + + {% endif %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% endfor %} +
{% trans module.title %}
{{ model.title }}{{ model.title }}{% trans 'Add' %} {% trans 'Change' %} 
+
+{% endblock %} diff --git a/wpadmin/dashboard/templates/wpadmin/dashboard/modules/recent_actions.html b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/recent_actions.html new file mode 100644 index 0000000..2e69874 --- /dev/null +++ b/wpadmin/dashboard/templates/wpadmin/dashboard/modules/recent_actions.html @@ -0,0 +1,29 @@ +{% extends "wpadmin/dashboard/module.html" %} +{% load i18n %} +{% block module_content %} +
+

{% trans 'Recent Actions' %}

+

{% trans 'My Actions' %}

+ {% if not module.children %} +

{% trans 'None available' %}

+ {% else %} +
    + {% for entry in module.children %} +
  • + {% if entry.is_deletion or not entry.get_admin_url %} + {{ entry.object_repr }} + {% else %} + {{ entry.object_repr }} + {% endif %} +
    + {% if entry.content_type %} + {% filter capfirst %}{{ entry.content_type }}{% endfilter %} + {% else %} + {% trans 'Unknown content' %} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} +
+{% endblock %} diff --git a/wpadmin/dashboard/templatetags/__init__.py b/wpadmin/dashboard/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wpadmin/dashboard/templatetags/wpadmin_dashboard_tags.py b/wpadmin/dashboard/templatetags/wpadmin_dashboard_tags.py new file mode 100644 index 0000000..66461b8 --- /dev/null +++ b/wpadmin/dashboard/templatetags/wpadmin_dashboard_tags.py @@ -0,0 +1,67 @@ +# coding: utf-8 + +""" +Dashboard template tags, the following dashboard tags are available: + * ``{% wpadmin_render_dashboard %}`` + * ``{% wpadmin_render_dashboard_module %}`` + +To load the dashboard tags: ``{% load wpadmin_dashboard_tags %}``. +""" + +# DJANGO IMPORTS +from django import template +from django.core.urlresolvers import reverse + +# WPADMIN IMPORTS +from wpadmin.dashboard.utils import get_admin_site_name, get_index_dashboard + +register = template.Library() +tag_func = register.inclusion_tag('wpadmin/dashboard/dummy.html', takes_context=True) + + +def wpadmin_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 +wpadmin_render_dashboard = tag_func(wpadmin_render_dashboard) + + +def wpadmin_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 +wpadmin_render_dashboard_module = tag_func(wpadmin_render_dashboard_module) diff --git a/wpadmin/dashboard/utils.py b/wpadmin/dashboard/utils.py new file mode 100644 index 0000000..4a53762 --- /dev/null +++ b/wpadmin/dashboard/utils.py @@ -0,0 +1,165 @@ +# coding: utf-8 + +""" +Admin ui common utilities. +""" + +# PYTHON IMPORTS +from __future__ import unicode_literals +from fnmatch import fnmatch +from importlib import import_module + +# DJANGO IMPORTS +from django.conf import settings +from django.contrib import admin +from django.core.urlresolvers import reverse + + +def _get_dashboard_cls(dashboard_cls, context): + if isinstance(dashboard_cls, dict): + curr_url = context.get('request').path + 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, + 'WPADMIN_INDEX_DASHBOARD', + 'wpadmin.dashboard.dashboards.DefaultIndexDashboard' + ), context)() + + +def get_admin_site(context=None, request=None): + dashboard_cls = getattr( + settings, + 'WPADMIN_INDEX_DASHBOARD', + 'wpadmin.dashboard.dashboards.DefaultIndexDashboard' + ) + + if isinstance(dashboard_cls, dict): + 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: str(x[0]._meta.verbose_name_plural.encode('utf-8'))) + included.extend(pattern_items) + + result = included[:] + for pattern in exclude: + for item in included: + model, perms = item + if fnmatch(full_name(model), pattern): + try: + result.remove(item) + except ValueError: # if the item was already removed skip + pass + 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())) diff --git a/wpadmin/templatetags/wpadmin_tags.py b/wpadmin/templatetags/wpadmin_tags.py index e96a2e8..4e6d611 100644 --- a/wpadmin/templatetags/wpadmin_tags.py +++ b/wpadmin/templatetags/wpadmin_tags.py @@ -1,4 +1,24 @@ + +# coding: utf-8 + +# python imports +from functools import wraps +import json + +try: + from django.contrib.auth import get_user_model + User = get_user_model() +except ImportError: + from django.contrib.auth.models import User + +# django imports from django import template +from django.contrib.contenttypes.models import ContentType +from django.utils.formats import get_format +from django.utils.safestring import mark_safe +from django.utils.translation import get_language +from django.template.loader import get_template +from django.template.context import Context from django.utils.translation import ugettext_lazy as _ from wpadmin.utils import ( @@ -40,3 +60,12 @@ def wpadmin_render_custom_title(context): register.simple_tag(takes_context=True)(wpadmin_render_custom_title) +@register.filter +def classname(obj, arg=None): + classname = obj.__class__.__name__.lower() + if arg: + if arg.lower() == classname: + return True + return False + return classname + From fcbda61fa1616661ecfa88b8c883188317283b52 Mon Sep 17 00:00:00 2001 From: Nic Date: Wed, 12 Aug 2015 12:31:48 -0400 Subject: [PATCH 2/2] fixed index.html was not showing proper page for apps index page --- wpadmin/dashboard/templates/admin/index.html | 68 ++++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/wpadmin/dashboard/templates/admin/index.html b/wpadmin/dashboard/templates/admin/index.html index 494ef5a..273ee01 100644 --- a/wpadmin/dashboard/templates/admin/index.html +++ b/wpadmin/dashboard/templates/admin/index.html @@ -2,28 +2,58 @@ {% load i18n wpadmin_tags log wpadmin_dashboard_tags admin_static %} -{% block extrastyle %}{{ block.super }} -{% endblock %} - -{% block javascripts %} - {{ block.super }} - -{% endblock %} - - {% block content %} -{% wpadmin_render_dashboard %} -{% endblock %} + {% if request.get_full_path == "/admin/" %} + {% wpadmin_render_dashboard %} + {% else %} +
+ {% if app_list %} + {% for app in app_list %} +
+ + + {% for model in app.models %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% if model.add_url %} + + {% else %} + + {% endif %} + + {% if model.admin_url %} + + {% else %} + + {% endif %} + + {% endfor %} +
+ {{ app.name }} +
{{ model.name }}{{ model.name }}{% trans 'Add' %} {% trans 'Change' %} 
+
+ {% endfor %} + {% else %} +

{% trans "You don't have permission to edit anything." %}

+ {% endif %} +
+ {% endif %} +{% endblock %} \ No newline at end of file