diff --git a/CHANGELOG b/CHANGELOG index 666bbe6..ac18eae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ Changelog ========= +v0.7 +---- + +- Override `get_app_list()` instead of overriding index template, so custom views are visible in sidebar too. +- Add `AdminSitePlus.custom_views_title` to make module title overridable (#51). + v0.6 ---- diff --git a/adminplus/__init__.py b/adminplus/__init__.py index 0fd8866..3a32662 100644 --- a/adminplus/__init__.py +++ b/adminplus/__init__.py @@ -2,5 +2,5 @@ Django-AdminPlus module """ -VERSION = (0, 6) +VERSION = (0, 7) __version__ = '.'.join(map(str, VERSION)) diff --git a/adminplus/sites.py b/adminplus/sites.py index 39654a5..b0d3aaf 100644 --- a/adminplus/sites.py +++ b/adminplus/sites.py @@ -1,17 +1,17 @@ -from collections import namedtuple import inspect +from collections import namedtuple from typing import Any, Callable, NewType, Sequence, Union from django.contrib.admin.sites import AdminSite -from django.urls import URLPattern, URLResolver, path +from django.urls import URLPattern, URLResolver, path, reverse from django.utils.text import capfirst from django.views.generic import View +_FuncT = NewType("_FuncT", Callable[..., Any]) -_FuncT = NewType('_FuncT', Callable[..., Any]) - -AdminView = namedtuple('AdminView', - ['path', 'view', 'name', 'urlname', 'visible']) +AdminView = namedtuple( + "AdminView", ["path", "view", "name", "urlname", "visible"] +) def is_class_based_view(view): @@ -21,14 +21,15 @@ def is_class_based_view(view): class AdminPlusMixin(object): """Mixin for AdminSite to allow registering custom admin views.""" - index_template = 'adminplus/index.html' # That was easy. + custom_views_title = "Custom Views" def __init__(self, *args, **kwargs): self.custom_views: list[AdminView] = [] - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) - def register_view(self, slug, name=None, urlname=None, visible=True, - view=None) -> Union[None, Callable[[_FuncT], _FuncT]]: + def register_view( + self, slug, name=None, urlname=None, visible=True, view=None + ) -> Union[None, Callable[[_FuncT], _FuncT]]: """Add a custom admin view. Can be used as a function or a decorator. * `path` is the path in the admin where the view will live, e.g. @@ -41,12 +42,15 @@ def register_view(self, slug, name=None, urlname=None, visible=True, the custom view should be visible in the admin dashboard or not. * `view` is any view function you can imagine. """ + def decorator(fn: _FuncT): if is_class_based_view(fn): fn = fn.as_view() self.custom_views.append( - AdminView(slug, fn, name, urlname, visible)) + AdminView(slug, fn, name, urlname, visible) + ) return fn + if view is not None: decorator(view) return @@ -55,31 +59,70 @@ def decorator(fn: _FuncT): def get_urls(self) -> Sequence[Union[URLPattern, URLResolver]]: """Add our custom views to the admin urlconf.""" urls: list[Union[URLPattern, URLResolver]] = super().get_urls() + urls.insert( + 0, + path( + "adminplus/", + self.admin_view(self.app_index), + kwargs={"app_label": "adminplus"}, + name="app_list_adminplus", + ), + ) for av in self.custom_views: urls.insert( - 0, path(av.path, self.admin_view(av.view), name=av.urlname)) + 0, path(av.path, self.admin_view(av.view), name=av.urlname) + ) return urls - def index(self, request, extra_context=None): - """Make sure our list of custom views is on the index page.""" - if not extra_context: - extra_context = {} - custom_list = [] - for slug, view, name, _, visible in self.custom_views: - if callable(visible): - visible = visible(request) - if visible: - if name: - custom_list.append((slug, name)) - else: - custom_list.append((slug, capfirst(view.__name__))) - - # Sort views alphabetically. - custom_list.sort(key=lambda x: x[1]) - extra_context.update({ - 'custom_list': custom_list - }) - return super().index(request, extra_context) + def get_app_list(self, request, app_label=None): + # Django 3.2 don't have app_label parameter + kwargs = {} + sig = inspect.signature(super(AdminPlusMixin, self).get_app_list) + for p in sig.parameters.values(): + if p.name == "app_label": + kwargs["app_label"] = app_label + app_list = super().get_app_list(request, **kwargs) + if app_label is None or app_label == "adminplus": + root_url = reverse("admin:index") + custom_list = [] + for av in self.custom_views: + visible = av.visible + if callable(visible): + visible = visible(request) + if visible: + name = av.name + if not name: + name = capfirst(av.view.__name__) + custom_list.append( + { + "model": None, + "name": name, + "object_name": av.view.__name__, + "admin_url": "%s%s" % (root_url, av.path), + "add_url": "", + "view_only": True, + "perms": { + "add": False, + "change": False, + "delete": False, + "view": True, + }, + } + ) + + # Sort views alphabetically. + custom_list.sort(key=lambda x: x["name"]) + + app_list.append( + { + "name": self.custom_views_title, + "app_label": "adminplus", + "app_url": reverse("admin:app_list_adminplus"), + "has_module_perms": True, + "models": custom_list, + } + ) + return app_list class AdminSitePlus(AdminPlusMixin, AdminSite): diff --git a/adminplus/templates/adminplus/index.html b/adminplus/templates/adminplus/index.html deleted file mode 100644 index d692b1a..0000000 --- a/adminplus/templates/adminplus/index.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "admin/index.html" %} - -{% block sidebar %} - {{ block.super }} - - {% if custom_list %} -
- - - - {% for path, name in custom_list %} - - {% endfor %} - -
Custom Views
{{ name }}
-
- {% endif %} -{% endblock %}