Skip to content

Commit

Permalink
Fixed #31034 -- Added a navigation sidebar to the admin.
Browse files Browse the repository at this point in the history
Co-authored-by: elky <[email protected]>
Co-authored-by: Goetz <[email protected]>
  • Loading branch information
3 people authored and felixxm committed May 11, 2020
1 parent d6aff36 commit d24ba1b
Show file tree
Hide file tree
Showing 17 changed files with 337 additions and 44 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ answer newbie questions, and generally made Django that much better:
Tobias McNulty <https://www.caktusgroup.com/blog/>
[email protected]
Todd O'Bryan <[email protected]>
Tom Carrick <https://www.carrick.eu>
Tom Christie <[email protected]>
Tom Forbes <[email protected]>
Tom Insam
Expand Down
3 changes: 3 additions & 0 deletions django/contrib/admin/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class AdminSite:
# URL for the "View site" link at the top of each admin page.
site_url = '/'

enable_nav_sidebar = True

_empty_value_display = '-'

login_form = None
Expand Down Expand Up @@ -309,6 +311,7 @@ def each_context(self, request):
'has_permission': self.has_permission(request),
'available_apps': self.get_app_list(request),
'is_popup': False,
'is_nav_sidebar_enabled': self.enable_nav_sidebar,
}

def password_change(self, request, extra_context=None):
Expand Down
21 changes: 21 additions & 0 deletions django/contrib/admin/static/admin/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

@import url(fonts.css);

html, body {
height: 100%;
}

body {
margin: 0;
padding: 0;
Expand Down Expand Up @@ -732,6 +736,23 @@ table#change-history tbody th {
width: 100%;
min-width: 980px;
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
}

#container > div {
flex-shrink: 0;
}

#container > .main {
display: flex;
flex: 1 0 auto;
}

.main > .content {
flex: 1 0;
max-width: 100%;
}

#content {
Expand Down
4 changes: 3 additions & 1 deletion django/contrib/admin/static/admin/css/login.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* LOGIN FORM */

body.login {
.login {
background: #f8f8f8;
height: auto;
}

.login #header {
Expand Down Expand Up @@ -30,6 +31,7 @@ body.login {
width: 28em;
min-width: 300px;
margin: 100px auto;
height: auto;
}

.login #content-main {
Expand Down
100 changes: 100 additions & 0 deletions django/contrib/admin/static/admin/css/nav_sidebar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.sticky {
position: sticky;
top: 0;
max-height: 100vh;
}

.toggle-nav-sidebar {
z-index: 20;
left: 0;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 23px;
width: 23px;
border-right: 1px solid #eaeaea;
background-color: #ffffff;
cursor: pointer;
font-size: 20px;
color: #447e9b;
}

[dir="rtl"] .toggle-nav-sidebar {
border-left: 1px solid #eaeaea;
border-right: 0;
}

.toggle-nav-sidebar:hover,
.toggle-nav-sidebar:focus {
background-color: #f6f6f6;
}

#nav-sidebar {
z-index: 15;
flex: 0 0 275px;
left: -276px;
margin-left: -276px;
border-top: 1px solid transparent;
border-right: 1px solid #eaeaea;
background-color: #ffffff;
overflow: auto;
}

[dir="rtl"] #nav-sidebar {
border-left: 1px solid #eaeaea;
border-right: 0;
left: 0;
margin-left: 0;
right: -276px;
margin-right: -276px;
}

.toggle-nav-sidebar::before {
content: '\00BB';
}

.main.shifted .toggle-nav-sidebar::before {
content: '\00AB';
}

.main.shifted > #nav-sidebar {
left: 24px;
margin-left: 0;
}

[dir="rtl"] .main.shifted > #nav-sidebar {
left: 0;
right: 24px;
margin-right: 0;
}

#nav-sidebar .module th {
width: 100%;
}

#nav-sidebar .module th,
#nav-sidebar .module caption {
padding-left: 16px;
}

[dir="rtl"] #nav-sidebar .module th,
[dir="rtl"] #nav-sidebar .module caption {
padding-left: 8px;
padding-right: 16px;
}

#nav-sidebar .current-app .section:link,
#nav-sidebar .current-app .section:visited {
color: #ffc;
font-weight: bold;
}

#nav-sidebar .current-model {
background: #ffc;
}

@media (max-width: 767px) {
#nav-sidebar, #toggle-nav-sidebar {
display: none;
}
}
22 changes: 22 additions & 0 deletions django/contrib/admin/static/admin/js/nav_sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';
{
const toggleNavSidebar = document.getElementById('toggle-nav-sidebar');
if (toggleNavSidebar !== null) {
const main = document.getElementById('main');
let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen');
if (navSidebarIsOpen === null) {
navSidebarIsOpen = 'true';
}
main.classList.toggle('shifted', navSidebarIsOpen === 'true');

toggleNavSidebar.addEventListener('click', function() {
if (navSidebarIsOpen == 'true') {
navSidebarIsOpen = 'false';
} else {
navSidebarIsOpen = 'true';
}
localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen);
main.classList.toggle('shifted');
});
}
}
40 changes: 40 additions & 0 deletions django/contrib/admin/templates/admin/app_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% load i18n %}

{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path %} current-app{% endif %}">
<table>
<caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">{{ app.name }}</a>
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path %} current-model{% endif %}">
{% if model.admin_url %}
<th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path %} aria-current="page"{% endif %}>{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}

