Merge pull request #1157 from samsplunks/main

Allow new ticket statuses configuration in settings
This commit is contained in:
Christopher Broderick 2024-02-07 22:21:39 +00:00 committed by GitHub
commit f379bbe80e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 176 additions and 87 deletions

View File

@ -179,6 +179,95 @@ Options that change ticket updates
Options that change ticket properties
-------------------------------------
- **HELPDESK_TICKET_OPEN_STATUS** Customize the id of OPEN_STATUS status.
**Default:** ``HELPDESK_TICKET_OPEN_STATUS = 1``
- **HELPDESK_TICKET_REOPENED_STATUS** Customize the id of REOPENED_STATUS status.
**Default:** ``HELPDESK_TICKET_REOPENED_STATUS = 2``
- **HELPDESK_TICKET_RESOLVED_STATUS** Customize the id of RESOLVED_STATUS status.
**Default:** ``HELPDESK_TICKET_RESOLVED_STATUS = 3``
- **HELPDESK_TICKET_CLOSED_STATUS** Customize the id of CLOSED_STATUS status.
**Default:** ``HELPDESK_TICKET_CLOSED_STATUS = 4``
- **HELPDESK_TICKET_DUPLICATE_STATUS** Customize the id of DUPLICATE_STATUS status.
**Default:** ``HELPDESK_TICKET_DUPLICATE_STATUS = 5``
- **HELPDESK_TICKET_STATUS_CHOICES** Customize the list of status choices for all tickets.
The **default** is below::
HELPDESK_TICKET_STATUS_CHOICES = (
(HELPDESK_TICKET_OPEN_STATUS, _('Open')),
(HELPDESK_TICKET_REOPENED_STATUS, _('Reopened')),
(HELPDESK_TICKET_RESOLVED_STATUS, _('Resolved')),
(HELPDESK_TICKET_CLOSED_STATUS, _('Closed')),
(HELPDESK_TICKET_DUPLICATE_STATUS, _('Duplicate')),
)
If you wish to modify or introduce new status choices, you may add them like this::
# don't forget to import the gettext_lazy function at the begining of your settings file
from django.utils.translation import gettext_lazy as _
# explicitly define status list integer values
HELPDESK_TICKET_OPEN_STATUS = 1
HELPDESK_TICKET_REOPENED_STATUS = 2
HELPDESK_TICKET_RESOLVED_STATUS = 3
HELPDESK_TICKET_CLOSED_STATUS = 4
HELPDESK_TICKET_DUPLICATE_STATUS = 5
HELPDESK_TICKET_FORKED_STATUS = 6
# create the list with associated labels
HELPDESK_TICKET_STATUS_CHOICES = (
(HELPDESK_TICKET_OPEN_STATUS, _('Open')),
(HELPDESK_TICKET_REOPENED_STATUS, _('Reopened')),
(HELPDESK_TICKET_RESOLVED_STATUS, _('Resolved')),
(HELPDESK_TICKET_CLOSED_STATUS, _('Closed')),
(HELPDESK_TICKET_DUPLICATE_STATUS, _('Duplicate')),
(HELPDESK_TICKET_FORKED_STATUS, _('Forked')),
)
- **HELPDESK_TICKET_OPEN_STATUSES** Define the list of statuses to be considered as a type of open status.
**Default:** ``HELPDESK_TICKET_OPEN_STATUSES = (HELPDESK_TICKET_OPEN_STATUS, HELPDESK_TICKET_REOPENED_STATUS)``
If you have added the ``HELPDESK_TICKET_FORKED_STATUS`` status and wish to have django-helpdesk treat it as an open status choice, add it to the list of OPEN_STATUSES like this::
HELPDESK_TICKET_OPEN_STATUSES = (HELPDESK_TICKET_OPEN_STATUS,
HELPDESK_TICKET_REOPENED_STATUS,
HELPDESK_TICKET_FORKED_STATUS)
- **HELPDESK_TICKET_STATUS_CHOICES_FLOW** Customize the allowed state changes depending on the current state.
The **default** is below::
HELPDESK_TICKET_STATUS_CHOICES_FLOW = {
HELPDESK_TICKET_OPEN_STATUS: (HELPDESK_TICKET_OPEN_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
HELPDESK_TICKET_REOPENED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
HELPDESK_TICKET_RESOLVED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS,),
HELPDESK_TICKET_CLOSED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_CLOSED_STATUS,),
HELPDESK_TICKET_DUPLICATE_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
}
If you wish to modify or have introduce new status choices, you may configure their status change flow like this::
# adding HELPDESK_TICKET_FORKED_STATUS to the other allowed states flow and defining its own flow
HELPDESK_TICKET_STATUS_CHOICES_FLOW = {
HELPDESK_TICKET_OPEN_STATUS: (HELPDESK_TICKET_OPEN_STATUS, HELPDESK_TICKET_FORKED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
HELPDESK_TICKET_REOPENED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_FORKED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
HELPDESK_TICKET_RESOLVED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS,),
HELPDESK_TICKET_CLOSED_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_CLOSED_STATUS,),
HELPDESK_TICKET_DUPLICATE_STATUS: (HELPDESK_TICKET_REOPENED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
HELPDESK_TICKET_FORKED_STATUS: (HELPDESK_TICKET_OPEN_STATUS, HELPDESK_TICKET_FORKED_STATUS, HELPDESK_TICKET_RESOLVED_STATUS, HELPDESK_TICKET_CLOSED_STATUS, HELPDESK_TICKET_DUPLICATE_STATUS,),
}
- **HELPDESK_TICKET_PRIORITY_CHOICES** Customize the priority choices for all tickets.
The **default** is below::

