Merge pull request #1185 from jorge-leon/reverse-dependencies

Add list of 'parent' tickets to ticket description table, solves #1184
This commit is contained in:
Christopher Broderick 2024-06-14 17:29:02 +01:00 committed by GitHub
commit 437287edbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 26 deletions

View File

@ -589,7 +589,26 @@ class TicketDependencyForm(forms.ModelForm):
class Meta:
model = TicketDependency
exclude = ('ticket',)
fields = ('depends_on',)
def __init__(self, ticket, *args, **kwargs):
super(TicketDependencyForm,self).__init__(*args, **kwargs)
# Only open tickets except myself, existing dependencies and parents
self.fields['depends_on'].queryset = Ticket.objects.filter(status__in=Ticket.OPEN_STATUSES).exclude(id=ticket.id).exclude(depends_on__ticket=ticket).exclude(ticketdependency__depends_on=ticket)
class TicketResolvesForm(forms.ModelForm):
''' Adds this ticket as a dependency for a different ticket '''
class Meta:
model = TicketDependency
fields = ('ticket',)
def __init__(self, ticket, *args, **kwargs):
super(TicketResolvesForm,self).__init__(*args, **kwargs)
# Only open tickets except myself, existing dependencies and parents
self.fields['ticket'].queryset = Ticket.objects.exclude(status__in=Ticket.OPEN_STATUSES).exclude(id=ticket.id).exclude(depends_on__ticket=ticket).exclude(ticketdependency__depends_on=ticket)
class MultipleTicketSelectForm(forms.Form):

View File

