Merge pull request #946 from alligatorbait/custom_datetime

CustomField datetime type formats updated to fixed string formats
This commit is contained in:
Garret Wassermann 2021-02-10 20:14:46 -05:00 committed by GitHub
commit db6202aac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 100 additions and 31 deletions

View File

@ -7,6 +7,7 @@ forms.py - Definitions of newforms-based forms for creating and maintaining
tickets.
"""
import logging
from datetime import datetime, date, time
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django import forms
@ -35,6 +36,10 @@ CUSTOMFIELD_TO_FIELD_DICT = {
'slug': forms.SlugField,
}
CUSTOMFIELD_DATE_FORMAT = "%Y-%m-%d"
CUSTOMFIELD_TIME_FORMAT = "%H:%M:%S"
CUSTOMFIELD_DATETIME_FORMAT = f"{CUSTOMFIELD_DATE_FORMAT} {CUSTOMFIELD_TIME_FORMAT}"
class CustomFieldMixin(object):
"""
@ -71,8 +76,14 @@ class CustomFieldMixin(object):
# Try to use the immediate equivalences dictionary
try:
fieldclass = CUSTOMFIELD_TO_FIELD_DICT[field.data_type]
# Change widget in case it is a boolean
if fieldclass == forms.BooleanField:
# Change widgets for the following classes
if fieldclass == forms.DateField:
instanceargs['widget'] = forms.DateInput(attrs={'class': 'form-control date-field'})
elif fieldclass == forms.DateTimeField:
instanceargs['widget'] = forms.DateTimeInput(attrs={'class': 'form-control datetime-field'})
elif fieldclass == forms.TimeField:
instanceargs['widget'] = forms.TimeInput(attrs={'class': 'form-control time-field'})
elif fieldclass == forms.BooleanField:
instanceargs['widget'] = forms.CheckboxInput(attrs={'class': 'form-control'})
except KeyError:
@ -88,6 +99,9 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
model = Ticket
exclude = ('created', 'modified', 'status', 'on_hold', 'resolution', 'last_escalation', 'assigned_to')
class Media:
js = ('helpdesk/js/init_due_date.js', 'helpdesk/js/init_datetime_classes.js')
def __init__(self, *args, **kwargs):
"""
Add any custom fields that are defined to the form
@ -99,14 +113,24 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
self.fields['merged_to'].help_text = _('This ticket is merged into the selected ticket.')
for field in CustomField.objects.all():
initial_value = None
try:
current_value = TicketCustomFieldValue.objects.get(ticket=self.instance, field=field)
initial_value = current_value.value
# Attempt to convert from fixed format string to date/time data type
if 'datetime' == current_value.field.data_type:
initial_value = datetime.strptime(initial_value, CUSTOMFIELD_DATETIME_FORMAT)
elif 'date' == current_value.field.data_type:
initial_value = datetime.strptime(initial_value, CUSTOMFIELD_DATE_FORMAT)
elif 'time' == current_value.field.data_type:
initial_value = datetime.strptime(initial_value, CUSTOMFIELD_TIME_FORMAT)
# If it is boolean field, transform the value to a real boolean instead of a string
if current_value.field.data_type == 'boolean':
initial_value = initial_value == 'True'
except TicketCustomFieldValue.DoesNotExist:
initial_value = None
elif 'boolean' == current_value.field.data_type:
initial_value = 'True' == initial_value
except (TicketCustomFieldValue.DoesNotExist, ValueError, TypeError):
# ValueError error if parsing fails, using initial_value = current_value.value
# TypeError if parsing None type
pass
instanceargs = {
'label': field.label,
'help_text': field.help_text,
@ -126,7 +150,16 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield)
except ObjectDoesNotExist:
cfv = TicketCustomFieldValue(ticket=self.instance, field=customfield)
cfv.value = value
# Convert date/time data type to known fixed format string.
if datetime is type(value):
cfv.value = value.strftime(CUSTOMFIELD_DATETIME_FORMAT)
elif date is type(value):
cfv.value = value.strftime(CUSTOMFIELD_DATE_FORMAT)
elif time is type(value):
cfv.value = value.strftime(CUSTOMFIELD_TIME_FORMAT)
else:
cfv.value = value
cfv.save()
return super(EditTicketForm, self).save(*args, **kwargs)
@ -182,7 +215,7 @@ class AbstractTicketForm(CustomFieldMixin, forms.Form):
due_date = forms.DateTimeField(
widget=forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'off'}),
required=False,
input_formats=['%d/%m/%Y', '%m/%d/%Y', "%d.%m.%Y"],
input_formats=[CUSTOMFIELD_DATE_FORMAT, CUSTOMFIELD_DATETIME_FORMAT, '%d/%m/%Y', '%m/%d/%Y', "%d.%m.%Y"],
label=_('Due on'),
)
@ -194,7 +227,7 @@ class AbstractTicketForm(CustomFieldMixin, forms.Form):
)
class Media:
js = ('helpdesk/js/init_due_date.js',)
js = ('helpdesk/js/init_due_date.js', 'helpdesk/js/init_datetime_classes.js')
def __init__(self, kbcategory=None, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -0,0 +1,10 @@
$(() => {
$(".date-field").datepicker({dateFormat: 'yy-mm-dd'});
});
$(() => {
$(".datetime-field").datepicker({dateFormat: 'yy-mm-dd 00:00:00'});
});
$(() => {
// TODO: This does not work as written, need to make functional
$(".time-field").tooltip="Time format 24hr: 00:00:00";
});

View File

@ -1,3 +1,3 @@
$(() => {
$("#id_due_date").datepicker();
$("#id_due_date").datepicker({dateFormat: 'yy-mm-dd 00:00:00'});
});

View File

@ -26,7 +26,7 @@
<strong>{% trans "Note" %}:</strong>
{% blocktrans %}Editing a ticket does <em>not</em> send an e-mail to the ticket owner or submitter. No new details should be entered, this form should only be used to fix incorrect details or clean up the submission.{% endblocktrans %}
</p>
{% if errors %}{% for error in errors %}{% trans "Error: " %}{{ error }}{% endfor %}{% endif %}
{% if errors %}<p class="text-danger">{% for error in errors %}{% trans "Error: " %}{{ error }}<br>{% endfor %}</p>{% endif %}
<form method='post'>
{% csrf_token %}
<fieldset>
@ -45,9 +45,5 @@
{% endblock %}
{% block helpdesk_js %}
<script>
$(() => {
$("#id_due_date").datepicker({dateFormat: 'yy-mm-dd'});
})
</script>
{{ form.media.js }}
{% endblock %}

View File

@ -1,5 +1,6 @@
{% load i18n humanize ticket_to_link %}
{% load static %}
{% load helpdesk_util %}
<div class="card mb-3">
<!--div class="card-header">
@ -9,7 +10,7 @@
<div class="table-responsive">
<table class="table table-sm table-border">
<thead class="thead-light">
<tr class=''><th colspan='4'><h3>{{ ticket.queue.slug }}-{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3>
<tr class=''><th colspan='4'><h3>{{ ticket.queue.slug }}-{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3>
{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}
<span class='ticket_toolbar float-right'>
<a href="{% url 'helpdesk:edit' ticket.id %}" class="ticket-edit"><button class="btn btn-warning btn-sm"><i class="fas fa-pencil-alt"></i> {% trans "Edit" %}</button></a>
@ -21,20 +22,25 @@
{% for customfield in ticket.ticketcustomfieldvalue_set.all %}
<tr>
<th class="table-secondary">{{ customfield.field.label }}</th>
<td>{% ifequal customfield.field.data_type "url" %}<a href='{{ customfield.value }}'>{{ customfield.value }}</a>{% else %}{{ customfield.value|default:"" }}{% endifequal %}</td>
<td>{% spaceless %}{% if "url" == customfield.field.data_type %}<a href='{{ customfield.value }}'>{{ customfield.value }}</a>
{% elif "datetime" == customfield.field.data_type %}{{ customfield.value|datetime_string_format }}
{% elif "date" == customfield.field.data_type %}{{ customfield.value|datetime_string_format }}
{% elif "time" == customfield.field.data_type %}{{ customfield.value|datetime_string_format }}
{% else %}{{ customfield.value|default:"" }}
{% endif %}{% endspaceless %}</td>
</tr>{% endfor %}
<tr>
<th class="table-active">{% trans "Due Date" %}</th>
<td>{{ ticket.due_date|date }} {% if ticket.due_date %}({{ ticket.due_date|naturaltime }}){% endif %}
<td>{{ ticket.due_date|date:"DATETIME_FORMAT" }} {% if ticket.due_date %}({{ ticket.due_date|naturaltime }}){% endif %}
</td>
<th class="table-active">{% trans "Submitted On" %}</th>
<td>{{ ticket.created|date }} ({{ ticket.created|naturaltime }})</td>
<td>{{ ticket.created|date:"DATETIME_FORMAT" }} ({{ ticket.created|naturaltime }})</td>
</tr>
<tr>
<th class="table-active">{% trans "Assigned To" %}</th>
<td>{{ ticket.get_assigned_to }}{% ifequal ticket.get_assigned_to _('Unassigned') %} <strong>
<td>{{ ticket.get_assigned_to }}{% if _('Unassigned') == ticket.get_assigned_to %} <strong>
<a data-toggle="tooltip" href='?take' title='{% trans "Assign this ticket to " %}{{ request.user.email }}'><button type="button" class="btn btn-primary btn-sm float-right"><i class="fas fa-hand-paper"></i></button></a>
</strong>{% endifequal %}
</strong>{% endif %}
</td>
<th class="table-active">{% trans "Submitter E-Mail" %}</th>
<td> {{ ticket.submitter_email }}
@ -97,7 +103,7 @@
</tr>
{% if ticket.resolution %}<tr>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><button type="button" class="btn btn-warning btn-sm">{% trans "Accept and Close" %}</button></a>{% endifequal %}</th>
<th colspan='2'>{% trans "Resolution" %}{% if "Resolved" == ticket.get_status_display %} <a href='?close'><button type="button" class="btn btn-warning btn-sm">{% trans "Accept and Close" %}</button></a>{% endif %}</th>
</tr>
<tr>
<td colspan='2'>{{ ticket.get_resolution_markdown|urlizetrunc:50|linebreaksbr }}</td>

View File

@ -1,9 +1,34 @@
from django import template
from django.template import Library
from django.template.defaultfilters import date as date_filter
from django.conf import settings
register = template.Library()
from datetime import datetime
from helpdesk.forms import CUSTOMFIELD_DATE_FORMAT, CUSTOMFIELD_TIME_FORMAT, CUSTOMFIELD_DATETIME_FORMAT
register = Library()
@register.filter
def get(value, arg, default=None):
""" Call the dictionary get function """
return value.get(arg, default)
@register.filter(expects_localtime=True)
def datetime_string_format(value):
"""
:param value: String - Expected to be a datetime, date, or time in specific format
:return: String - reformatted to default datetime, date, or time string if received in one of the expected formats
"""
try:
new_value = date_filter(datetime.strptime(value, CUSTOMFIELD_DATETIME_FORMAT), settings.DATETIME_FORMAT)
except ValueError:
try:
new_value = date_filter(datetime.strptime(value, CUSTOMFIELD_DATE_FORMAT), settings.DATE_FORMAT)
except ValueError:
try:
new_value = date_filter(datetime.strptime(value, CUSTOMFIELD_TIME_FORMAT), settings.TIME_FORMAT)
except ValueError:
new_value = value
return new_value

View File

@ -47,7 +47,7 @@ class KBTests(TestCase):
self.assertContains(response, 'This is a test category')
self.assertContains(response, 'KBItem 1')
self.assertContains(response, 'KBItem 2')
self.assertContains(response, 'Contact a human')
self.assertContains(response, 'Create New Ticket Queue:')
self.client.login(username=self.user.get_username(), password='password')
response = self.client.get(reverse('helpdesk:kb_category', args=("test_cat", )))
self.assertContains(response, '<i class="fa fa-thumbs-up fa-lg"></i>')

View File

@ -24,6 +24,7 @@ from django.utils.html import escape
from django.utils import timezone
from django.views.generic.edit import FormView, UpdateView
from helpdesk.forms import CUSTOMFIELD_DATE_FORMAT
from helpdesk.query import (
get_query_class,
query_to_base64,
@ -75,9 +76,6 @@ else:
lambda u: u.is_authenticated and u.is_active and u.is_staff)
User = get_user_model()
def _get_queue_choices(queues):
"""Return list of `choices` array for html form for given queues
@ -382,6 +380,7 @@ def view_ticket(request, ticket_id):
)
else:
submitter_userprofile_url = None
return render(request, 'helpdesk/ticket.html', {
'ticket': ticket,
'submitter_userprofile_url': submitter_userprofile_url,
@ -1774,8 +1773,8 @@ def calc_basic_ticket_stats(Tickets):
date_30 = date_rel_to_today(today, 30)
date_60 = date_rel_to_today(today, 60)
date_30_str = date_30.strftime('%Y-%m-%d')
date_60_str = date_60.strftime('%Y-%m-%d')
date_30_str = date_30.strftime(CUSTOMFIELD_DATE_FORMAT)
date_60_str = date_60.strftime(CUSTOMFIELD_DATE_FORMAT)
# > 0 & <= 30
ota_le_30 = all_open_tickets.filter(created__gte=date_30_str)