#!/usr/bin/env python
# cardinal_pythonlib/django/admin.py
"""
===============================================================================
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
**Helper functions for the Django admin site.**
"""
from typing import Any, Callable
# noinspection PyUnresolvedReferences
from django.contrib.admin import AdminSite, ModelAdmin
# noinspection PyUnresolvedReferences
from django.utils.html import escape
# noinspection PyUnresolvedReferences
from django.urls import reverse
# =============================================================================
# Disable boolean icons for a ModelAdmin field
# =============================================================================
# https://stackoverflow.com/questions/13990846/disable-on-off-icon-for-boolean-field-in-django # noqa: E501
# ... extended to use closures
[docs]def disable_bool_icon(fieldname: str, model) -> Callable[[Any], bool]:
"""
Disable boolean icons for a Django ModelAdmin field.
The '_meta' attribute is present on Django model classes and instances.
model_class: ``Union[Model, Type[Model]]``
... only the type checker in Py3.5 is broken; see ``files.py``
"""
# noinspection PyUnusedLocal
def func(self, obj):
return getattr(obj, fieldname)
func.boolean = False
func.admin_order_field = fieldname
# func.short_description = \
# model._meta.get_field_by_name(fieldname)[0].verbose_name
# get_field_by_name() deprecated in Django 1.9 and will go in 1.10
# https://docs.djangoproject.com/en/1.8/ref/models/meta/
# noinspection PyProtectedMember, PyUnresolvedReferences
func.short_description = model._meta.get_field(fieldname).verbose_name
return func
# =============================================================================
# Links in model admin
# =============================================================================
# noinspection PyProtectedMember
[docs]def admin_view_url(
admin_site: AdminSite,
obj,
view_type: str = "change",
current_app: str = None,
) -> str:
"""
Get a Django admin site URL for an object.
"""
app_name = obj._meta.app_label.lower()
model_name = obj._meta.object_name.lower()
pk = obj.pk
viewname = f"admin:{app_name}_{model_name}_{view_type}"
if current_app is None:
current_app = admin_site.name
url = reverse(viewname, args=[pk], current_app=current_app)
return url
# noinspection PyProtectedMember
[docs]def admin_view_fk_link(
modeladmin: ModelAdmin,
obj,
fkfield: str,
missing: str = "(None)",
use_str: bool = True,
view_type: str = "change",
current_app: str = None,
) -> str:
"""
Get a Django admin site URL for an object that's found from a foreign
key in our object of interest.
"""
if not hasattr(obj, fkfield):
return missing
linked_obj = getattr(obj, fkfield)
app_name = linked_obj._meta.app_label.lower()
model_name = linked_obj._meta.object_name.lower()
viewname = f"admin:{app_name}_{model_name}_{view_type}"
# https://docs.djangoproject.com/en/dev/ref/contrib/admin/#reversing-admin-urls # noqa: E501
if current_app is None:
current_app = modeladmin.admin_site.name
# ... plus a bit of home-grown magic; see Django source
url = reverse(viewname, args=[linked_obj.pk], current_app=current_app)
if use_str:
label = escape(str(linked_obj))
else:
label = f"{escape(linked_obj._meta.object_name)} {linked_obj.pk}"
return f'<a href="{url}">{label}</a>'
# noinspection PyProtectedMember
[docs]def admin_view_reverse_fk_links(
modeladmin: ModelAdmin,
obj,
reverse_fk_set_field: str,
missing: str = "(None)",
use_str: bool = True,
separator: str = "<br>",
view_type: str = "change",
current_app: str = None,
) -> str:
"""
Get multiple Django admin site URL for multiple objects linked to our
object of interest (where the other objects have foreign keys to our
object).
"""
if not hasattr(obj, reverse_fk_set_field):
return missing
linked_objs = getattr(obj, reverse_fk_set_field).all()
if not linked_objs:
return missing
first = linked_objs[0]
app_name = first._meta.app_label.lower()
model_name = first._meta.object_name.lower()
viewname = f"admin:{app_name}_{model_name}_{view_type}"
if current_app is None:
current_app = modeladmin.admin_site.name
links = []
for linked_obj in linked_objs:
# log.debug("linked_obj: {}", linked_obj)
url = reverse(viewname, args=[linked_obj.pk], current_app=current_app)
if use_str:
label = escape(str(linked_obj))
else:
label = f"{escape(linked_obj._meta.object_name)} {linked_obj.pk}"
links.append(f'<a href="{url}">{label}</a>')
# log.debug("links: {}", links)
return separator.join(links)