From ff7049432b7ea50e7e4e10a3de697661a3b91c67 Mon Sep 17 00:00:00 2001 From: Julie Pichon Date: Wed, 12 Mar 2014 16:31:06 +0000 Subject: [PATCH] Handle "null" time values for Stacks The timesince filter expects a date or datetime object, and would fail when receiving an empty string. Create a new filter that returns the string value "Never" under these circumstances. Change-Id: I73f4dbb608fc143c3ac60d753e9e222762579e51 Closes-Bug: #1286959 --- horizon/test/tests/utils.py | 46 ++++++++++++++++++- horizon/utils/filters.py | 18 ++++++++ .../dashboards/project/stacks/tables.py | 13 ++++-- .../templates/stacks/_detail_overview.html | 4 +- .../templates/stacks/_resource_overview.html | 2 +- .../test/test_data/heat_data.py | 2 +- 6 files changed, 75 insertions(+), 10 deletions(-) diff --git a/horizon/test/tests/utils.py b/horizon/test/tests/utils.py index 36f6e8f66a..27e86fdaff 100644 --- a/horizon/test/tests/utils.py +++ b/horizon/test/tests/utils.py @@ -15,11 +15,11 @@ # under the License. import datetime - import os from django.core.exceptions import ValidationError # noqa import django.template +from django.template import defaultfilters from horizon.test import helpers as test from horizon.utils import fields @@ -288,6 +288,50 @@ class FiltersTests(test.TestCase): self.assertIsInstance(result, datetime.datetime) +class TimeSinceNeverFilterTests(test.TestCase): + + default = u"Never" + + def test_timesince_or_never_returns_default_for_empty_string(self): + c = django.template.Context({'time': ''}) + t = django.template.Template('{{time|timesince_or_never}}') + self.assertEqual(t.render(c), self.default) + + def test_timesince_or_never_returns_default_for_none(self): + c = django.template.Context({'time': None}) + t = django.template.Template('{{time|timesince_or_never}}') + self.assertEqual(t.render(c), self.default) + + def test_timesince_or_never_returns_default_for_gibberish(self): + c = django.template.Context({'time': django.template.Context()}) + t = django.template.Template('{{time|timesince_or_never}}') + self.assertEqual(t.render(c), self.default) + + def test_timesince_or_never_returns_with_custom_default(self): + custom = "Hello world" + c = django.template.Context({'date': ''}) + t = django.template.Template('{{date|timesince_or_never:"%s"}}' + % custom) + self.assertEqual(t.render(c), custom) + + def test_timesince_or_never_returns_with_custom_empty_string_default(self): + c = django.template.Context({'date': ''}) + t = django.template.Template('{{date|timesince_or_never:""}}') + self.assertEqual(t.render(c), "") + + def test_timesince_or_never_returns_same_output_as_django_date(self): + d = datetime.date(year=2014, month=3, day=7) + c = django.template.Context({'date': d}) + t = django.template.Template('{{date|timesince_or_never}}') + self.assertEqual(t.render(c), defaultfilters.timesince(d)) + + def test_timesince_or_never_returns_same_output_as_django_datetime(self): + now = datetime.datetime.now() + c = django.template.Context({'date': now}) + t = django.template.Template('{{date|timesince_or_never}}') + self.assertEqual(t.render(c), defaultfilters.timesince(now)) + + class MemoizedTests(test.TestCase): def test_memoized_decorator_cache_on_next_call(self): values_list = [] diff --git a/horizon/utils/filters.py b/horizon/utils/filters.py index 5b183b7793..867b8fe92e 100644 --- a/horizon/utils/filters.py +++ b/horizon/utils/filters.py @@ -14,12 +14,15 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + import iso8601 from django.template.defaultfilters import register # noqa from django.template.defaultfilters import timesince # noqa from django.utils.safestring import mark_safe from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ @register.filter @@ -38,6 +41,21 @@ def parse_isotime(timestr, default=None): return default or '' +@register.filter +def timesince_or_never(dt, default=None): + """Call the Django ``timesince`` filter, but return the string + *default* if *dt* is not a valid ``date`` or ``datetime`` object. + When *default* is None, "Never" is returned. + """ + if default is None: + default = _("Never") + + if isinstance(dt, datetime.date): + return timesince(dt) + else: + return default + + @register.filter def timesince_sortable(dt): delta = timezone.now() - dt diff --git a/openstack_dashboard/dashboards/project/stacks/tables.py b/openstack_dashboard/dashboards/project/stacks/tables.py index f7c6f54076..e5f829acd4 100644 --- a/openstack_dashboard/dashboards/project/stacks/tables.py +++ b/openstack_dashboard/dashboards/project/stacks/tables.py @@ -14,7 +14,6 @@ from django.core import urlresolvers from django.http import Http404 # noqa -from django.template.defaultfilters import timesince # noqa from django.template.defaultfilters import title # noqa from django.utils.http import urlencode # noqa from django.utils.translation import ugettext_lazy as _ @@ -90,10 +89,12 @@ class StacksTable(tables.DataTable): link="horizon:project:stacks:detail",) created = tables.Column("creation_time", verbose_name=_("Created"), - filters=(filters.parse_isotime, timesince)) + filters=(filters.parse_isotime, + filters.timesince_or_never)) updated = tables.Column("updated_time", verbose_name=_("Updated"), - filters=(filters.parse_isotime, timesince)) + filters=(filters.parse_isotime, + filters.timesince_or_never)) status = tables.Column("status", filters=(title, filters.replace_underscores), verbose_name=_("Status"), @@ -123,7 +124,8 @@ class EventsTable(tables.DataTable): link=mappings.resource_to_url) timestamp = tables.Column('event_time', verbose_name=_("Time Since Event"), - filters=(filters.parse_isotime, timesince)) + filters=(filters.parse_isotime, + filters.timesince_or_never)) status = tables.Column("resource_status", filters=(title, filters.replace_underscores), verbose_name=_("Status"),) @@ -169,7 +171,8 @@ class ResourcesTable(tables.DataTable): verbose_name=_("Stack Resource Type"),) updated_time = tables.Column('updated_time', verbose_name=_("Date Updated"), - filters=(filters.parse_isotime, timesince)) + filters=(filters.parse_isotime, + filters.timesince_or_never)) status = tables.Column("resource_status", filters=(title, filters.replace_underscores), verbose_name=_("Status"), diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html index f4756e07aa..f391d80d78 100644 --- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html +++ b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html @@ -20,9 +20,9 @@
{% trans "Created" %}
-
{{ stack.creation_time|parse_isotime|timesince }}
+
{{ stack.creation_time|parse_isotime|timesince_or_never }}
{% trans "Last Updated" %}
-
{{ stack.updated_time|parse_isotime|timesince }}
+
{{ stack.updated_time|parse_isotime|timesince_or_never }}
{% trans "Status" %}
{{ stack.stack_status|title }}: {{ stack.stack_status_reason }}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html index ba8156e8bf..835bf422df 100644 --- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html +++ b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html @@ -32,7 +32,7 @@
{% trans "Last Updated" %}
-
{{ resource.updated_time|parse_isotime|timesince }}
+
{{ resource.updated_time|parse_isotime|timesince_or_never }}
{% trans "Status" %}
{{ resource.resource_status|title|replace_underscores }}: {{ resource.resource_status_reason }}
diff --git a/openstack_dashboard/test/test_data/heat_data.py b/openstack_dashboard/test/test_data/heat_data.py index e0854b1fd2..d550e7084b 100644 --- a/openstack_dashboard/test/test_data/heat_data.py +++ b/openstack_dashboard/test/test_data/heat_data.py @@ -353,7 +353,7 @@ def data(TEST): "stack_status_reason": "Stack successfully created", "stack_name": "stack-test", "creation_time": "2013-04-22T00:11:39Z", - "updated_time": "2013-04-22T00:11:39Z", + "updated_time": "null", "stack_status": "CREATE_COMPLETE", "id": "05b4f39f-ea96-4d91-910c-e758c078a089" }