Skip to content

Commit

Permalink
Add support for collapsable filter (Django >= 4.1) (#120)
Browse files Browse the repository at this point in the history
The markup for change list filters changed between 4.0 and 4.1
  • Loading branch information
mgrdcm committed May 19, 2024
1 parent 2fdb039 commit ae06133
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 8 deletions.
6 changes: 6 additions & 0 deletions rangefilter/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ def get_template(self):
if django.VERSION[:2] <= (1, 8):
return "rangefilter/date_filter_1_8.html"

if django.VERSION[:2] <= (4, 0):
return "rangefilter/date_filter_4_0.html"

return "rangefilter/date_filter.html"

template = property(get_template)
Expand Down Expand Up @@ -309,6 +312,9 @@ def get_facet_counts(self, pk_attname, filtered_qs):
return {}

def get_template(self):
if django.VERSION[:2] <= (4, 0):
return "rangefilter/numeric_filter_4_0.html"

return "rangefilter/numeric_filter.html"

template = property(get_template)
Expand Down
13 changes: 9 additions & 4 deletions rangefilter/templates/rangefilter/date_filter.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% load i18n rangefilter_compat %}
<h3>{{ title }}</h3>
<details data-filter-title="{{ title }}" open>
<summary>{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}</summary>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}">
<style nonce="{{ spec.request.csp_nonce }}">
{% default_css_vars_if_needed %}
Expand All @@ -13,9 +14,12 @@ <h3>{{ title }}</h3>
cursor: pointer;
}
.admindatefilter {
padding-left: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid var(--hairline-color);
}
.admindatefilter:last-child {
border-bottom: none;
}
.admindatefilter p {
padding-left: 0px;
Expand Down Expand Up @@ -157,3 +161,4 @@ <h3>{{ title }}</h3>
</div>
</form>
</div>
</details>
159 changes: 159 additions & 0 deletions rangefilter/templates/rangefilter/date_filter_4_0.html
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>
13 changes: 9 additions & 4 deletions rangefilter/templates/rangefilter/numeric_filter.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% load i18n rangefilter_compat %}
<h3>{{ title }}</h3>
<details data-filter-title="{{ title }}" open>
<summary>{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}</summary>
<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,
Expand All @@ -12,9 +13,12 @@ <h3>{{ title }}</h3>
cursor: pointer;
}
.numericrangefilter {
padding-left: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid var(--hairline-color);
}
.numericrangefilter:last-child {
border-bottom: none;
}
.numericrangefilter p {
padding-left: 0px;
Expand Down Expand Up @@ -62,3 +66,4 @@ <h3>{{ title }}</h3>
</div>
</form>
</div>
</details>
64 changes: 64 additions & 0 deletions rangefilter/templates/rangefilter/numeric_filter_4_0.html
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>

0 comments on commit ae06133

Please sign in to comment.