Repository: farridav/django-jazzmin

Issue:
Side menu does not display models in some cases
When a user does not have `view_user` permission, but overwrites `has_xxxx_permission`  in `UserAdmin`, as follows:

```python
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    def has_add_permission(self, request):
        return True

    def has_change_permission(self, request, obj=None):
        return True

    def has_view_permission(self, request, obj=None):
        return True

    def has_module_permission(self, request):
        return True
```

Side menu still does not display user.

Discussion:

Patch:
diff --git a/jazzmin/templates/admin/base.html b/jazzmin/templates/admin/base.html
index d2447bd..833281d 100644
--- a/jazzmin/templates/admin/base.html
+++ b/jazzmin/templates/admin/base.html
@@ -171,10 +171,15 @@
                                     <li class="nav-header">{{ app.name }}</li>
                                     {% for model in app.models %}
                                         <li class="nav-item">
-                                            <a href="{% if model.url %}{{ model.url }}{% else %}javascript:void(0){% endif %}" class="nav-link">
-                                                <i class="nav-icon {{ model.icon }}"></i>
-                                                <p>{{ model.name }}</p>
+                                            {% if model.url %}
+                                            <a href="{{ model.url }}" class="nav-link">
+                                                <i class="nav-icon {{ model.icon }}"></i> <p>{{ model.name }}</p>
                                             </a>
+                                        {% else %}
+                                            <span class="nav-link disabled">
+                                                <i class="nav-icon {{ model.icon }}"></i> <p>{{ model.name }}</p>
+                                            </span>
+                                        {% endif %}
                                         </li>
                                     {% endfor %}
                                 {% endfor %}
diff --git a/jazzmin/templatetags/jazzmin.py b/jazzmin/templatetags/jazzmin.py
index 02bac21..12f0475 100644
--- a/jazzmin/templatetags/jazzmin.py
+++ b/jazzmin/templatetags/jazzmin.py
@@ -25,7 +25,6 @@ from ..utils import (
     order_with_respect_to,
     get_filter_id,
     get_admin_url,
-    get_view_permissions,
     make_menu,
     has_fieldsets_check,
 )
@@ -39,13 +38,14 @@ logger = logging.getLogger(__name__)
 def get_side_menu(context):
     """
     Get the list of apps and models to render out in the side menu and on the dashboard page
+
+    N.B - Permissions are not checked here, as context["available_apps"] has already been filtered by django
     """
 
     user = context.get("user")
     if not user:
         return []
 
-    model_permissions = get_view_permissions(user)
     options = get_settings()
 
     menu = []
@@ -64,8 +64,6 @@ def get_side_menu(context):
         menu_items = []
         for model in app.get("models", []):
             model_str = "{app_label}.{model}".format(app_label=app_label, model=model["object_name"]).lower()
-            if model_str not in model_permissions:
-                continue
             if model_str in options.get("hide_models", []):
                 continue
 
diff --git a/jazzmin/utils.py b/jazzmin/utils.py
index 777f770..17b9760 100644
--- a/jazzmin/utils.py
+++ b/jazzmin/utils.py
@@ -37,7 +37,7 @@ def get_admin_url(instance, admin_site="admin", **kwargs):
             app_label, model_name = instance.lower().split(".")
             url = reverse(
                 "admin:{app_label}_{model_name}_changelist".format(app_label=app_label, model_name=model_name),
-                current_app=admin_site
+                current_app=admin_site,
             )
 
         # Model class
@@ -45,7 +45,7 @@ def get_admin_url(instance, admin_site="admin", **kwargs):
             app_label, model_name = instance._meta.app_label, instance._meta.model_name
             url = reverse(
                 "admin:{app_label}_{model_name}_changelist".format(app_label=app_label, model_name=model_name),
-                current_app=admin_site
+                current_app=admin_site,
             )
 
         # Model instance
@@ -54,11 +54,11 @@ def get_admin_url(instance, admin_site="admin", **kwargs):
             url = reverse(
                 "admin:{app_label}_{model_name}_change".format(app_label=app_label, model_name=model_name),
                 args=(instance.pk,),
-                current_app=admin_site
+                current_app=admin_site,
             )
 
     except (NoReverseMatch, ValueError):
-        logger.error("Couldnt reverse url from {instance}".format(instance=instance))
+        logger.warning("Couldnt reverse url from {instance}".format(instance=instance))
 
     if kwargs:
         url += "?{params}".format(params=urlencode(kwargs))
@@ -130,10 +130,10 @@ def get_app_admin_urls(app):
 
 def get_view_permissions(user):
     """
-    Get model names based on a users view permissions
+    Get model names based on a users view/change permissions
     """
     lower_perms = map(lambda x: x.lower(), user.get_all_permissions())