@ -12,7 +12,7 @@
<thead class="thead-light">
<tr class=''>
<th colspan='4'>
<h3>{{ ticket.queue.slug }}-{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3>
<h3>{{ ticket.queue.slug }}-{{ ticket.id }}. {{ ticket.title }}</h3>
{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}
<span class='ticket_toolbar float-right'>
<a href="{% url 'helpdesk:edit' ticket.id %}" class="btn btn-warning btn-sm ticket-edit">
@ -117,24 +117,9 @@
</td>
</tr>
{% if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET != False and helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET != False %}
<tr>
{% if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET %}
<th class="table-active">{% trans "Dependencies" %}</th>
<td>
<a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_add' ticket.id %}' title="{% trans "Click on 'Add Dependency', if you want to make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed." %}"><button type="button" class="btn btn-primary btn-sm float-right"><i class="fas fa-link"></i></button></a>
{% for dep in ticket.ticketdependency.all %}
{% if forloop.first %}<p>{% trans "This ticket cannot be resolved until the following ticket(s) are resolved" %}</p><ul>{% endif %}
<li><a href='{{ dep.depends_on.get_absolute_url }}'>{{ dep.depends_on.ticket }} {{ dep.depends_on.title }}</a> ({{ dep.depends_on.get_status_display }}) <a href='{% url 'helpdesk:ticket_dependency_del' ticket.id dep.id %}'><button type="button" class="btn btn-warning btn-sm"><i class="fas fa-trash"></i></button></a></li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
{% trans "This ticket has no dependencies." %}
{% endfor %}
</td>
{% else %}
<th class="table-active"></th>
<td></td>
{% endif %}
<tr>
<th class="table-active">{% trans "Status" %}</th>
<td>{{ ticket.get_status }}</td>
{% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %}
<th class="table-active">{% trans "Total time spent" %}</th>
<td>{{ ticket.time_spent_formated }}</td>
@ -142,8 +127,64 @@
<th class="table-active"></th>
<td></td>
{% endif %}
</tr>
{% endif %}
</tr>
{% if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET != False %}
<tr>
<th class="table-active">
{% trans "Resolves" %}
<a data-toggle='tooltip' href='{% url 'helpdesk:ticket_resolves_add' ticket.id %}'
title='{% trans "Make this ticket resolve another ticket." %}'>
<button type="button" class="btn btn-primary btn-sm float-right"><i class="fas fa-link"></i></button></a>
</th>
<td colspan="3" class="p-0">
{% for resolves in ticket.depends_on.all %}
{% if forloop.first %}<table class="table table-borderless table-responsive m-0">{% endif %}
<tr>
<td>
<a data-toggle='tooltip' href='{% url 'helpdesk:ticket_resolves_del' resolves.ticket.id resolves.id %}'
title='{% trans "Drop the dependency on this ticket. A ticket may not be closed until all tickets it depends on are closed or removed." %}'>
<button type="button" class="btn btn-warning btn-sm"><i class="fas fa-trash"></i></button></a>
</td>
<td>{{ resolves.ticket.get_status_display }}</td>
<td>
<a href='{{ resolves.ticket.get_absolute_url }}'>{{ resolves.ticket.ticket }} {{ resolves.ticket.title }}</a>
</td>
</tr>
{% if forloop.last %}</table>{% endif %}
{% empty %}
<small class="p-2">{% trans "This ticket does not resolve any other" %}</small>
{% endfor %}
</td>
</tr>
<tr>
<th class="table-active">
{% trans "Depends" %}
<a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_add' ticket.id %}'
title='{% trans "Make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed or removed." %}'>
<button type="button" class="btn btn-primary btn-sm float-right"><i class="fas fa-link"></i></button></a>
</th>
<td colspan="3" class="p-0">
{% for dep in dependencies %}
{% if forloop.first %}<table class="table table-borderless table-hover table-responsive m-0">{% endif %}
<tr>
<td>
<a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_del' ticket.id dep.id %}'
title='{% trans "Drop the dependency on this ticket. A ticket may not be closed until all tickets it depends on are closed or removed." %}'>
<button type="button" class="btn btn-warning btn-sm"><i class="fas fa-trash"></i></button></a>
</td>
<td>{{ dep.depends_on.get_status_display }}</td>
<td>
<a href='{{ dep.depends_on.get_absolute_url }}'>{{ dep.depends_on.ticket }} {{ dep.depends_on.title }}</a>
</td>
{% if forloop.last %}</table>{% endif %}
{% empty %}
<small class="p-2">{% trans "This ticket has no dependencies." %}</small>
{% endfor %}
</td>
</tr>
{% endif %}
{% if ticket.kbitem %}
<tr>
<th class="table-active">{% trans "Knowlegebase item" %}</th>

View File

@ -0,0 +1,37 @@
{% extends "helpdesk/base.html" %}{% load i18n %}
{% block helpdesk_title %}{% trans "Add Ticket as Dependency" %}{% endblock %}
{% block helpdesk_breadcrumb %}
<li class="breadcrumb-item">
<a href="{% url 'helpdesk:list' %}">{% trans "Tickets" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'helpdesk:list' %}{{ depends_on.id }}/">{{ depends_on.queue.slug }}-{{ depends_on.id }}</a>
</li>
<li class="breadcrumb-item active">{% trans "Add Ticket as Dependency" %}</li>
{% endblock %}
{% block helpdesk_body %}{% blocktrans %}
<h2>Add Ticket as Dependency</h2>
<p>Ticket: {{ depends_on }} </p>
<p>Adding this ticket as dependency will stop you resolving the selected ticket until this ticket has been resolved or closed.</p>{% endblocktrans %}
<form method='post' action='./'>
<fieldset>
<dl>{% for field in form %}
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
<dd>{{ field }}</dd>
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
{% endfor %}</dl>
</fieldset>
<input class="btn btn-primary" type='submit' value='{% trans "Save Ticket Dependency" %}' />
{% csrf_token %}</form>
{% endblock %}

View File

@ -88,6 +88,16 @@ urlpatterns = [
staff.ticket_dependency_del,
name="ticket_dependency_del",
),
path(
"tickets/<int:ticket_id>/resolves/add/",
staff.ticket_resolves_add,
name="ticket_resolves_add",
),
path(
"tickets/<int:ticket_id>/resolves/delete/<int:dependency_id>/",
staff.ticket_resolves_del,
name="ticket_resolves_del",
),
path(
"tickets/<int:ticket_id>/attachment_delete/<int:attachment_id>/",
staff.attachment_del,

View File

@ -20,7 +20,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.handlers.wsgi import WSGIRequest
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Q
from django.db.models import Q, Case, When
from django.forms import HiddenInput, inlineformset_factory, TextInput
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
@ -54,6 +54,7 @@ from helpdesk.forms import (
TicketCCUserForm,
TicketDependencyForm,
TicketForm,
TicketResolvesForm,
UserSettingsForm
)
from helpdesk.lib import queue_template_context, safe_template_context
@ -422,8 +423,16 @@ def view_ticket(request, ticket_id):
return redirect('helpdesk:edit_ticket_checklist', ticket.id, checklist.id)
# List open tickets on top
dependencies = ticket.ticketdependency.annotate(
rank=Case(
When(depends_on__status__in=Ticket.OPEN_STATUSES, then=1),
default=2
)).order_by('rank')
return render(request, 'helpdesk/ticket.html', {
'ticket': ticket,
'dependencies': dependencies,
'submitter_userprofile_url': submitter_userprofile_url,
'form': form,
'active_users': users,
@ -1664,15 +1673,15 @@ def ticket_dependency_add(request, ticket_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
ticket_perm_check(request, ticket)
if request.method == 'POST':
form = TicketDependencyForm(request.POST)
form = TicketDependencyForm(ticket, request.POST)
if form.is_valid():
ticketdependency = form.save(commit=False)
ticketdependency.ticket = ticket
if ticketdependency.ticket != ticketdependency.depends_on:
ticketdependency.save()
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
return redirect(ticket.get_absolute_url())
else:
form = TicketDependencyForm()
form = TicketDependencyForm(ticket)
return render(request, 'helpdesk/ticket_dependency_add.html', {
'ticket': ticket,
'form': form,
@ -1695,6 +1704,42 @@ def ticket_dependency_del(request, ticket_id, dependency_id):
ticket_dependency_del = staff_member_required(ticket_dependency_del)
@helpdesk_staff_member_required
def ticket_resolves_add(request, ticket_id):
depends_on = get_object_or_404(Ticket, id=ticket_id)
ticket_perm_check(request, depends_on)
if request.method == 'POST':
form = TicketResolvesForm(depends_on, request.POST)
if form.is_valid():
ticketdependency = form.save(commit=False)
ticketdependency.depends_on = depends_on
if ticketdependency.ticket != ticketdependency.depends_on:
ticketdependency.save()
return redirect(depends_on.get_absolute_url())
else:
form = TicketResolvesForm(depends_on)
return render(request, 'helpdesk/ticket_resolves_add.html', {
'depends_on': depends_on,
'form': form,
})
ticket_resolves_add = staff_member_required(ticket_resolves_add)
@helpdesk_staff_member_required
def ticket_resolves_del(request, ticket_id, dependency_id):
dependency = get_object_or_404(
TicketDependency, ticket__id=ticket_id, id=dependency_id)
depends_on_id = dependency.depends_on.id
if request.method == 'POST':
dependency.delete()
return HttpResponseRedirect(reverse('helpdesk:view', args=[depends_on_id]))
return render(request, 'helpdesk/ticket_dependency_del.html', {'dependency': dependency})
ticket_resolves_del = staff_member_required(ticket_resolves_del)
@helpdesk_staff_member_required
def attachment_del(request, ticket_id, attachment_id):
ticket = get_object_or_404(Ticket, id=ticket_id)