Skip to content
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

Include date hierarchy in quick removal links #218

Merged
13 changes: 10 additions & 3 deletions admin_interface/templates/admin/change_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% translate 'Filter' %}</h2>
{% if cl.has_active_filters %}
{% get_admin_interface_active_date_hierarchy cl as active_date_hierarchy %}
{% get_admin_interface_setting "list_filter_removal_links" as list_filter_removal_links %}
{% if list_filter_removal_links %}
{% if cl.has_active_filters %}
{% if list_filter_removal_links %}
{% if active_date_hierarchy %}{% admin_interface_date_hierarchy_removal_link cl active_date_hierarchy %}{% endif %}
{% for spec in cl.filter_specs %}{% admin_interface_filter_removal_link cl spec %}{% endfor %}
<h3 id="changelist-filter-clear">
<a href="{{ cl.clear_all_filters_qs }}">{% translate "Clear all filters" %} &#10006;</a>
Expand All @@ -18,7 +20,12 @@ <h3 id="changelist-filter-clear">
{# Translators: don't translate this, the django catalog already contains it #}
<a href="{{ cl.clear_all_filters_qs }}">&#10006; {% translate "Clear all filters" %}</a>
</h3>
{% endif %}
{% endif %}
{% elif active_date_hierarchy and list_filter_removal_links %}
{% admin_interface_date_hierarchy_removal_link cl active_date_hierarchy %}
<h3 id="changelist-filter-clear">
<a href="{{ cl.clear_all_filters_qs }}">{% translate "Clear all filters" %} &#10006;</a>
</h3>
{% endif %}
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</div>
fabiocaccamo marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="changelist-filter-clear"><a href="{{ removal_link }}">
{{ date_label|capfirst }}: <span>{{ date_value|date:date_format|capfirst }}</span>&nbsp;&#10006;
</a></div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% load admin_interface_tags %}
{% if spec.lookup_val or spec.value %}
<div class="changelist-filter-clear"><a href="{% admin_interface_clear_filter_qs cl spec %}">
{{ title|capfirst }}: <span>{{ selected_value }}</span> &#10006;
<div class="changelist-filter-clear"><a href="{{ removal_link }}">
{{ title|capfirst }}: <span>{{ selected_value }}</span>&nbsp;&#10006;
</a></div>
{% endif %}
69 changes: 55 additions & 14 deletions admin_interface/templatetags/admin_interface_tags.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import datetime
import hashlib
import re

from django import template
from django.conf import settings
from django.template.loader import get_template
from django.urls import NoReverseMatch, reverse
from django.utils import translation

Expand Down Expand Up @@ -93,14 +93,23 @@ def get_admin_interface_nocache():
return hash_string(__version__)


@register.simple_tag()
def admin_interface_clear_filter_qs(changelist, list_filter):
return changelist.get_query_string(remove=list_filter.expected_parameters())
@register.simple_tag(takes_context=False)
def get_admin_interface_active_date_hierarchy(changelist):
merwok marked this conversation as resolved.
Show resolved Hide resolved
date_field = changelist.date_hierarchy
if not date_field:
return

params = changelist.get_filters_params()
# link to clear all filters contains 'date_field__gte',
# only filters with specific year are really active
if f"{date_field}__year" not in params:
return

@register.simple_tag()
return date_field


@register.inclusion_tag("admin_interface/list_filter_removal_link.html")
def admin_interface_filter_removal_link(changelist, list_filter):
template = get_template("admin_interface/list_filter_removal_link.html")
title = list_filter.title
choices = [
choice for choice in list_filter.choices(changelist) if choice.get("selected")
Expand All @@ -110,14 +119,46 @@ def admin_interface_filter_removal_link(changelist, list_filter):
except (IndexError, KeyError):
value = "..."

return template.render(
{
"cl": changelist,
"spec": list_filter,
"selected_value": value,
"title": title,
}
)
removal_link = changelist.get_query_string(remove=list_filter.expected_parameters())

return {
"cl": changelist,
"spec": list_filter,
"selected_value": value,
"title": title,
"removal_link": removal_link,
}


@register.inclusion_tag("admin_interface/date_hierarchy_removal_link.html")
def admin_interface_date_hierarchy_removal_link(changelist, date_field):
date_label = changelist.model._meta.get_field(date_field).verbose_name

params = changelist.get_filters_params()
date_params = [p for p in params if p.startswith(date_field)]

date_args = [int(params[f"{date_field}__year"]), 1, 1]
date_format = "Y"

if f"{date_field}__month" in params:
date_args[1] = int(params[f"{date_field}__month"])
date_format = "YEAR_MONTH_FORMAT"

if f"{date_field}__day" in params:
date_args[2] = int(params[f"{date_field}__day"])
date_format = "DATE_FORMAT"

date_value = datetime.date(*date_args)

removal_link = changelist.get_query_string(remove=date_params)

return {
"cl": changelist,
"date_label": date_label,
"date_value": date_value,
"date_format": date_format,
"removal_link": removal_link,
}


@register.simple_tag()
Expand Down
126 changes: 126 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from datetime import date
from unittest.mock import Mock

from django.contrib.admin.views.main import ChangeList
from django.template import Context, Template
from django.test import TestCase, override_settings
from django.test.client import RequestFactory
Expand Down Expand Up @@ -137,6 +141,10 @@ def test_get_theme(self):
)
self.assertEqual(rendered, "Django")

