Skip to content

Add support for collapsable filter (Django >= 4.1) [Alternate Version] #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>