-
Notifications
You must be signed in to change notification settings - Fork 106
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for collapsable filter (Django >= 4.1) (#120)
The markup for change list filters changed between 4.0 and 4.1
- Loading branch information
Showing
5 changed files
with
247 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
{% load i18n rangefilter_compat %} | ||
<h3>{{ title }}</h3> | ||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}"> | ||
<style nonce="{{ spec.request.csp_nonce }}"> | ||
{% default_css_vars_if_needed %} | ||
.admindatefilter .button, .admindatefilter input[type=submit], .admindatefilter input[type=button], .admindatefilter .submit-row input, .admindatefilter a.button, | ||
.admindatefilter .button, .admindatefilter input[type=reset] { | ||
background: var(--button-bg); | ||
padding: 4px 5px; | ||
border: none; | ||
border-radius: 4px; | ||
color: var(--button-fg); | ||
cursor: pointer; | ||
} | ||
.admindatefilter { | ||
padding-left: 15px; | ||
padding-bottom: 10px; | ||
border-bottom: 1px solid var(--border-color); | ||
} | ||
.admindatefilter p { | ||
padding-left: 0px; | ||
line-height: 0; | ||
} | ||
.admindatefilter p.datetime { | ||
line-height: 0; | ||
} | ||
.admindatefilter .timezonewarning { | ||
display: none; | ||
} | ||
.admindatefilter .datetimeshortcuts a:first-child { | ||
margin-right: 4px; | ||
display: none; | ||
} | ||
.calendarbox { | ||
z-index: 1100; | ||
} | ||
.clockbox { | ||
z-index: 1100; | ||
margin-left: -8em !important; | ||
margin-top: 5em !important; | ||
} | ||
.admindatefilter .datetimeshortcuts { | ||
font-size: 0; | ||
float: right; | ||
position: absolute; | ||
padding-top: 4px; | ||
} | ||
.admindatefilter a { | ||
color: #999; | ||
position: absolute; | ||
padding-top: 3px; | ||
padding-left: 4px; | ||
} | ||
@media (min-width: 768px) { | ||
.calendarbox { | ||
margin-left: -16em !important; | ||
margin-top: 9em !important; | ||
} | ||
} | ||
@media (max-width: 767px) { | ||
.calendarbox { | ||
overflow: visible; | ||
} | ||
} | ||
</style> | ||
|
||
{% comment %} | ||
Force load jsi18n, issues #5 | ||
https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 | ||
{% endcomment %} | ||
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script> | ||
<script type="text/javascript" nonce="{{ spec.request.csp_nonce }}"> | ||
django.jQuery('document').ready(function () { | ||
django.jQuery('.admindatefilter #{{ choices.0.system_name }}-form input[type="submit"]').click(function(event) { | ||
event.preventDefault(); | ||
var form = django.jQuery(this).closest('div.admindatefilter').find('form'); | ||
var query_string = django.jQuery('input#{{ choices.0.system_name }}-query-string').val(); | ||
var form_data = form.serialize(); | ||
var amp = query_string === "?" ? "" : "&"; // avoid leading ?& combination | ||
window.location = window.location.pathname + query_string + amp + form_data; | ||
}); | ||
|
||
django.jQuery('.admindatefilter #{{ choices.0.system_name }}-form input[type="reset"]').click(function() { | ||
var form = django.jQuery(this).closest('div.admindatefilter').find('form'); | ||
var query_string = form.find('input#{{ choices.0.system_name }}-query-string').val(); | ||
window.location = window.location.pathname + query_string; | ||
}); | ||
}); | ||
{% comment %} | ||
// Code below makes sure that the DateTimeShortcuts.js is loaded exactly once | ||
// regardless the presence of AdminDateWidget | ||
// How it worked: | ||
// - First Django loads the model formset with predefined widgets for different | ||
// field types. If there's a date based field, then it loads the AdminDateWidget | ||
// and it's required media to context under {{media.js}} in admin/change_list.html. | ||
// (Note: it accumulates media in django.forms.widgets.Media object, | ||
// which prevents duplicates, but the DateRangeFilter is not included yet | ||
// since it's not model field related. | ||
// List of predefined widgets is in django.contrib.admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS) | ||
// - After that Django starts rendering forms, which have the {{form.media}} | ||
// tag. Only then the DjangoRangeFilter.get_media is called and rendered, | ||
// which creates the duplicates. | ||
// How it works: | ||
// - first step is the same, if there's a AdminDateWidget to be loaded then | ||
// nothing changes | ||
// - DOM gets rendered and if the AdminDateWidget was rendered then | ||
// the DateTimeShortcuts.js is initiated which sets the window.DateTimeShortcuts. | ||
// Otherwise, the window.DateTimeShortcuts is undefined. | ||
// - The lines below check if the DateTimeShortcuts has been set and if not | ||
// then the DateTimeShortcuts.js and calendar.js is rendered | ||
// | ||
// https://github.com/silentsokolov/django-admin-rangefilter/issues/9 | ||
// | ||
// Django 2.1 | ||
// https://github.com/silentsokolov/django-admin-rangefilter/issues/21 | ||
{% endcomment %} | ||
function embedScript(url) { | ||
return new Promise(function pr(resolve, reject) { | ||
var newScript = document.createElement("script"); | ||
newScript.type = "text/javascript"; | ||
newScript.src = url; | ||
newScript.onload = resolve; | ||
if ("{{ spec.request.csp_nonce }}" !== "") { | ||
newScript.setAttribute("nonce", "{{ spec.request.csp_nonce }}"); | ||
} | ||
document.head.appendChild(newScript); | ||
}); | ||
} | ||
|
||
django.jQuery('document').ready(function () { | ||
if (!('DateTimeShortcuts' in window)) { | ||
var promiseList = []; | ||
|
||
{% for m in spec.form.js %} | ||
promiseList.push(embedScript("{{ m }}")); | ||
{% endfor %} | ||
|
||
Promise.all(promiseList).then(function() { | ||
django.jQuery('.datetimeshortcuts').remove(); | ||
if ('DateTimeShortcuts' in window) { | ||
window.DateTimeShortcuts.init(); | ||
} | ||
}); | ||
} | ||
}); | ||
</script> | ||
{% block quick-select-choices %}{% endblock %} | ||
<div class="admindatefilter"> | ||
<form method="GET" action="." id="{{ choices.0.system_name }}-form"> | ||
{{ spec.form.as_p }} | ||
{% for choice in choices %} | ||
<input type="hidden" id="{{ choice.system_name }}-query-string" value="{{ choice.query_string }}"> | ||
{% endfor %} | ||
<div class="controls"> | ||
<input type="submit" class="button" value="{% trans "Search" %}"> | ||
<input type="reset" class="button" value="{% trans "Reset" %}"> | ||
</div> | ||
</form> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{% load i18n rangefilter_compat %} | ||
<h3>{{ title }}</h3> | ||
<style nonce="{{ spec.request.csp_nonce }}"> | ||
{% default_css_vars_if_needed %} | ||
.numericrangefilter .button, .numericrangefilter input[type=submit], .numericrangefilter input[type=button], .numericrangefilter .submit-row input, .numericrangefilter a.button, | ||
.numericrangefilter .button, .numericrangefilter input[type=reset] { | ||
background: var(--button-bg); | ||
padding: 4px 5px; | ||
border: none; | ||
border-radius: 4px; | ||
color: var(--button-fg); | ||
cursor: pointer; | ||
} | ||
.numericrangefilter { | ||
padding-left: 15px; | ||
padding-bottom: 10px; | ||
border-bottom: 1px solid var(--border-color); | ||
} | ||
.numericrangefilter p { | ||
padding-left: 0px; | ||
display: inline; | ||
} | ||
.numericrangefilter p input { | ||
margin-bottom: 10px; | ||
width: 70px; | ||
} | ||
</style> | ||
|
||
{% comment %} | ||
Force load jsi18n, issues #5 | ||
https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 | ||
{% endcomment %} | ||
|
||
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script> | ||
<script type="text/javascript" nonce="{{ spec.request.csp_nonce }}"> | ||
django.jQuery('document').ready(function () { | ||
django.jQuery('.numericrangefilter #{{ choices.0.system_name }}-form input[type="submit"]').click(function(event) { | ||
event.preventDefault(); | ||
var form = django.jQuery(this).closest('div.numericrangefilter').find('form'); | ||
var query_string = django.jQuery('input#{{ choices.0.system_name }}-query-string').val(); | ||
var form_data = form.serialize(); | ||
var amp = query_string === "?" ? "" : "&"; // avoid leading ?& combination | ||
window.location = window.location.pathname + query_string + amp + form_data; | ||
}); | ||
|
||
django.jQuery('.numericrangefilter #{{ choices.0.system_name }}-form input[type="reset"]').click(function() { | ||
var form = django.jQuery(this).closest('div.numericrangefilter').find('form'); | ||
var query_string = form.find('input#{{ choices.0.system_name }}-query-string').val(); | ||
window.location = window.location.pathname + query_string; | ||
}); | ||
}); | ||
</script> | ||
<div class="numericrangefilter"> | ||
<form method="GET" action="." id="{{ choices.0.system_name }}-form"> | ||
{{ spec.form.as_p }} | ||
{% for choice in choices %} | ||
<input type="hidden" id="{{ choice.system_name }}-query-string" value="{{ choice.query_string }}"> | ||
{% endfor %} | ||
<div class="controls"> | ||
<input type="submit" class="button" value="{% trans "Search" %}"> | ||
<input type="reset" class="button" value="{% trans "Reset" %}"> | ||
</div> | ||
</form> | ||
</div> |