diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 16d51f3ac8..6686c12899 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,7 @@ Changelog
Unreleased
==========
+* feat: Added code for burger menu in actions column of changelist view.
1.0.7 (2022-04-01)
==================
diff --git a/djangocms_navigation/admin.py b/djangocms_navigation/admin.py
index 7241c03679..13ae481faf 100644
--- a/djangocms_navigation/admin.py
+++ b/djangocms_navigation/admin.py
@@ -82,8 +82,13 @@ class MenuContentAdmin(admin.ModelAdmin):
list_filter = (LanguageFilter, )
class Media:
- js = ("admin/js/jquery.init.js", "djangocms_versioning/js/actions.js",)
- css = {"all": ("djangocms_versioning/css/actions.css", "djangocms_version_locking/css/version-locking.css",)}
+ js = ("admin/js/jquery.init.js", "djangocms_versioning/js/actions.js")
+ css = {
+ "all": (
+ "djangocms_versioning/css/actions.css",
+ "djangocms_version_locking/css/version-locking.css",
+ )
+ }
def get_version(self, obj):
"""
@@ -325,11 +330,11 @@ class MenuItemAdmin(TreeAdmin):
actions = None
change_form_template = "admin/djangocms_navigation/menuitem/change_form.html"
change_list_template = "admin/djangocms_navigation/menuitem/change_list.html"
- list_display = ["__str__", "get_object_url", "soft_root", 'hide_node']
sortable_by = ["pk"]
list_per_page = TREE_MAX_RESULT_PER_PAGE_COUNT
class Media:
+ js = ("admin/js/jquery.init.js", "djangocms_versioning/js/actions.js")
css = {
"all": (
"djangocms_versioning/css/actions.css",
diff --git a/djangocms_navigation/templates/admin/djangocms_navigation/menucontent/change_list.html b/djangocms_navigation/templates/admin/djangocms_navigation/menucontent/change_list.html
new file mode 100644
index 0000000000..1c993ffdcd
--- /dev/null
+++ b/djangocms_navigation/templates/admin/djangocms_navigation/menucontent/change_list.html
@@ -0,0 +1,11 @@
+{% extends "admin/change_list.html" %}
+{% load static %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+ {# INFO: versioning_static_url_prefix variable is used in versioning actions.js #}
+
+{% endblock extrahead %}
diff --git a/djangocms_navigation/templates/admin/djangocms_navigation/menuitem/change_list.html b/djangocms_navigation/templates/admin/djangocms_navigation/menuitem/change_list.html
index df56d43e05..abb33f8345 100644
--- a/djangocms_navigation/templates/admin/djangocms_navigation/menuitem/change_list.html
+++ b/djangocms_navigation/templates/admin/djangocms_navigation/menuitem/change_list.html
@@ -2,6 +2,14 @@
{% extends "admin/tree_change_list.html" %}
{% load static admin_list admin_urls navigation_admin_tree i18n djangocms_versioning %}
+{% block extrahead %}
+ {{ block.super }}
+ {# INFO: versioning_static_url_prefix variable is used to inject static_url into actions.js #}
+
+{% endblock extrahead %}
+
{% block extrastyle %}
{{ block.super }}
diff --git a/djangocms_navigation/templatetags/navigation_admin_tree.py b/djangocms_navigation/templatetags/navigation_admin_tree.py
index 35c70fdc44..274213fe2a 100644
--- a/djangocms_navigation/templatetags/navigation_admin_tree.py
+++ b/djangocms_navigation/templatetags/navigation_admin_tree.py
@@ -1,10 +1,21 @@
# -*- coding: utf-8 -*-
-from django import template
+
+import datetime
+
from django.contrib.admin.templatetags.admin_list import (
result_headers,
result_hidden_fields,
)
+from django.contrib.admin.utils import (
+ display_for_field,
+ display_for_value,
+ lookup_field,
+)
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import models
+from django.template import Library
from django.templatetags.static import static
+from django.utils.encoding import force_str
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
@@ -13,7 +24,7 @@
from treebeard.templatetags.admin_tree import check_empty_dict, results
-register = template.Library()
+register = Library()
"""
This module is simply for overwriting some of treebeard's admin_tree templatetag functions
@@ -21,9 +32,62 @@
CAVEAT: Treebeard encapulates markup in some of it's template tags, so we are needing
to keep the same approach in order to overwrite markup, ie. with get_space and get_collapse below.
+
+INFO: get_result_and_row_class is an almost exact copy, with the exception of a field_name
+function to allow proper str representation for callables included as part of list_display
+on changelist views.
"""
+def get_field_name(field_name):
+ if callable(field_name):
+ return field_name.__name__
+ return field_name
+
+
+def get_result_and_row_class(cl, field_name, result):
+ empty_value_display = cl.model_admin.get_empty_value_display()
+ row_classes = ['field-%s' % get_field_name(field_name)]
+ try:
+ f, attr, value = lookup_field(field_name, result, cl.model_admin)
+ except ObjectDoesNotExist:
+ result_repr = empty_value_display
+ else:
+ empty_value_display = getattr(attr, 'empty_value_display', empty_value_display)
+ if f is None:
+ if field_name == 'action_checkbox':
+ row_classes = ['action-checkbox']
+ allow_tags = getattr(attr, 'allow_tags', False)
+ boolean = getattr(attr, 'boolean', False)
+ result_repr = display_for_value(value, empty_value_display, boolean)
+ # Strip HTML tags in the resulting text, except if the
+ # function has an "allow_tags" attribute set to True.
+ # WARNING: this will be deprecated in Django 2.0
+ if allow_tags:
+ result_repr = mark_safe(result_repr)
+ if isinstance(value, (datetime.date, datetime.time)):
+ row_classes.append('nowrap')
+ else:
+ if isinstance(getattr(f, 'remote_field'), models.ManyToOneRel):
+ field_val = getattr(result, f.name)
+ if field_val is None:
+ result_repr = empty_value_display
+ else:
+ result_repr = field_val
+ else:
+ result_repr = display_for_field(value, f, empty_value_display)
+ if isinstance(f, (models.DateField, models.TimeField,
+ models.ForeignKey)):
+ row_classes.append('nowrap')
+ if force_str(result_repr) == '':
+ result_repr = mark_safe(' ')
+ row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
+ return result_repr, row_class
+
+
+admin_tree.get_result_and_row_class = get_result_and_row_class
+
+
def get_spacer(first, result):
if first:
spacer = f' '
diff --git a/tests/test_admin.py b/tests/test_admin.py
index af2747c961..ca29df9d1f 100644
--- a/tests/test_admin.py
+++ b/tests/test_admin.py
@@ -1,6 +1,7 @@
import html
import importlib
import json
+import re
import sys
from unittest.mock import patch
@@ -241,6 +242,20 @@ def test_list_display_without_versioning(self):
menu_content_admin.get_list_display(request), ["title", "get_menuitem_link", "get_preview_link"]
)
+ def test_menucontent_changelist_verify_burger_menu_available(self):
+ """
+ The actions column should display a burger menu for any actions in addition
+ to edit or preview. Dependent on djangocms_versioning, verify media is
+ present.
+ """
+ factories.MenuContentWithVersionFactory()
+ list_url = reverse("admin:djangocms_navigation_menucontent_changelist")
+
+ response = self.client.get(list_url)
+ soup = BeautifulSoup(str(response.content), features="lxml")
+
+ self.assertTrue(soup.find("script", src=re.compile("djangocms_versioning/js/actions.js")))
+
class MenuItemModelAdminTestCase(TestCase):
def setUp(self):
@@ -974,6 +989,23 @@ def test_menuitem_changelist_check_for_expand_all(self):
self.assertEqual(_('Toggle expand/collapse all'), link['title'])
self.assertIn('+', link.string)
+ def test_menuitem_changelist_verify_burger_menu_available(self):
+ """
+ The actions burger menu should be available for MenuItem. Dependent on
+ djangocms_versioning, verify css class is present and js is loaded.
+ """
+ menu_content = factories.MenuContentWithVersionFactory()
+
+ list_url = reverse(
+ "admin:djangocms_navigation_menuitem_list", args=(menu_content.id,)
+ )
+ response = self.client.get(list_url)
+
+ soup = BeautifulSoup(str(response.content), features="lxml")
+
+ self.assertTrue(soup.find_all("a", class_="cms-versioning-action-btn"))
+ self.assertTrue(soup.find("script", src=re.compile("djangocms_versioning/js/actions.js")))
+
@override_settings(
CMS_PERMISSION=True,