View File

@ -186,10 +186,10 @@ class EditFollowUpForm(forms.ModelForm):
exclude = ('date', 'user',)
def __init__(self, *args, **kwargs):
"""Filter not openned tickets here."""
"""Filter not opened tickets here."""
super(EditFollowUpForm, self).__init__(*args, **kwargs)
self.fields["ticket"].queryset = Ticket.objects.filter(
status__in=(Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS))
status__in=Ticket.OPEN_STATUSES)
class AbstractTicketForm(CustomFieldMixin, forms.Form):

View File

@ -83,9 +83,12 @@ def escalate_tickets(queues, verbose):
if verbose:
print("Processing: %s" % q)
Q_OPEN_STATUSES = Q()
for open_status in Ticket.OPEN_STATUSES:
Q_OPEN_STATUSES |= Q(status=open_status)
for t in q.ticket_set.filter(
Q(status=Ticket.OPEN_STATUS) |
Q(status=Ticket.REOPENED_STATUS)
Q_OPEN_STATUSES
).exclude(
priority=1
).filter(

View File

@ -463,19 +463,15 @@ class Ticket(models.Model):
the dashboard to prompt users to take ownership of them.
"""
OPEN_STATUS = 1
REOPENED_STATUS = 2
RESOLVED_STATUS = 3
CLOSED_STATUS = 4
DUPLICATE_STATUS = 5
OPEN_STATUS = helpdesk_settings.OPEN_STATUS
REOPENED_STATUS = helpdesk_settings.REOPENED_STATUS
RESOLVED_STATUS = helpdesk_settings.RESOLVED_STATUS
CLOSED_STATUS = helpdesk_settings.CLOSED_STATUS
DUPLICATE_STATUS = helpdesk_settings.DUPLICATE_STATUS
STATUS_CHOICES = (
(OPEN_STATUS, _('Open')),
(REOPENED_STATUS, _('Reopened')),
(RESOLVED_STATUS, _('Resolved')),
(CLOSED_STATUS, _('Closed')),
(DUPLICATE_STATUS, _('Duplicate')),
)
STATUS_CHOICES = helpdesk_settings.TICKET_STATUS_CHOICES
OPEN_STATUSES = helpdesk_settings.TICKET_OPEN_STATUSES
STATUS_CHOICES_FLOW = helpdesk_settings.TICKET_STATUS_CHOICES_FLOW
PRIORITY_CHOICES = helpdesk_settings.TICKET_PRIORITY_CHOICES
@ -715,6 +711,22 @@ class Ticket(models.Model):
return u'%s%s%s' % (self.get_status_display(), held_msg, dep_msg)
get_status = property(_get_status)
def _get_allowed_status_flow(self):
"""
Returns the list of allowed ticket status modifications for current state.
"""
status_id_list = self.STATUS_CHOICES_FLOW.get(self.status, ())
if status_id_list:
# keep defined statuses in order and add labels for display
status_dict = dict(helpdesk_settings.TICKET_STATUS_CHOICES)
new_statuses = [(status_id, status_dict.get(status_id, _('No label')))
for status_id in status_id_list]
else:
# defaults to all choices if status was not mapped
new_statuses = helpdesk_settings.TICKET_STATUS_CHOICES
return new_statuses
get_allowed_status_flow = property(_get_allowed_status_flow)
def _get_ticket_url(self):
"""
Returns a publicly-viewable URL for this ticket, used when giving
@ -771,9 +783,8 @@ class Ticket(models.Model):
True = any dependencies are resolved
False = There are non-resolved dependencies
"""
OPEN_STATUSES = (Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS)
return TicketDependency.objects.filter(ticket=self).filter(
depends_on__status__in=OPEN_STATUSES).count() == 0
depends_on__status__in=Ticket.OPEN_STATUSES).count() == 0
can_be_resolved = property(_can_be_resolved)
def get_submitter_userprofile(self):

View File

@ -103,6 +103,42 @@ ALLOWED_URL_SCHEMES = getattr(settings, 'ALLOWED_URL_SCHEMES', (
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
))
# Ticket status choices
OPEN_STATUS = getattr(settings, 'HELPDESK_TICKET_OPEN_STATUS', 1)
REOPENED_STATUS = getattr(settings, 'HELPDESK_TICKET_REOPENED_STATUS', 2)
RESOLVED_STATUS = getattr(settings, 'HELPDESK_TICKET_RESOLVED_STATUS', 3)
CLOSED_STATUS = getattr(settings, 'HELPDESK_TICKET_CLOSED_STATUS', 4)
DUPLICATE_STATUS = getattr(settings, 'HELPDESK_TICKET_DUPLICATE_STATUS', 5)
DEFAULT_TICKET_STATUS_CHOICES = (
(OPEN_STATUS, _('Open')),
(REOPENED_STATUS, _('Reopened')),
(RESOLVED_STATUS, _('Resolved')),
(CLOSED_STATUS, _('Closed')),
(DUPLICATE_STATUS, _('Duplicate')),
)
TICKET_STATUS_CHOICES = getattr(settings,
'HELPDESK_TICKET_STATUS_CHOICES',
DEFAULT_TICKET_STATUS_CHOICES)
# List of status choices considered as "open"
DEFAULT_TICKET_OPEN_STATUSES = (OPEN_STATUS, REOPENED_STATUS)
TICKET_OPEN_STATUSES = getattr(settings,
'HELPDESK_TICKET_OPEN_STATUSES',
DEFAULT_TICKET_OPEN_STATUSES)
# New status list choices depending on current ticket status
DEFAULT_TICKET_STATUS_CHOICES_FLOW = {
OPEN_STATUS: (OPEN_STATUS, RESOLVED_STATUS, CLOSED_STATUS, DUPLICATE_STATUS,),
REOPENED_STATUS: (REOPENED_STATUS, RESOLVED_STATUS, CLOSED_STATUS, DUPLICATE_STATUS,),
RESOLVED_STATUS: (REOPENED_STATUS, RESOLVED_STATUS, CLOSED_STATUS,),
CLOSED_STATUS: (REOPENED_STATUS, CLOSED_STATUS,),
DUPLICATE_STATUS: (REOPENED_STATUS, DUPLICATE_STATUS,),
}
TICKET_STATUS_CHOICES_FLOW = getattr(settings,
'HELPDESK_TICKET_STATUS_CHOICES_FLOW',
DEFAULT_TICKET_STATUS_CHOICES_FLOW)
# Ticket priority choices
DEFAULT_TICKET_PRIORITY_CHOICES = (
(1, _('1. Critical')),
@ -112,8 +148,8 @@ DEFAULT_TICKET_PRIORITY_CHOICES = (
(5, _('5. Very Low')),
)
TICKET_PRIORITY_CHOICES = getattr(settings,
'HELPDESK_TICKET_PRIORITY_CHOICES',
DEFAULT_TICKET_PRIORITY_CHOICES)
'HELPDESK_TICKET_PRIORITY_CHOICES',
DEFAULT_TICKET_PRIORITY_CHOICES)
############################
# options for public pages #

View File

@ -107,31 +107,12 @@
{% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %}
{% if ticket.status == 1 %}
<input type="hidden" name="new_status" value="{{ ticket.status }}" />
{% endif %}
{% if ticket.status == 2 %}
<input type="hidden" name="new_status" value="{{ ticket.status }}" />
{% else %}
<dd><div class="form-group">
<label for='st_reopened' class='active radio-inline'><input type='radio' name='new_status' value='2' id='st_reopened' checked='checked'>{% trans "Reopened" %} &raquo;</label>
<label class="radio-inline" for='st_resolved'><input type='radio' name='new_status' value='3' id='st_resolved'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Resolved" %} &raquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Closed" %} &raquo;</label>
<label class="radio-inline" for='st_duplicate'><input type='radio' name='new_status' value='5' id='st_duplicate'>{% trans "Duplicate" %}</label>
</div></dd>
{% endif %}
{% if ticket.status == 3 %}
<dd><div class="form-group">
<label for='st_reopened' class="radio-inline"><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label for='st_resolved' class='active radio-inline'><input type='radio' name='new_status' value='3' id='st_resolved' checked='checked'>{% trans "Resolved" %} &raquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed'>{% trans "Closed" %}</label>
</div></dd>
{% endif %}
{% if ticket.status == 4 %}
<dd><div class="form-group"><label for='st_reopened' class="radio-inline"><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed' checked='checked'>{% trans "Closed" %}</label></div></dd>
{% endif %}
{% if ticket.status == 5 %}
<dd><div class="form-group">
<label class="radio-inline" for='st_reopened'><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label class="radio-inline" for='st_duplicate'><input type='radio' name='new_status' value='5' id='st_duplicate' checked='checked'>{% trans "Duplicate" %}</label>
{% for status_choice in ticket.get_allowed_status_flow %}
<label for='st_{{ status_choice.1|lower }}' class='{% if ticket.status == status_choice.0 %}active {% endif %}radio-inline'><input type='radio' name='new_status' value='{{ status_choice.0 }}' id='st_{{ status_choice.1|lower }}'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}{% if ticket.status == status_choice.0 %} checked='checked'{% endif %}>{{ status_choice.1 }}{% if forloop.last %}{% else %} &raquo;{% endif %}</label>
{% endfor %}
</div></dd>
{% endif %}

View File

@ -109,39 +109,11 @@
<dt><label>{% trans "New Status" %}</label></dt>
{% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %}
{% if ticket.status == 1 %}
<dd><div class="form-group">
<label for='st_open' class='active radio-inline'><input type='radio' name='new_status' value='1' id='st_open' checked='checked'>{% trans "Open" %} &raquo;</label>
<label for='st_resolved' class="radio-inline"><input type='radio' name='new_status' value='3' id='st_resolved'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Resolved" %} &raquo;</label>
<label for='st_closed' class="radio-inline"><input type='radio' name='new_status' value='4' id='st_closed'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Closed" %} &raquo;</label>
<label class="radio-inline" for='st_duplicate'><input type='radio' name='new_status' value='5' id='st_duplicate'>{% trans "Duplicate" %}</label>
{% for status_choice in ticket.get_allowed_status_flow %}
<label for='st_{{ status_choice.1|lower }}' class='{% if ticket.status == status_choice.0 %}active {% endif %}radio-inline'><input type='radio' name='new_status' value='{{ status_choice.0 }}' id='st_{{ status_choice.1|lower }}'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}{% if ticket.status == status_choice.0 %} checked='checked'{% endif %}>{{ status_choice.1 }}{% if forloop.last %}{% else %} &raquo;{% endif %}</label>
{% endfor %}
</div></dd>
{% endif %}
{% if ticket.status == 2 %}
<dd><div class="form-group">
<label for='st_reopened' class='active radio-inline'><input type='radio' name='new_status' value='2' id='st_reopened' checked='checked'>{% trans "Reopened" %} &raquo;</label>
<label class="radio-inline" for='st_resolved'><input type='radio' name='new_status' value='3' id='st_resolved'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Resolved" %} &raquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}>{% trans "Closed" %} &raquo;</label>
<label class="radio-inline" for='st_duplicate'><input type='radio' name='new_status' value='5' id='st_duplicate'>{% trans "Duplicate" %}</label>
</div></dd>
{% endif %}
{% if ticket.status == 3 %}
<dd><div class="form-group">
<label for='st_reopened' class="radio-inline"><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label for='st_resolved' class='active radio-inline'><input type='radio' name='new_status' value='3' id='st_resolved' checked='checked'>{% trans "Resolved" %} &raquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed'>{% trans "Closed" %}</label>
</div></dd>
{% endif %}
{% if ticket.status == 4 %}
<dd><div class="form-group"><label for='st_reopened' class="radio-inline"><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label class="radio-inline" for='st_closed'><input type='radio' name='new_status' value='4' id='st_closed' checked='checked'>{% trans "Closed" %}</label></div></dd>
{% endif %}
{% if ticket.status == 5 %}
<dd><div class="form-group">
<label class="radio-inline" for='st_reopened'><input type='radio' name='new_status' value='2' id='st_reopened'>{% trans "Reopened" %} &laquo;</label>
<label class="radio-inline" for='st_duplicate'><input type='radio' name='new_status' value='5' id='st_duplicate' checked='checked'>{% trans "Duplicate" %}</label>
</div></dd>
{% endif %}
{% if helpdesk_settings.HELPDESK_UPDATE_PUBLIC_DEFAULT %}
<input type='hidden' name='public' value='1'>

View File

@ -19,6 +19,11 @@ from helpdesk.models import FollowUp, Queue, Ticket
User = get_user_model()
Q_OPEN_STATUSES = Q()
for open_status in Ticket.OPEN_STATUSES:
Q_OPEN_STATUSES |= Q(status=open_status)
class OpenTicketsByUser(Feed):
title_template = 'helpdesk/rss/ticket_title.html'
description_template = 'helpdesk/rss/ticket_description.html'
@ -73,15 +78,11 @@ class OpenTicketsByUser(Feed):
assigned_to=obj['user']
).filter(
queue=obj['queue']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
).filter(Q_OPEN_STATUSES)
else:
return Ticket.objects.filter(
assigned_to=obj['user']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
).filter(Q_OPEN_STATUSES)
def item_pubdate(self, item):
return item.created
@ -104,9 +105,7 @@ class UnassignedTickets(Feed):
def items(self, obj):
return Ticket.objects.filter(
assigned_to__isnull=True
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
).filter(Q_OPEN_STATUSES)
def item_pubdate(self, item):
return item.created
@ -157,9 +156,7 @@ class OpenTicketsByQueue(Feed):
def items(self, obj):
return Ticket.objects.filter(
queue=obj
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
).filter(Q_OPEN_STATUSES)
def item_pubdate(self, item):
return item.created