Repository: maykinmedia/django-admin-index (language: python)

Issue:
Support Django 3.x and 4.x
Django 3.0+ changed the admin base templates to accommodate the collapsible sidebar. These templates are not compatible with admin index.

Django 3.2 (LTS) is "around the corner" (scheduled for release on [April 6](https://code.djangoproject.com/wiki/Version3.2Roadmap)), would be nice to support that so we can keep using django-admin-index with the next LTS version.


Discussion / hints:

Fix patch:
diff --git a/django_admin_index/apps.py b/django_admin_index/apps.py
index 73f20f4..134d2ee 100644
--- a/django_admin_index/apps.py
+++ b/django_admin_index/apps.py
@@ -2,7 +2,7 @@
 
 from django.apps import AppConfig, apps
 from django.core.checks import Error, Tags, Warning, register
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 __all__ = ["AdminIndexConfig"]
 
diff --git a/django_admin_index/models.py b/django_admin_index/models.py
index af87f27..0c16344 100644
--- a/django_admin_index/models.py
+++ b/django_admin_index/models.py
@@ -5,7 +5,7 @@
 from django.db import models
 from django.urls import reverse
 from django.utils.text import capfirst
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 from ordered_model.models import OrderedModel, OrderedModelQuerySet
 
 from .conf import settings
diff --git a/django_admin_index/static/admin/css/admin-index.css b/django_admin_index/static/admin/css/admin-index.css
index 822e4ff..5c64ed8 100644
--- a/django_admin_index/static/admin/css/admin-index.css
+++ b/django_admin_index/static/admin/css/admin-index.css
@@ -1,2 +1,2 @@
-.breadcrumbs{position:sticky;top:49px;z-index:100000}.dropdown-menu{position:sticky;top:49px;z-index:1000000;display:block;background-color:#417690;padding:0 40px;cursor:pointer}.dropdown-menu .dropdown-menu__item,.dropdown-menu .dropdown-menu__item:link,.dropdown-menu .dropdown-menu__item:visited{color:#fff;display:inline-block;padding:10px;position:relative}.dropdown-menu .dropdown-menu__item.dropdown-menu__item--active,.dropdown-menu .dropdown-menu__item:link.dropdown-menu__item--active,.dropdown-menu .dropdown-menu__item:visited.dropdown-menu__item--active{background-color:#79aec8;text-decoration:underline}.dropdown-menu .dropdown-menu__item:hover,.dropdown-menu .dropdown-menu__item:link:hover,.dropdown-menu .dropdown-menu__item:visited:hover{background-color:#79aec8}.dropdown-menu .dropdown-menu__item:hover .dropdown-menu__drop,.dropdown-menu .dropdown-menu__item:link:hover .dropdown-menu__drop,.dropdown-menu .dropdown-menu__item:visited:hover .dropdown-menu__drop{display:block}.dropdown-menu .dropdown-menu__drop{box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.2);position:absolute;left:0;top:100%;display:none;background-color:#417690;min-width:150px}.dropdown-menu .dropdown-menu__link,.dropdown-menu .dropdown-menu__link:link,.dropdown-menu .dropdown-menu__link:visited{color:#fff;display:block;padding:10px}.dropdown-menu .dropdown-menu__link:hover,.dropdown-menu .dropdown-menu__link:link:hover,.dropdown-menu .dropdown-menu__link:visited:hover{background-color:#79aec8}.dropdown-menu .dropdown-menu__link.dropdown-menu__link--active,.dropdown-menu .dropdown-menu__link:link.dropdown-menu__link--active,.dropdown-menu .dropdown-menu__link:visited.dropdown-menu__link--active{background-color:#79aec8;text-decoration:underline}#header{position:sticky;top:0;z-index:100000;height:29px}
+.breadcrumbs{position:fixed;top:84px;z-index:4000;width:100%;padding:12px 40px!important}.dropdown-menu{position:fixed;top:42px;display:block;cursor:pointer;margin-top:8px;z-index:5000}.dropdown-menu .dropdown-menu__item,.dropdown-menu .dropdown-menu__item:link,.dropdown-menu .dropdown-menu__item:visited{color:#fff;display:inline-block;padding:10px;position:relative;z-index:50;background-color:#4383a3}.dropdown-menu .dropdown-menu__item.dropdown-menu__item--active,.dropdown-menu .dropdown-menu__item:link.dropdown-menu__item--active,.dropdown-menu .dropdown-menu__item:visited.dropdown-menu__item--active{background-color:#79aec8;text-decoration:none}.dropdown-menu .dropdown-menu__item:hover,.dropdown-menu .dropdown-menu__item:link:hover,.dropdown-menu .dropdown-menu__item:visited:hover{background-color:#79aec8;text-decoration:none!important}.dropdown-menu .dropdown-menu__item:hover .dropdown-menu__drop,.dropdown-menu .dropdown-menu__item:link:hover .dropdown-menu__drop,.dropdown-menu .dropdown-menu__item:visited:hover .dropdown-menu__drop{display:block}.dropdown-menu .dropdown-menu__drop{box-shadow:0 4px 8px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.2);position:absolute;left:0;top:100%;display:none;background-color:#417690;min-width:150px;z-index:50}.dropdown-menu .dropdown-menu__link,.dropdown-menu .dropdown-menu__link:link,.dropdown-menu .dropdown-menu__link:visited{color:#fff;display:block;padding:10px}.dropdown-menu .dropdown-menu__link:hover,.dropdown-menu .dropdown-menu__link:link:hover,.dropdown-menu .dropdown-menu__link:visited:hover{background-color:#79aec8;text-decoration:none!important}.dropdown-menu .dropdown-menu__link.dropdown-menu__link--active,.dropdown-menu .dropdown-menu__link:link.dropdown-menu__link--active,.dropdown-menu .dropdown-menu__link:visited.dropdown-menu__link--active{background-color:#79aec8;text-decoration:none}#header{display:grid;grid-template-columns:2fr 2fr;grid-template-rows:1fr 1fr;grid-gap:10px;z-index:5000;position:fixed;width:calc(100% - 50px)}@media (max-width:768px){#header{width:calc(100% - 20px)}}#content,#main{padding-top:104px}#content #nav-sidebar,#main #nav-sidebar{position:static}.content #content{padding-top:0}body[class*=change-] #content,body[class*=change-] #main{padding-top:144px}body[class*=change-] #content #nav-sidebar,body[class*=change-] #main #nav-sidebar{position:static}body[class*=change-] .content #content{padding-top:0}
 /*# sourceMappingURL=/admin-index.css.map */
\ No newline at end of file
diff --git a/django_admin_index/static/admin/css/admin-index.css.map b/django_admin_index/static/admin/css/admin-index.css.map
index 4d6797d..5ce6cf6 100644
--- a/django_admin_index/static/admin/css/admin-index.css.map
+++ b/django_admin_index/static/admin/css/admin-index.css.map
@@ -1,1 +1,1 @@
-{"version":3,"sources":["components/_breadcrumbs.scss","components/_dropdown-menu.scss","components/_header.scss"],"names":[],"mappings":"AAAA,aAEI,eAAA,CACA,QAAA,CACA,eCJJ,eAEI,eAAA,CACA,QAAA,CACA,eAAA,CAEA,aAAA,CACA,wBAAA,CACA,cAAA,CACA,eAEA,yHAGI,UAAA,CACA,oBAAA,CACA,YAAA,CACA,kBAEA,6MACI,wBAAA,CACA,0BAGJ,2IACI,yBAEA,0MACI,cAKZ,oCACI,iEAAA,CACA,iBAAA,CACA,MAAA,CACA,QAAA,CACA,YAAA,CACA,wBAAA,CACA,gBAGJ,yHAGI,UAAA,CACA,aAAA,CACA,aAEA,2IACI,yBAGJ,6MACI,wBAAA,CACA,0BCxDZ,QAEI,eAAA,CACA,KAAA,CACA,cAAA,CAGA","file":"admin-index.css","sourceRoot":"../../../../scss","sourcesContent":[".breadcrumbs {\n    /* Show the header at all times */\n    position: sticky;\n    top: 49px;\n    z-index: 100000;\n}\n",".dropdown-menu {\n    /* Show the header at all times */\n    position: sticky;\n    top: 49px;\n    z-index: 1000000;\n\n    display: block;\n    background-color: #417690;\n    padding: 0 40px;\n    cursor: pointer;\n\n    .dropdown-menu__item,\n    .dropdown-menu__item:link,\n    .dropdown-menu__item:visited {\n        color: #fff;\n        display: inline-block;\n        padding: 10px;\n        position: relative;\n\n        &.dropdown-menu__item--active {\n            background-color: #79aec8;\n            text-decoration: underline;\n        }\n\n        &:hover {\n            background-color: #79aec8;\n\n            .dropdown-menu__drop {\n                display: block;\n            }\n        }\n    }\n\n    .dropdown-menu__drop {\n        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);\n        position: absolute;\n        left: 0;\n        top: 100%;\n        display: none;\n        background-color: #417690;\n        min-width: 150px;\n    }\n\n    .dropdown-menu__link,\n    .dropdown-menu__link:link,\n    .dropdown-menu__link:visited {\n        color: #fff;\n        display: block;\n        padding: 10px;\n\n        &:hover {\n            background-color: #79aec8;\n        }\n\n        &.dropdown-menu__link--active {\n            background-color: #79aec8;\n            text-decoration: underline;\n        }\n    }\n}\n","#header {\n    /* Show the header at all times */\n    position: sticky;\n    top: 0;\n    z-index: 100000;\n    /* Make the header hight absolute so we don't depend on fuzzy sized elements inside\n       the header */\n    height: 29px;\n}\n"]}
\ No newline at end of file
+{"version":3,"sources":["components/_breadcrumbs.scss","components/_dropdown-menu.scss","components/_header.scss","components/_main.scss"],"names":[],"mappings":"AAAA,aAEI,cAAA,CACA,QAAA,CACA,YAAA,CACA,UAAA,CACA,4BCNJ,eAEI,cAAA,CACA,QAAA,CACA,aAAA,CACA,cAAA,CACA,cAAA,CACA,aAEA,yHAGI,UAAA,CACA,oBAAA,CACA,YAAA,CACA,iBAAA,CACA,UAAA,CACA,yBAEA,6MACI,wBAAA,CACA,qBAGJ,2IACI,wBAAA,CACA,+BAEA,0MACI,cAKZ,oCACI,iEAAA,CACA,iBAAA,CACA,MAAA,CACA,QAAA,CACA,YAAA,CACA,wBAAA,CACA,eAAA,CACA,WAGJ,yHAGI,UAAA,CACA,aAAA,CACA,aAEA,2IACI,wBAAA,CACA,+BAGJ,6MACI,wBAAA,CACA,qBC3DZ,QACI,YAAA,CACA,6BAAA,CACA,0BAAA,CACA,aAAA,CACA,YAAA,CACA,cAAA,CAEA,wBAEA,yBAVJ,QAWQ,yBCXR,eAGI,kBAEA,yCACI,gBAMJ,kBACI,cAKJ,yDAGI,kBAEA,mFACI,gBAMJ,uCACI","file":"admin-index.css","sourceRoot":"../../../../scss","sourcesContent":[".breadcrumbs {\n    /* Show the header at all times */\n    position: fixed;\n    top: 84px;\n    z-index: 4000;\n    width: 100%;\n    padding: 12px 40px !important;\n}\n",".dropdown-menu {\n    /* Show the header at all times */\n    position: fixed;\n    top: 42px;\n    display: block;\n    cursor: pointer;\n    margin-top: 8px;\n    z-index: 5000;\n\n    .dropdown-menu__item,\n    .dropdown-menu__item:link,\n    .dropdown-menu__item:visited {\n        color: #fff;\n        display: inline-block;\n        padding: 10px;\n        position: relative;\n        z-index: 50;\n        background-color: #4383a3;\n\n        &.dropdown-menu__item--active {\n            background-color: #79aec8;\n            text-decoration: none;\n        }\n\n        &:hover {\n            background-color: #79aec8;\n            text-decoration: none !important;\n\n            .dropdown-menu__drop {\n                display: block;\n            }\n        }\n    }\n\n    .dropdown-menu__drop {\n        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);\n        position: absolute;\n        left: 0;\n        top: 100%;\n        display: none;\n        background-color: #417690;\n        min-width: 150px;\n        z-index: 50;\n    }\n\n    .dropdown-menu__link,\n    .dropdown-menu__link:link,\n    .dropdown-menu__link:visited {\n        color: #fff;\n        display: block;\n        padding: 10px;\n\n        &:hover {\n            background-color: #79aec8;\n            text-decoration: none !important;\n        }\n\n        &.dropdown-menu__link--active {\n            background-color: #79aec8;\n            text-decoration: none;\n        }\n    }\n}\n","#header {\n    display: grid;\n    grid-template-columns: 2fr 2fr;\n    grid-template-rows: 1fr 1fr;\n    grid-gap: 10px;\n    z-index: 5000;\n    position: fixed;\n\n    width: calc(100% - 50px);\n\n    @media (max-width: 768px) {\n        width: calc(100% - 20px);\n    }\n}\n","#main,\n#content // < Django 3.2\n{\n    padding-top: 104px;\n\n    #nav-sidebar {\n        position: initial;\n    }\n}\n\n.content // > Django 3.2\n{\n    #content {\n        padding-top: 0;\n    }\n}\n\nbody[class*=\"change-\"] {\n    #main,\n    #content // < Django 3.2\n    {\n        padding-top: 144px;\n\n        #nav-sidebar {\n            position: initial;\n        }\n    }\n\n    .content // > Django 3.2\n    {\n        #content {\n            padding-top: 0;\n        }\n    }\n}\n\n"]}
\ No newline at end of file
diff --git a/django_admin_index/templates/admin/base.html b/django_admin_index/templates/admin/base.html
deleted file mode 100644
index 4715b1e..0000000
--- a/django_admin_index/templates/admin/base.html
+++ /dev/null
@@ -1,125 +0,0 @@
-{% load i18n static %}<!DOCTYPE html>
-{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
-<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
-<head>
-<title>{% block title %}{% endblock %}</title>
-<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
-<link rel="stylesheet" type="text/css" href="{% static "admin/css/admin-index.css" %}">
-
-{% block extrastyle %}{% endblock %}
-{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">{% endif %}
-{% block extrahead %}{% endblock %}
-{% block responsive %}
-    <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
-    <link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive.css" %}">
-    {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive_rtl.css" %}">{% endif %}
-{% endblock %}
-{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE">{% endblock %}
-</head>
-{% load i18n django_admin_index %}
-
-<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"
-  data-admin-utc-offset="{% now "Z" %}">
-
-<!-- Container -->
-<div id="container">
-
-    {% if not is_popup %}
-    <!-- Header -->
-    <div id="header">
-        <div id="branding">
-        {% block branding %}{% endblock %}
-        </div>
-        {% block usertools %}
-        {% if has_permission %}
-        <div id="user-tools">
-            {% block welcome-msg %}
-                {% trans 'Welcome,' %}
-                <strong>{% firstof user.get_short_name user.get_username %}</strong>.
-            {% endblock %}
-            {% block userlinks %}
-                {% if site_url %}
-                    <a href="{{ site_url }}">{% trans 'View site' %}</a> /
-                {% endif %}
-                {% if user.is_active and user.is_staff %}
-                    {% url 'django-admindocs-docroot' as docsroot %}
-                    {% if docsroot %}
-                        <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
-                    {% endif %}
-                {% endif %}
-                {% if user.has_usable_password %}
-                <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
-                {% endif %}
-                <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
-            {% endblock %}
-        </div>
-        {% endif %}
-        {% endblock %}
-        {% block nav-global %}{% endblock %}
-    </div>
-    <!-- END Header -->
-
-    {# Start menu #}
-    {% dashboard_app_list as dashboard_app_list %}
-    {% admin_index_settings as admin_index_settings %}
-
-    {% display_dropdown_menu request as should_display_dropdown %}
-    {% if dashboard_app_list and should_display_dropdown %}
-        <div class="dropdown-menu">
-            {% url 'admin:index' as home %}
-            <a href="{{ home }}" class="dropdown-menu__item {% if request.path == home %}dropdown-menu__item--active{% endif %}">
-                {% trans "Dashboard" %}
-            </a>
-            {% for app in dashboard_app_list %}
-                <div class="dropdown-menu__item {% if app.active %}dropdown-menu__item--active{% endif %}">
-                    <span>{{ app.name }}</span>
-                    <span class="dropdown-menu__drop">
-                        {% for model in app.models %}
-                            {% if model.admin_url %}
-                                <div class="dropdown-menu__drop-item">
-                                    <a href="{{ model.admin_url }}" class="dropdown-menu__link {% if model.active %}dropdown-menu__link--active{% endif %}">{{ model.name }}</a>
-                                </div>
-                            {% endif %}
-                        {% endfor %}
-                    </span>
-                </div>
-            {% endfor %}
-        </div>
-    {% endif %}
-    {# End menu #}
-
-    {% block breadcrumbs %}
-    <div class="breadcrumbs">
-    <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
-    {% if title %} &rsaquo; {{ title }}{% endif %}
-    </div>
-    {% endblock %}
-    {% endif %}
-
-    {% block messages %}
-        {% if messages %}
-        <ul class="messagelist">{% for message in messages %}
-          <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
-        {% endfor %}</ul>
-        {% endif %}
-    {% endblock messages %}
-
-    <!-- Content -->
-    <div id="content" class="{% block coltype %}colM{% endblock %}">
-        {% block pretitle %}{% endblock %}
-        {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
-        {% block content %}
-        {% block object-tools %}{% endblock %}
-        {{ content }}
-        {% endblock %}
-        {% block sidebar %}{% endblock %}
-        <br class="clear">
-    </div>
-    <!-- END Content -->
-
-    {% block footer %}<div id="footer"></div>{% endblock %}
-</div>
-<!-- END Container -->
-
-</body>
-</html>
diff --git a/django_admin_index/templates/admin/change_form.html b/django_admin_index/templates/admin/change_form.html
index 48ec7f3..6d77361 100644
--- a/django_admin_index/templates/admin/change_form.html
+++ b/django_admin_index/templates/admin/change_form.html
@@ -1,91 +1,106 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_urls static admin_modify %}
+{% load i18n admin_urls static admin_modify django_admin_index %}
 
 {% block extrahead %}{{ block.super }}
-<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
-{{ media }}
+    <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
+    {{ media }}
 {% endblock %}
 
-{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}
+{% block extrastyle %}{{ block.super }}
+    <link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}
 
 {% block coltype %}colM{% endblock %}
 
 {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
 
-{% if not is_popup %}
-{% block breadcrumbs %}
-<div class="breadcrumbs">
-<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
-{# NOTE: Remove app groups if needed #}
-{% if not admin_index_settings.HIDE_APP_INDEX_PAGES %}
-    &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
-{% endif %}
-{% block breadcrumbs_pre_changelist %}{% endblock %}
-&rsaquo; {% if has_view_permission or has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
-&rsaquo; {% if add %}{% blocktrans with name=opts.verbose_name %}Add {{ name }}{% endblocktrans %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
-</div>
-{% endblock %}
-{% endif %}
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
 
-{% block content %}<div id="content-main">
-{% block object-tools %}
-{% if change %}{% if not is_popup %}
-  <ul class="object-tools">
-    {% block object-tools-items %}
-    {# NOTE: This does not work in Django 1.11 and 2.0. The template part remains the same. #}
-    {# {% change_form_object_tools %} #}
-    <li>
-        {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
-        <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
-    </li>
-    {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %}
+{% if not is_popup %}
+    {% block breadcrumbs %}
+        {% admin_index_settings as admin_index_settings %}
+        <div class="breadcrumbs">
+            <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+            {# NOTE: Remove app groups if needed #}
+            {% if not admin_index_settings.HIDE_APP_INDEX_PAGES %}
+                &rsaquo;
+                <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
+            {% endif %}
+            {% block breadcrumbs_pre_changelist %}{% endblock %}
+            &rsaquo; {% if has_view_permission or has_change_permission %}
+            <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}
+            {{ opts.verbose_name_plural|capfirst }}{% endif %}
+            &rsaquo; {% if add %}{% blocktrans with name=opts.verbose_name %}Add {{ name }}{% endblocktrans %}{% else %}
+            {{ original|truncatewords:"18" }}{% endif %}
+        </div>
     {% endblock %}
-  </ul>
-{% endif %}{% endif %}
-{% endblock %}
-<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
-<div>
-{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
-{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
-{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
-{% if errors %}
-    <p class="errornote">
-    {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
-    </p>
-    {{ adminform.form.non_field_errors }}
 {% endif %}
 
-{% block field_sets %}
-{% for fieldset in adminform %}
-  {% include "admin/includes/fieldset.html" %}
-{% endfor %}
-{% endblock %}
+{% block content %}
+    <div id="content-main">
+        {% block object-tools %}
+            {% if change %}{% if not is_popup %}
+                <ul class="object-tools">
+                    {% block object-tools-items %}
+                        {# NOTE: This does not work in Django 1.11 and 2.0. The template part remains the same. #}
+                        {# {% change_form_object_tools %} #}
+                        <li>
+                            {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
+                            <a href="{% add_preserved_filters history_url %}"
+                               class="historylink">{% trans "History" %}</a>
+                        </li>
+                        {% if has_absolute_url %}
+                            <li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>
+                        {% endif %}
+                    {% endblock %}
+                </ul>
+            {% endif %}{% endif %}
+        {% endblock %}
+        <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post"
+              id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
+            <div>
+                {% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
+                {% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
+                {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
+                {% if errors %}
+                    <p class="errornote">
+                        {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}
+                            {% trans "Please correct the errors below." %}{% endif %}
+                    </p>
+                    {{ adminform.form.non_field_errors }}
+                {% endif %}
 
-{% block after_field_sets %}{% endblock %}
+                {% block field_sets %}
+                    {% for fieldset in adminform %}
+                        {% include "admin/includes/fieldset.html" %}
+                    {% endfor %}
+                {% endblock %}
 
-{% block inline_field_sets %}
-{% for inline_admin_formset in inline_admin_formsets %}
-    {% include inline_admin_formset.opts.template %}
-{% endfor %}
-{% endblock %}
+                {% block after_field_sets %}{% endblock %}
 
-{% block after_related_objects %}{% endblock %}
+                {% block inline_field_sets %}
+                    {% for inline_admin_formset in inline_admin_formsets %}
+                        {% include inline_admin_formset.opts.template %}
+                    {% endfor %}
+                {% endblock %}
 
-{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
+                {% block after_related_objects %}{% endblock %}
 
-{% block admin_change_form_document_ready %}
-    <script type="text/javascript"
-            id="django-admin-form-add-constants"
-            src="{% static 'admin/js/change_form.js' %}"
-            {% if adminform and add %}
-                data-model-name="{{ opts.model_name }}"
-            {% endif %}>
-    </script>
-{% endblock %}
+                {% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
+
+                {% block admin_change_form_document_ready %}
+                    <script type="text/javascript"
+                            id="django-admin-form-add-constants"
+                            src="{% static 'admin/js/change_form.js' %}"
+                        {% if adminform and add %}
+                            data-model-name="{{ opts.model_name }}"
+                        {% endif %}>
+                    </script>
+                {% endblock %}
 
-{# JavaScript for prepopulated fields #}
-{% prepopulated_fields_js %}
+                {# JavaScript for prepopulated fields #}
+                {% prepopulated_fields_js %}
 
-</div>
-</form></div>
+            </div>
+        </form>
+    </div>
 {% endblock %}
diff --git a/django_admin_index/templates/admin/change_list.html b/django_admin_index/templates/admin/change_list.html
index 693d377..0ea2a26 100644
--- a/django_admin_index/templates/admin/change_list.html
+++ b/django_admin_index/templates/admin/change_list.html
@@ -1,95 +1,107 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_urls static admin_list %}
+{% load i18n admin_urls static admin_list django_admin_index %}
 
 {% block extrastyle %}
-  {{ block.super }}
-  <link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}">
-  {% if cl.formset %}
-    <link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">
-  {% endif %}
-  {% if cl.formset or action_form %}
-    <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
-  {% endif %}
-  {{ media.css }}
-  {% if not actions_on_top and not actions_on_bottom %}
-    <style>
-      #changelist table thead th:first-child {width: inherit}
-    </style>
-  {% endif %}
+    {{ block.super }}
+    <link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}">
+    {% if cl.formset %}
+        <link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">
+    {% endif %}
+    {% if cl.formset or action_form %}
+        <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
+    {% endif %}
+    {{ media.css }}
+    {% if not actions_on_top and not actions_on_bottom %}
+        <style>
+            #changelist table thead th:first-child {
+                width: inherit
+            }
+        </style>
+    {% endif %}
 {% endblock %}
 
 {% block extrahead %}
-{{ block.super }}
-{{ media.js }}
+    {{ block.super }}
+    {{ media.js }}
 {% endblock %}
 
 {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
 
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
+
 {% if not is_popup %}
-{% block breadcrumbs %}
-<div class="breadcrumbs">
-<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
-{# NOTE: Remove app groups if needed #}
-{% if not admin_index_settings.HIDE_APP_INDEX_PAGES %}
-    &rsaquo; <a href="{% url 'admin:app_list' app_label=cl.opts.app_label %}">{{ cl.opts.app_config.verbose_name }}</a>
-{% endif %}
-{% block breadcrumbs_pre_changelist %}{% endblock %}
-&rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}
-</div>
-{% endblock %}
+    {% block breadcrumbs %}
+        {% admin_index_settings as admin_index_settings %}
+        <div class="breadcrumbs">
+            <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+            {# NOTE: Remove app groups if needed #}
+            {% if not admin_index_settings.HIDE_APP_INDEX_PAGES %}
+                &rsaquo;
+                <a href="{% url 'admin:app_list' app_label=cl.opts.app_label %}">{{ cl.opts.app_config.verbose_name }}</a>
+            {% endif %}
+            {% block breadcrumbs_pre_changelist %}{% endblock %}
+            &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}
+        </div>
+    {% endblock %}
 {% endif %}
 
 {% block coltype %}flex{% endblock %}
 
 {% block content %}
-  <div id="content-main">
-    {% block object-tools %}
-        <ul class="object-tools">
-          {% block object-tools-items %}
-            {# NOTE: This does not work in Django 1.11 and 2.0. The template part remains the same. #}
-            {# {% change_list_object_tools %} #}
-            {% if has_add_permission %}
-            <li>
-              {% url cl.opts|admin_urlname:'add' as add_url %}
-              <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
-                {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
-              </a>
-            </li>
-            {% endif %}
-          {% endblock %}
-        </ul>
-    {% endblock %}
-    {% if cl.formset and cl.formset.errors %}
-        <p class="errornote">
-        {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
-        </p>
-        {{ cl.formset.non_form_errors }}
-    {% endif %}
-    <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
-      {% block search %}{% search_form cl %}{% endblock %}
-      {% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
-
-      {% block filters %}
-        {% if cl.has_filters %}
-          <div id="changelist-filter">
-            <h2>{% trans 'Filter' %}</h2>
-            {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
-          </div>
+    <div id="content-main">
+        {% block object-tools %}
+            <ul class="object-tools">
+                {% block object-tools-items %}
+                    {# NOTE: This does not work in Django 1.11 and 2.0. The template part remains the same. #}
+                    {# {% change_list_object_tools %} #}
+                    {% if has_add_permission %}
+                        <li>
+                            {% url cl.opts|admin_urlname:'add' as add_url %}
+                            <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
+                                {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
+                            </a>
+                        </li>
+                    {% endif %}
+                {% endblock %}
+            </ul>
+        {% endblock %}
+        {% if cl.formset and cl.formset.errors %}
+            <p class="errornote">
+                {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}
+                    {% trans "Please correct the errors below." %}{% endif %}
+            </p>
+            {{ cl.formset.non_form_errors }}
         {% endif %}
-      {% endblock %}
+        <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
+            <div class="changelist-form-container">
+                {% block search %}{% search_form cl %}{% endblock %}
+                {% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
+
+                <form id="changelist-form" method="post"{% if cl.formset and cl.formset.is_multipart %}
+                      enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
+                    {% if cl.formset %}
+                        <div>{{ cl.formset.management_form }}</div>
+                    {% endif %}
+
 
-      <form id="changelist-form" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
-      {% if cl.formset %}
-        <div>{{ cl.formset.management_form }}</div>
-      {% endif %}
+                    {% block result_list %}
+                        {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
+                        {% result_list cl %}
+                        {% if action_form and actions_on_bottom and cl.show_admin_actions %}
+                            {% admin_actions %}{% endif %}
+                    {% endblock %}
+                    {% block pagination %}{% pagination cl %}{% endblock %}
+                </form>
 
-      {% block result_list %}
-          {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
-          {% result_list cl %}
-          {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
-      {% endblock %}
-      {% block pagination %}{% pagination cl %}{% endblock %}
-      </form>
+            </div>
+            {% block filters %}
+                {% if cl.has_filters %}
+                    <div id="changelist-filter">
+                        <h2>{% trans 'Filter' %}</h2>
+                        {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
+                    </div>
+                {% endif %}
+            {% endblock %}
+        </div>
     </div>
-  </div>
 {% endblock %}
diff --git a/django_admin_index/templates/admin/delete_confirmation.html b/django_admin_index/templates/admin/delete_confirmation.html
index 27315a2..5674007 100644
--- a/django_admin_index/templates/admin/delete_confirmation.html
+++ b/django_admin_index/templates/admin/delete_confirmation.html
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_urls static %}
+{% load i18n admin_urls static django_admin_index %}
 
 {% block extrahead %}
     {# NOTE: Switched these 2 variables around to allow overrides #}
@@ -10,7 +10,10 @@
 
 {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %}
 
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
+
 {% block breadcrumbs %}
+{% admin_index_settings as admin_index_settings %}
 <div class="breadcrumbs">
 <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
 {# NOTE: Remove app groups if needed #}
diff --git a/django_admin_index/templates/admin/delete_selected_confirmation.html b/django_admin_index/templates/admin/delete_selected_confirmation.html
index a6220c8..e2f2b1b 100644
--- a/django_admin_index/templates/admin/delete_selected_confirmation.html
+++ b/django_admin_index/templates/admin/delete_selected_confirmation.html
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n l10n admin_urls static %}
+{% load i18n l10n admin_urls static django_admin_index %}
 
 {% block extrahead %}
     {# NOTE: Switched these 2 variables around to allow overrides #}
@@ -10,7 +10,10 @@
 
 {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
 
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
+
 {% block breadcrumbs %}
+{% admin_index_settings as admin_index_settings %}
 <div class="breadcrumbs">
 <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
 {# NOTE: Remove app groups if needed #}
diff --git a/django_admin_index/templates/admin/index.html b/django_admin_index/templates/admin/index.html
index 485a872..02bec8c 100644
--- a/django_admin_index/templates/admin/index.html
+++ b/django_admin_index/templates/admin/index.html
@@ -7,6 +7,8 @@
 
 {% block bodyclass %}{{ block.super }} dashboard{% endblock %}
 
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
+
 {% block breadcrumbs %}{% endblock %}
 
 {% block content %}
diff --git a/django_admin_index/templates/admin/object_history.html b/django_admin_index/templates/admin/object_history.html
index d80ffae..85271e4 100644
--- a/django_admin_index/templates/admin/object_history.html
+++ b/django_admin_index/templates/admin/object_history.html
@@ -1,7 +1,10 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_urls %}
+{% load i18n admin_urls django_admin_index %}
+
+{% block nav-global %}{% include "includes/app_list.html" %}{% endblock nav-global %}
 
 {% block breadcrumbs %}
+{% admin_index_settings as admin_index_settings %}
 <div class="breadcrumbs">
 <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
 {# NOTE: Remove app groups if needed #}
diff --git a/django_admin_index/templates/includes/app_list.html b/django_admin_index/templates/includes/app_list.html
new file mode 100644
index 0000000..9301982
--- /dev/null
+++ b/django_admin_index/templates/includes/app_list.html
@@ -0,0 +1,30 @@
+{% load i18n django_admin_index static %}
+
+{% dashboard_app_list as dashboard_app_list %}
+{% display_dropdown_menu request as should_display_dropdown %}
+
+{% if dashboard_app_list and should_display_dropdown %}
+    <div class="dropdown-menu">
+        <link rel="stylesheet" type="text/css" href="{% static "admin/css/admin-index.css" %}">
+        {% url 'admin:index' as home %}
+        <a href="{{ home }}"
+           class="dropdown-menu__item {% if request.path == home %}dropdown-menu__item--active{% endif %}">
+            {% trans "Dashboard" %}
+        </a>
+        {% for app in dashboard_app_list %}
+            <div class="dropdown-menu__item {% if app.active %}dropdown-menu__item--active{% endif %}">
+                <span>{{ app.name }}</span>
+                <span class="dropdown-menu__drop">
+                        {% for model in app.models %}
+                            {% if model.admin_url %}
+                                <div class="dropdown-menu__drop-item">
+                                    <a href="{{ model.admin_url }}"
+                                       class="dropdown-menu__link {% if model.active %}dropdown-menu__link--active{% endif %}">{{ model.name }}</a>
+                                </div>
+                            {% endif %}
+                        {% endfor %}
+                    </span>
+            </div>
+        {% endfor %}
+    </div>
+{% endif %}
diff --git a/scss/admin-index.scss b/scss/admin-index.scss
index 07dcdbc..1b96d15 100644
--- a/scss/admin-index.scss
+++ b/scss/admin-index.scss
@@ -1,3 +1,4 @@
 @import "components/breadcrumbs";
 @import "components/dropdown-menu";
 @import "components/header";
+@import "components/main";
diff --git a/scss/components/_breadcrumbs.scss b/scss/components/_breadcrumbs.scss
index 9f9adb5..2148cdc 100644
--- a/scss/components/_breadcrumbs.scss
+++ b/scss/components/_breadcrumbs.scss
@@ -1,6 +1,8 @@
 .breadcrumbs {
     /* Show the header at all times */
-    position: sticky;
-    top: 49px;
-    z-index: 100000;
+    position: fixed;
+    top: 84px;
+    z-index: 4000;
+    width: 100%;
+    padding: 12px 40px !important;
 }
diff --git a/scss/components/_dropdown-menu.scss b/scss/components/_dropdown-menu.scss
index 5855d70..636b450 100644
--- a/scss/components/_dropdown-menu.scss
+++ b/scss/components/_dropdown-menu.scss
@@ -1,13 +1,11 @@
 .dropdown-menu {
     /* Show the header at all times */
-    position: sticky;
-    top: 49px;
-    z-index: 1000000;
-
+    position: fixed;
+    top: 42px;
     display: block;
-    background-color: #417690;
-    padding: 0 40px;
     cursor: pointer;
+    margin-top: 8px;
+    z-index: 5000;
 
     .dropdown-menu__item,
     .dropdown-menu__item:link,
@@ -16,14 +14,17 @@
         display: inline-block;
         padding: 10px;
         position: relative;
+        z-index: 50;
+        background-color: #4383a3;
 
         &.dropdown-menu__item--active {
             background-color: #79aec8;
-            text-decoration: underline;
+            text-decoration: none;
         }
 
         &:hover {
             background-color: #79aec8;
+            text-decoration: none !important;
 
             .dropdown-menu__drop {
                 display: block;
@@ -39,6 +40,7 @@
         display: none;
         background-color: #417690;
         min-width: 150px;
+        z-index: 50;
     }
 
     .dropdown-menu__link,
@@ -50,11 +52,12 @@
 
         &:hover {
             background-color: #79aec8;
+            text-decoration: none !important;
         }
 
         &.dropdown-menu__link--active {
             background-color: #79aec8;
-            text-decoration: underline;
+            text-decoration: none;
         }
     }
 }
diff --git a/scss/components/_header.scss b/scss/components/_header.scss
index 0eb9fba..704db6b 100644
--- a/scss/components/_header.scss
+++ b/scss/components/_header.scss
@@ -1,9 +1,14 @@
 #header {
-    /* Show the header at all times */
-    position: sticky;
-    top: 0;
-    z-index: 100000;
-    /* Make the header hight absolute so we don't depend on fuzzy sized elements inside
-       the header */
-    height: 29px;
+    display: grid;
+    grid-template-columns: 2fr 2fr;
+    grid-template-rows: 1fr 1fr;
+    grid-gap: 10px;
+    z-index: 5000;
+    position: fixed;
+
+    width: calc(100% - 50px);
+
+    @media (max-width: 768px) {
+        width: calc(100% - 20px);
+    }
 }
diff --git a/scss/components/_main.scss b/scss/components/_main.scss
new file mode 100644
index 0000000..70ba540
--- /dev/null
+++ b/scss/components/_main.scss
@@ -0,0 +1,36 @@
+#main,
+#content // < Django 3.2
+{
+    padding-top: 104px;
+
+    #nav-sidebar {
+        position: initial;
+    }
+}
+
+.content // > Django 3.2
+{
+    #content {
+        padding-top: 0;
+    }
+}
+
+body[class*="change-"] {
+    #main,
+    #content // < Django 3.2
+    {
+        padding-top: 144px;
+
+        #nav-sidebar {
+            position: initial;
+        }
+    }
+
+    .content // > Django 3.2
+    {
+        #content {
+            padding-top: 0;
+        }
+    }
+}
+


Test patch:
diff --git a/tests/proj/urls.py b/tests/proj/urls.py
index d101c7e..9ade9b5 100644
--- a/tests/proj/urls.py
+++ b/tests/proj/urls.py
@@ -1,8 +1,8 @@
 from __future__ import absolute_import, unicode_literals
 
-from django.conf.urls import url
 from django.contrib import admin
+from django.urls import re_path
 
 urlpatterns = [
-    url(r"^admin/", admin.site.urls),
+    re_path(r"^admin/", admin.site.urls),
 ]
diff --git a/tests/unit/test_integration.py b/tests/unit/test_integration.py
index ed2f2a4..4d43bf5 100644
--- a/tests/unit/test_integration.py
+++ b/tests/unit/test_integration.py
@@ -52,12 +52,18 @@ def test_app_groups_in_index(self):
             template_path,
         )
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_index(self):
         response = self.client.get(reverse("admin:index"))
         html = response.content.decode("utf-8")
 
         self.assertNotIn('{}"'.format(self.auth_app_list_url), html)
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_change_form(self):
         response = self.client.get(
             reverse("admin:auth_user_change", args=(self.superuser.pk,))
@@ -66,12 +72,18 @@ def test_no_app_list_links_change_form(self):
 
         self.assertNotIn('{}"'.format(self.auth_app_list_url), html)
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_change_list(self):
         response = self.client.get(reverse("admin:auth_user_changelist"))
         html = response.content.decode("utf-8")
 
         self.assertNotIn('{}"'.format(self.auth_app_list_url), html)
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_delete_confirmation(self):
         response = self.client.get(
             reverse("admin:auth_user_delete", args=(self.superuser.pk,))
@@ -80,6 +92,9 @@ def test_no_app_list_links_delete_confirmation(self):
 
         self.assertNotIn('{}"'.format(self.auth_app_list_url), html)
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_delete_selected_confirmation(self):
         response = self.client.post(
             reverse("admin:auth_user_changelist"),
@@ -94,6 +109,9 @@ def test_no_app_list_links_delete_selected_confirmation(self):
 
         self.assertNotIn('{}"'.format(self.auth_app_list_url), html)
 
+    @skipIf(
+        django.VERSION > (3, 0), "Django > 3.0 includes a sidebar with app list link."
+    )
     def test_no_app_list_links_object_history(self):
         response = self.client.get(
             reverse("admin:auth_user_history", args=(self.superuser.pk,))