-    return {x.replace("view_", "") for x in lower_perms if "view" in x}
+    return {x.replace("view_", "") for x in lower_perms if "view" in x or "change" in x}
 
 
 def make_menu(user, links, options, allow_appmenus=True):
@@ -186,7 +186,7 @@ def make_menu(user, links, options, allow_appmenus=True):
         # App links
         elif "app" in link and allow_appmenus:
             children = [
-                {"name": child.get("verbose_name", child["name"]), "url": child["url"], "children": None}
+                {"name": child.get("verbose_name", child["name"]), "url": child["url"], "children": None,}
                 for child in get_app_admin_urls(link["app"])
                 if child["model"] in model_permissions
             ]
diff --git a/jazzmin/widgets.py b/jazzmin/widgets.py
index fbe3263..e74d20e 100644
--- a/jazzmin/widgets.py
+++ b/jazzmin/widgets.py
@@ -8,7 +8,7 @@ class JazzminSelect(Select):
     @property
     def media(self):
         return forms.Media(
-            css={"all": ("adminlte/plugins/select2/select2.min.css",)}, js=("adminlte/plugins/select2/select2.min.js",)
+            css={"all": ("adminlte/plugins/select2/select2.min.css",)}, js=("adminlte/plugins/select2/select2.min.js",),
         )
 
 
@@ -22,5 +22,5 @@ class JazzminSelectMultiple(SelectMultiple):
     @property
     def media(self):
         return forms.Media(
-            css={"all": ("adminlte/plugins/select2/select2.min.css",)}, js=("adminlte/plugins/select2/select2.min.js",)
+            css={"all": ("adminlte/plugins/select2/select2.min.css",)}, js=("adminlte/plugins/select2/select2.min.js",),
         )
diff --git a/mkdocs.yml b/mkdocs.yml
index 16ada5e..eaddadf 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -5,7 +5,3 @@ nav:
     - Configuration: configuration.md
     - Development: development.md
 theme: readthedocs
-
-markdown_extensions:
-  - toc:
-      permalink: true


Test patch:
diff --git a/tests/test_app/settings.py b/tests/test_app/settings.py
index 5bb8989..0b19e92 100644
--- a/tests/test_app/settings.py
+++ b/tests/test_app/settings.py
@@ -151,7 +151,12 @@ JAZZMIN_SETTINGS = {
     # Custom links to append to app groups, keyed on app name
     "custom_links": {
         "polls": [
-            {"name": "Make Messages", "url": "make_messages", "icon": "fas fa-comments", "permissions": ["polls.view_poll"]}
+            {
+                "name": "Make Messages",
+                "url": "make_messages",
+                "icon": "fas fa-comments",
+                "permissions": ["polls.view_poll"],
+            }
         ]
     },
     # Custom icons for side menu apps/models See https://www.fontawesomecheatsheet.com/font-awesome-cheatsheet-5x/
diff --git a/tests/test_permissions.py b/tests/test_permissions.py
index aeb6765..dead923 100644
--- a/tests/test_permissions.py
+++ b/tests/test_permissions.py
@@ -37,23 +37,27 @@ def test_no_add_permission(client):
 
 
 @pytest.mark.django_db
-def test_no_view_permission(client):
+def test_delete_but_no_view_permission(client):
     """
-    When our user has no view permission, they dont see things they are not supposed to
+    When our user has delete but no view/change permission, menu items render out, but with no links
+
+    As in Plain old Django Admin
     """
-    user = user_with_permissions("polls.change_poll")
+    user = user_with_permissions("polls.delete_poll")
 
     url = reverse("admin:index")
     client.force_login(user)
 
     response = client.get(url)
-    assert parse_sidemenu(response) == {"Global": ["/admin/"]}
+    assert parse_sidemenu(response) == {"Global": ['/admin/'], 'Polls': [None]}
 
 
 @pytest.mark.django_db
 def test_no_permission(client):
     """
     When our user has no permissions at all, they see no menu or dashboard
+
+    As in Plain old Django Admin
     """
     user = user_with_permissions()
 
diff --git a/tests/utils.py b/tests/utils.py
index 15a533f..ca1e661 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -51,8 +51,8 @@ def parse_sidemenu(response):
             current_app = li.text.strip()
 
         elif "nav-item" in li["class"]:
-            href = li.find("a")["href"]
-            menu[current_app].append(href)
+            href = li.find("a")
+            menu[current_app].append(href["href"] if href else None)
 
     return menu