mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2024-12-12 18:00:45 +01:00
Merge pull request #946 from alligatorbait/custom_datetime
CustomField datetime type formats updated to fixed string formats
This commit is contained in:
commit
db6202aac0
@ -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)
|
||||
|
10
helpdesk/static/helpdesk/js/init_datetime_classes.js
Normal file
10
helpdesk/static/helpdesk/js/init_datetime_classes.js
Normal 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";
|
||||
});
|
@ -1,3 +1,3 @@
|
||||
$(() => {
|
||||
$("#id_due_date").datepicker();
|
||||
$("#id_due_date").datepicker({dateFormat: 'yy-mm-dd 00:00:00'});
|
||||
});
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>')
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user