* Introduce the isodate(), isotime(), and isodatetime() template filters * Display the relative time on mouse hover * Render journal entry times in ISO 8601 format * Use ISO 8601 format when displaying dates & times in a table * Standardize the use of DateTimeColumn across all tables
This commit is contained in:
parent
f0aca5bac1
commit
77a4300888
|
@ -30,10 +30,12 @@ class UserTokenTable(NetBoxTable):
|
|||
write_enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Write Enabled')
|
||||
)
|
||||
created = columns.DateColumn(
|
||||
created = columns.DateTimeColumn(
|
||||
timespec='minutes',
|
||||
verbose_name=_('Created'),
|
||||
)
|
||||
expires = columns.DateColumn(
|
||||
expires = columns.DateTimeColumn(
|
||||
timespec='minutes',
|
||||
verbose_name=_('Expires'),
|
||||
)
|
||||
last_used = columns.DateTimeColumn(
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django_tables2.utils import A
|
||||
|
||||
from core.tables.columns import RQJobStatusColumn
|
||||
from netbox.tables import BaseTable
|
||||
from netbox.tables import BaseTable, columns
|
||||
|
||||
|
||||
class BackgroundQueueTable(BaseTable):
|
||||
|
@ -75,13 +75,13 @@ class BackgroundTaskTable(BaseTable):
|
|||
linkify=("core:background_task", [A("id")]),
|
||||
verbose_name=_("ID")
|
||||
)
|
||||
created_at = tables.DateTimeColumn(
|
||||
created_at = columns.DateTimeColumn(
|
||||
verbose_name=_("Created")
|
||||
)
|
||||
enqueued_at = tables.DateTimeColumn(
|
||||
enqueued_at = columns.DateTimeColumn(
|
||||
verbose_name=_("Enqueued")
|
||||
)
|
||||
ended_at = tables.DateTimeColumn(
|
||||
ended_at = columns.DateTimeColumn(
|
||||
verbose_name=_("Ended")
|
||||
)
|
||||
status = RQJobStatusColumn(
|
||||
|
@ -117,7 +117,7 @@ class WorkerTable(BaseTable):
|
|||
state = tables.Column(
|
||||
verbose_name=_("State")
|
||||
)
|
||||
birth_date = tables.DateTimeColumn(
|
||||
birth_date = columns.DateTimeColumn(
|
||||
verbose_name=_("Birth")
|
||||
)
|
||||
pid = tables.Column(
|
||||
|
|
|
@ -732,7 +732,7 @@ class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ExportTemplat
|
|||
|
||||
def __str__(self):
|
||||
created = timezone.localtime(self.created)
|
||||
return f"{date_format(created, format='SHORT_DATETIME_FORMAT')} ({self.get_kind_display()})"
|
||||
return f"{created.date().isoformat()} {created.time().isoformat(timespec='minutes')} ({self.get_kind_display()})"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('extras:journalentry', args=[self.pk])
|
||||
|
|
|
@ -432,10 +432,10 @@ class ConfigTemplateTable(NetBoxTable):
|
|||
|
||||
|
||||
class ObjectChangeTable(NetBoxTable):
|
||||
time = tables.DateTimeColumn(
|
||||
time = columns.DateTimeColumn(
|
||||
verbose_name=_('Time'),
|
||||
linkify=True,
|
||||
format=settings.SHORT_DATETIME_FORMAT
|
||||
timespec='minutes',
|
||||
linkify=True
|
||||
)
|
||||
user_name = tables.Column(
|
||||
verbose_name=_('Username')
|
||||
|
@ -475,10 +475,10 @@ class ObjectChangeTable(NetBoxTable):
|
|||
|
||||
|
||||
class JournalEntryTable(NetBoxTable):
|
||||
created = tables.DateTimeColumn(
|
||||
created = columns.DateTimeColumn(
|
||||
verbose_name=_('Created'),
|
||||
linkify=True,
|
||||
format=settings.SHORT_DATETIME_FORMAT
|
||||
timespec='minutes',
|
||||
linkify=True
|
||||
)
|
||||
assigned_object_type = columns.ContentTypeColumn(
|
||||
verbose_name=_('Object Type')
|
||||
|
|
|
@ -10,7 +10,6 @@ from django.db.models import DateField, DateTimeField
|
|||
from django.template import Context, Template
|
||||
from django.urls import reverse
|
||||
from django.utils.dateparse import parse_date
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -52,18 +51,17 @@ __all__ = (
|
|||
#
|
||||
|
||||
@library.register
|
||||
class DateColumn(tables.DateColumn):
|
||||
class DateColumn(tables.Column):
|
||||
"""
|
||||
Overrides the default implementation of DateColumn to better handle null values, returning a default value for
|
||||
tables and null when exporting data. It is registered in the tables library to use this class instead of the
|
||||
default, making this behavior consistent in all fields of type DateField.
|
||||
Render a datetime.date in ISO 8601 format.
|
||||
"""
|
||||
def render(self, value):
|
||||
if value:
|
||||
return date_format(value, format="SHORT_DATE_FORMAT")
|
||||
return value.isoformat()
|
||||
|
||||
def value(self, value):
|
||||
return value
|
||||
if value:
|
||||
return value.isoformat()
|
||||
|
||||
@classmethod
|
||||
def from_field(cls, field, **kwargs):
|
||||
|
@ -72,16 +70,24 @@ class DateColumn(tables.DateColumn):
|
|||
|
||||
|
||||
@library.register
|
||||
class DateTimeColumn(tables.DateTimeColumn):
|
||||
class DateTimeColumn(tables.Column):
|
||||
"""
|
||||
Overrides the default implementation of DateTimeColumn to better handle null values, returning a default value for
|
||||
tables and null when exporting data. It is registered in the tables library to use this class instead of the
|
||||
default, making this behavior consistent in all fields of type DateTimeField.
|
||||
Render a datetime.datetime in ISO 8601 format.
|
||||
|
||||
Args:
|
||||
timespec: Granularity specification; passed through to datetime.isoformat()
|
||||
"""
|
||||
def __init__(self, *args, timespec='seconds', **kwargs):
|
||||
self.timespec = timespec
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, value):
|
||||
if value:
|
||||
return f"{value.date().isoformat()} {value.time().isoformat(timespec=self.timespec)}"
|
||||
|
||||
def value(self, value):
|
||||
if value:
|
||||
return date_format(value, format="SHORT_DATETIME_FORMAT")
|
||||
return None
|
||||
return value.isoformat()
|
||||
|
||||
@classmethod
|
||||
def from_field(cls, field, **kwargs):
|
||||
|
@ -498,7 +504,7 @@ class CustomFieldColumn(tables.Column):
|
|||
if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value:
|
||||
return render_markdown(value)
|
||||
if self.customfield.type == CustomFieldTypeChoices.TYPE_DATE and value:
|
||||
return date_format(parse_date(value), format="SHORT_DATE_FORMAT")
|
||||
return parse_date(value).isoformat()
|
||||
if value is not None:
|
||||
obj = self.customfield.deserialize(value)
|
||||
return mark_safe(self._linkify_item(obj))
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Account Created" %}</th>
|
||||
<td>{{ request.user.date_joined|annotated_date }}</td>
|
||||
<td>{{ request.user.date_joined|isodate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Last Login" %}</th>
|
||||
<td>{{ request.user.last_login|annotated_date }}</td>
|
||||
<td>{{ request.user.last_login|isodatetime:"minutes"|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Superuser" %}</th>
|
||||
|
|
|
@ -41,15 +41,15 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created" %}</th>
|
||||
<td>{{ object.created|annotated_date }}</td>
|
||||
<td>{{ object.created|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Expires" %}</th>
|
||||
<td>{{ object.expires|placeholder }}</td>
|
||||
<td>{{ object.expires|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Last used" %}</th>
|
||||
<td>{{ object.last_used|placeholder }}</td>
|
||||
<td>{{ object.last_used|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Allowed IPs" %}</th>
|
||||
|
|
|
@ -45,11 +45,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Install Date" %}</th>
|
||||
<td>{{ object.install_date|annotated_date|placeholder }}</td>
|
||||
<td>{{ object.install_date|isodate|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Termination Date" %}</th>
|
||||
<td>{{ object.termination_date|annotated_date|placeholder }}</td>
|
||||
<td>{{ object.termination_date|isodate|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Commit Rate" %}</th>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
{% block subtitle %}
|
||||
{% if object.created %}
|
||||
<div class="text-secondary fs-5">
|
||||
{% trans "Created" %} {{ object.created|annotated_date }}
|
||||
{% trans "Created" %} {{ object.created|isodatetime }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock subtitle %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
{% block subtitle %}
|
||||
<div class="text-secondary fs-5">
|
||||
{% trans "Created" %} {{ object.created|annotated_date }}
|
||||
{% trans "Created" %} {{ object.created|isodatetime }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -49,12 +49,12 @@
|
|||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created" %}</th>
|
||||
<td>{{ object.created|annotated_date }}</td>
|
||||
<td>{{ object.created|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Scheduled" %}</th>
|
||||
<td>
|
||||
{{ object.scheduled|annotated_date|placeholder }}
|
||||
{{ object.scheduled|isodatetime|placeholder }}
|
||||
{% if object.interval %}
|
||||
({% blocktrans with interval=object.interval %}every {{ interval }} minutes{% endblocktrans %})
|
||||
{% endif %}
|
||||
|
@ -62,11 +62,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Started" %}</th>
|
||||
<td>{{ object.started|annotated_date|placeholder }}</td>
|
||||
<td>{{ object.started|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Completed" %}</th>
|
||||
<td>{{ object.completed|annotated_date|placeholder }}</td>
|
||||
<td>{{ object.completed|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
{% block subtitle %}
|
||||
<div class="text-secondary fs-5">
|
||||
<span>{% trans "Created" %} {{ job.created_at|annotated_date }}</span>
|
||||
<span>{% trans "Created" %} {{ job.created_at|isodatetime }}</span>
|
||||
</div>
|
||||
{% endblock subtitle %}
|
||||
|
||||
|
@ -71,11 +71,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created" %}</th>
|
||||
<td>{{ job.created_at|annotated_date }}</td>
|
||||
<td>{{ job.created_at|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Queued" %}</th>
|
||||
<td>{{ job.enqueued_at|annotated_date }}</td>
|
||||
<td>{{ job.enqueued_at|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Status" %}</th>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
{% block subtitle %}
|
||||
<div class="text-secondary fs-5">
|
||||
<span>{% trans "Created" %} {{ worker.birth_date|annotated_date }}</span>
|
||||
<span>{% trans "Created" %} {{ worker.birth_date|isodatetime }}</span>
|
||||
</div>
|
||||
{% endblock subtitle %}
|
||||
|
||||
|
@ -49,7 +49,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Birth" %}</th>
|
||||
<td>{{ worker.birth_date|annotated_date }}</td>
|
||||
<td>{{ worker.birth_date|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Queues" %}</th>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
{% load humanize %}
|
||||
{% load helpers %}
|
||||
{% load log_levels %}
|
||||
{% load i18n %}
|
||||
|
@ -6,11 +5,11 @@
|
|||
<div class="htmx-container">
|
||||
<p>
|
||||
{% if job.started %}
|
||||
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
|
||||
{% trans "Started" %}: <strong>{{ job.started|isodatetime }}</strong>
|
||||
{% elif job.scheduled %}
|
||||
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
||||
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|isodatetime }}</strong>
|
||||
{% else %}
|
||||
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
|
||||
{% trans "Created" %}: <strong>{{ job.created|isodatetime }}</strong>
|
||||
{% endif %}
|
||||
{% if job.completed %}
|
||||
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created" %}</th>
|
||||
<td>{{ object.created|annotated_date }}</td>
|
||||
<td>{{ object.created|isodatetime:"minutes" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created By" %}</th>
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Time" %}</th>
|
||||
<td>
|
||||
{{ object.time|annotated_date }}
|
||||
</td>
|
||||
<td>{{ object.time|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "User" %}</th>
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<td>{{ script.description|markdown|placeholder }}</td>
|
||||
{% if last_job %}
|
||||
<td>
|
||||
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|annotated_date }}</a>
|
||||
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% badge last_job.get_status_display last_job.get_status_color %}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">{% trans "Scripts" %}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ script.get_absolute_url }}">{{ script }}</a></li>
|
||||
<li class="breadcrumb-item">{{ job.created|annotated_date }}</li>
|
||||
<li class="breadcrumb-item">{{ job.created|isodatetime }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
@ -48,10 +48,10 @@ Context:
|
|||
|
||||
{% block subtitle %}
|
||||
<div class="text-secondary fs-5">
|
||||
<span>{% trans "Created" %} {{ object.created|annotated_date }}</span>
|
||||
{% trans "Created" %} {{ object.created|isodatetime:"minutes" }}
|
||||
{% if object.last_updated %}
|
||||
<span class="separator">·</span>
|
||||
<span>{% trans "Updated" %} <span title="{{ object.last_updated }}">{{ object.last_updated|timesince }}</span> {% trans "ago" %}</span>
|
||||
{% trans "Updated" %} {{ object.last_updated|isodatetime:"minutes" }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock subtitle %}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Date Added" %}</th>
|
||||
<td>{{ object.date_added|annotated_date|placeholder }}</td>
|
||||
<td>{{ object.date_added|isodate|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Description" %}</th>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends 'generic/object.html' %}
|
||||
{% load i18n %}
|
||||
{% load helpers %}
|
||||
{% load i18n %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}{% trans "Token" %} {{ object }}{% endblock %}
|
||||
|
@ -33,15 +33,15 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Created" %}</th>
|
||||
<td>{{ object.created|annotated_date }}</td>
|
||||
<td>{{ object.created|isodatetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Expires" %}</th>
|
||||
<td>{{ object.expires|placeholder }}</td>
|
||||
<td>{{ object.expires|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Last used" %}</th>
|
||||
<td>{{ object.last_used|placeholder }}</td>
|
||||
<td>{{ object.last_used|isodatetime|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Allowed IPs" %}</th>
|
||||
|
|
|
@ -27,11 +27,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Account Created" %}</th>
|
||||
<td>{{ object.date_joined|annotated_date }}</td>
|
||||
<td>{{ object.date_joined|isodate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Last Login" %}</th>
|
||||
<td>{{ object.last_login|annotated_date }}</td>
|
||||
<td>{{ object.last_login|isodatetime:"minutes"|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Active" %}</th>
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
{% elif customfield.type == 'boolean' and value == False %}
|
||||
{% checkmark value false="False" %}
|
||||
{% elif customfield.type == 'date' and value %}
|
||||
{{ value|annotated_date }}
|
||||
{{ value|isodate }}
|
||||
{% elif customfield.type == 'datetime' and value %}
|
||||
{{ value|annotated_date }}
|
||||
{{ value|isodate }} {{ value|isodatetime }}
|
||||
{% elif customfield.type == 'url' and value %}
|
||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||
{% elif customfield.type == 'json' and value %}
|
||||
|
|
|
@ -5,6 +5,7 @@ import re
|
|||
import yaml
|
||||
from django import template
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.humanize.templatetags.humanize import naturaltime
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from markdown import markdown
|
||||
|
@ -20,6 +21,9 @@ __all__ = (
|
|||
'content_type',
|
||||
'content_type_id',
|
||||
'fgcolor',
|
||||
'isodate',
|
||||
'isodatetime',
|
||||
'isotime',
|
||||
'linkify',
|
||||
'meta',
|
||||
'placeholder',
|
||||
|
@ -202,3 +206,36 @@ def render_yaml(value):
|
|||
{{ data_dict|yaml }}
|
||||
"""
|
||||
return yaml.dump(json.loads(json.dumps(value)))
|
||||
|
||||
|
||||
#
|
||||
# Time & date
|
||||
#
|
||||
|
||||
@register.filter()
|
||||
def isodate(value):
|
||||
if type(value) is datetime.date:
|
||||
text = value.isoformat()
|
||||
elif type(value) is datetime.datetime:
|
||||
text = value.date().isoformat()
|
||||
else:
|
||||
return ''
|
||||
return mark_safe(f'<span title="{naturaltime(value)}">{text}</span>')
|
||||
|
||||
|
||||
@register.filter()
|
||||
def isotime(value, spec='seconds'):
|
||||
if type(value) is datetime.time:
|
||||
return value.isoformat(timespec=spec)
|
||||
if type(value) is datetime.datetime:
|
||||
return value.time().isoformat(timespec=spec)
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter()
|
||||
def isodatetime(value, spec='seconds'):
|
||||
if type(value) is datetime.datetime:
|
||||
text = f'{isodate(value)} {isotime(value, spec=spec)}'
|
||||
else:
|
||||
return ''
|
||||
return mark_safe(f'<span title="{naturaltime(value)}">{text}</span>')
|
||||
|
|
Loading…
Reference in New Issue