{% if model.add_url %}
<td><a href="{{ model.add_url }}" class="addlink">{% translate 'Add' %}</a></td>
{% else %}
<td></td>
{% endif %}

{% if model.admin_url and show_changelinks %}
{% if model.view_only %}
<td><a href="{{ model.admin_url }}" class="viewlink">{% translate 'View' %}</a></td>
{% else %}
<td><a href="{{ model.admin_url }}" class="changelink">{% translate 'Change' %}</a></td>
{% endif %}
{% elif show_changelinks %}
<td></td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
{% endif %}
16 changes: 16 additions & 0 deletions django/contrib/admin/templates/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
{% if not is_popup and is_nav_sidebar_enabled %}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/nav_sidebar.css" %}">
{% endif %}
{% 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 %}
Expand Down Expand Up @@ -64,6 +67,14 @@
{% endblock %}
{% endif %}

<div class="main shifted" id="main">
{% if not is_popup and is_nav_sidebar_enabled %}
{% block nav-sidebar %}
{% include "admin/nav_sidebar.html" %}
{% endblock %}
{% endif %}
<div class="content">

{% block messages %}
{% if messages %}
<ul class="messagelist">{% for message in messages %}
Expand All @@ -86,8 +97,13 @@
<!-- END Content -->

{% block footer %}<div id="footer"></div>{% endblock %}
</div>
</div>
</div>
<!-- END Container -->

{% if not is_popup and is_nav_sidebar_enabled %}
<script src="{% static 'admin/js/nav_sidebar.js' %}" async></script>
{% endif %}
</body>
</html>
42 changes: 3 additions & 39 deletions django/contrib/admin/templates/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,11 @@

{% block breadcrumbs %}{% endblock %}

{% block nav-sidebar %}{% endblock %}

{% block content %}
<div id="content-main">

{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">{{ app.name }}</a>
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}

{% if model.add_url %}
<td><a href="{{ model.add_url }}" class="addlink">{% translate 'Add' %}</a></td>
{% else %}
<td></td>
{% endif %}

{% if model.admin_url %}
{% if model.view_only %}
<td><a href="{{ model.admin_url }}" class="viewlink">{% translate 'View' %}</a></td>
{% else %}
<td><a href="{{ model.admin_url }}" class="changelink">{% translate 'Change' %}</a></td>
{% endif %}
{% else %}
<td></td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
{% endif %}
{% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
</div>
{% endblock %}

Expand Down
2 changes: 2 additions & 0 deletions django/contrib/admin/templates/admin/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

{% block nav-global %}{% endblock %}

{% block nav-sidebar %}{% endblock %}

{% block content_title %}{% endblock %}

{% block breadcrumbs %}{% endblock %}
Expand Down
4 changes: 4 additions & 0 deletions django/contrib/admin/templates/admin/nav_sidebar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar"></div>
<nav class="sticky" id="nav-sidebar">
{% include 'admin/app_list.html' with app_list=available_apps %}
</nav>
2 changes: 2 additions & 0 deletions django/contrib/admin/templates/registration/logged_out.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

{% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url 'admin:index' %}">{% translate 'Home' %}</a></div>{% endblock %}

{% block nav-sidebar %}{% endblock %}

{% block content %}

<p>{% translate "Thanks for spending some quality time with the Web site today." %}</p>
Expand Down
7 changes: 7 additions & 0 deletions docs/ref/contrib/admin/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2841,6 +2841,13 @@ Templates can override or extend base admin templates as described in
setting an ``empty_value_display`` attribute on the field. See
:attr:`ModelAdmin.empty_value_display` for examples.

.. attribute:: AdminSite.enable_nav_sidebar

.. versionadded:: 3.1

A boolean value that determines whether to show the navigation sidebar
on larger screens. By default, it is set to ``True``.

.. attribute:: AdminSite.login_template

Path to a custom template that will be used by the admin site login view.
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/3.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ Minor features
* Filters in the right sidebar of the admin changelist view now contains a link
to clear all filters.

* The admin now has a sidebar on larger screens for easier navigation. It is
enabled by default but can be disabled by using a custom ``AdminSite`` and
setting :attr:`.AdminSite.enable_nav_sidebar` to ``False``.

* ``XRegExp`` is upgraded from version 2.0.0 to 3.2.0.

* jQuery is upgraded from version 3.4.1 to 3.5.1.
Expand Down
4 changes: 3 additions & 1 deletion tests/admin_inlines/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,7 +1331,9 @@ def test_inline_formset_error(self):
hide_links = self.selenium.find_elements_by_link_text('HIDE')
self.assertEqual(len(hide_links), 2)
for hide_index, field_name in enumerate(test_fields):
hide_links[hide_index].click()
hide_link = hide_links[hide_index]
self.selenium.execute_script('window.scrollTo(0, %s);' % hide_link.location['y'])
hide_link.click()
self.wait_until_invisible(field_name)
self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
self.assertEqual(
Expand Down
Loading

4 comments on commit d24ba1b

@elky
Copy link
Contributor

@elky elky commented on d24ba1b May 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congrats! 👏

@AtmegaBuzz
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this issue solved and committed? cause i can still see this issue on code.djangoproject.com and this save button overlapping is still happening in my latest fetch of django
image
image

@felixxm
Copy link
Member

@felixxm felixxm commented on d24ba1b Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AtmegaBuzz As you can see on Trac, this ticket is not fixed. Feel-free to prepare a patch.

@AtmegaBuzz
Copy link

@AtmegaBuzz AtmegaBuzz commented on d24ba1b Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixxm ok working on it

Please sign in to comment.