Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom views are visible in sidebar too, customizable title #76

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
----

Expand Down
2 changes: 1 addition & 1 deletion adminplus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
Django-AdminPlus module
"""

VERSION = (0, 6)
VERSION = (0, 7)
__version__ = '.'.join(map(str, VERSION))
107 changes: 75 additions & 32 deletions adminplus/sites.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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):
Expand Down
18 changes: 0 additions & 18 deletions adminplus/templates/adminplus/index.html

This file was deleted.