def test_get_setting(self):
title = templatetags.get_admin_interface_setting("title")
self.assertEqual(title, "Django administration")

def test_get_version(self):
version = templatetags.get_admin_interface_version()
self.assertEqual(version, __version__)
Expand Down Expand Up @@ -164,3 +172,121 @@ def test_get_admin_interface_inline_template(self):
"admin/edit_inline/stacked.html"
)
self.assertEqual(headless_template, "admin/edit_inline/headerless_stacked.html")

def test_get_active_date_hierarchy_none(self):
changelist = Mock()
changelist.date_hierarchy = None

date_field = templatetags.get_admin_interface_active_date_hierarchy(changelist)

self.assertIsNone(date_field)

def test_get_active_date_hierarchy_inactive(self):
changelist = Mock()
changelist.date_hierarchy = "last_login"
changelist.get_filters_params.return_value = {}

date_field = templatetags.get_admin_interface_active_date_hierarchy(changelist)

self.assertIsNone(date_field)

def test_get_active_date_hierarchy_active(self):
changelist = Mock()
changelist.date_hierarchy = "last_login"
params = {"some_field": 2, "last_login__year": 2022}
changelist.get_filters_params.return_value = params
fabiocaccamo marked this conversation as resolved.
Show resolved Hide resolved

date_field = templatetags.get_admin_interface_active_date_hierarchy(changelist)

self.assertEqual(date_field, "last_login")

def _add_changelist_methods(self, mock, params):
def get_query_string(**kwargs):
return ChangeList.get_query_string(mock, **kwargs)

def get_filters_params(**kwargs):
return ChangeList.get_filters_params(mock, **kwargs)

mock.get_query_string = get_query_string
mock.get_filters_params = get_filters_params
mock.params = params

def test_filter_removal_link(self):
changelist = Mock()
params = {"shape": "pointy", "size": "small"}
self._add_changelist_methods(changelist, params)
list_filter = Mock()
list_filter.title = "Shape filter"
choices = [{"display": "Round"}, {"display": "Pointy", "selected": True}]
list_filter.choices.return_value = choices
list_filter.expected_parameters.return_value = ("shape",)

ctx = templatetags.admin_interface_filter_removal_link(changelist, list_filter)

self.assertEqual(ctx["removal_link"], "?size=small")
self.assertEqual(ctx["title"], "Shape filter")
self.assertEqual(ctx["selected_value"], "Pointy")

def test_filter_removal_link_no_display(self):
changelist = Mock()
params = {"shape": "pointy", "size": "small"}
self._add_changelist_methods(changelist, params)
list_filter = Mock()
list_filter.title = "Shape filter"
choices = [{"other": "Round"}, {"other": "Pointy", "selected": True}]
list_filter.choices.return_value = choices
list_filter.expected_parameters.return_value = ("shape",)

ctx = templatetags.admin_interface_filter_removal_link(changelist, list_filter)

self.assertEqual(ctx["removal_link"], "?size=small")
self.assertEqual(ctx["title"], "Shape filter")
self.assertEqual(ctx["selected_value"], "...")

def test_date_hierarchy_removal_link_year(self):
changelist = Mock()
params = {"shape": "pointy", "last_login__year": 2022}
self._add_changelist_methods(changelist, params)
changelist.model._meta.get_field.return_value.verbose_name = "last login"

ctx = templatetags.admin_interface_date_hierarchy_removal_link(
changelist, "last_login"
)

self.assertEqual(ctx["removal_link"], "?shape=pointy")
self.assertEqual(ctx["date_label"], "last login")
self.assertEqual(ctx["date_value"], date(2022, 1, 1))

def test_date_hierarchy_removal_link_year_month(self):
changelist = Mock()
changelist.model._meta.get_field.return_value.verbose_name = "last login"
params = {"last_login__year": 2022, "last_login__month": "11"}
self._add_changelist_methods(changelist, params)

ctx = templatetags.admin_interface_date_hierarchy_removal_link(
changelist, "last_login"
)

self.assertEqual(ctx["removal_link"], "?")
self.assertEqual(ctx["date_label"], "last login")
self.assertEqual(ctx["date_value"], date(2022, 11, 1))

def test_date_hierarchy_removal_link_year_month_day(self):
changelist = Mock()
changelist.model._meta.get_field.return_value.verbose_name = "last login"
params = {
"last_login__year": 2022,
"last_login__month": "11",
"last_login__day": "30",
"shape": "round",
"size": "small",
}
self._add_changelist_methods(changelist, params)

ctx = templatetags.admin_interface_date_hierarchy_removal_link(
changelist, "last_login"
)

self.assertEqual(ctx["removal_link"], "?shape=round&size=small")
self.assertEqual(ctx["date_label"], "last login")
self.assertEqual(ctx["date_value"], date(2022, 11, 30))
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ python =
3.11: py311

[testenv]
passenv = CI GITHUB_WORKFLOW
passenv = CI,GITHUB_WORKFLOW
Copy link
Owner

Choose a reason for hiding this comment

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

I totally missed this change from tox 3 to 4, thank you very much!
BTW, tox is not used anymore in the CI, for this reason I have not noticed the breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It’s used for the lint checks! (see preceding commit)

Copy link
Owner

Choose a reason for hiding this comment

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

tox is still used but these env variables are not used anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I understand! tox still complained about the format (no spaces allowed)

deps =
dj22: Django == 2.2.*
dj30: Django == 3.0.*
Expand Down