Merge branch 'django-helpdesk:main' into attachment-enable-setting

This commit is contained in:
Georg Lehner 2024-06-14 18:33:19 +02:00 committed by GitHub
commit 66ba2d076a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 26 deletions

View File

@ -593,7 +593,26 @@ class TicketDependencyForm(forms.ModelForm):
class Meta: class Meta:
model = TicketDependency 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): class MultipleTicketSelectForm(forms.Form):

View File

@ -12,7 +12,7 @@
<thead class="thead-light"> <thead class="thead-light">
<tr class=''> <tr class=''>
<th colspan='4'> <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 %} {% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}
<span class='ticket_toolbar float-right'> <span class='ticket_toolbar float-right'>
<a href="{% url 'helpdesk:edit' ticket.id %}" class="btn btn-warning btn-sm ticket-edit"> <a href="{% url 'helpdesk:edit' ticket.id %}" class="btn btn-warning btn-sm ticket-edit">
@ -117,24 +117,9 @@
</td> </td>
</tr> </tr>
{% if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET != False and helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET != False %} <tr>
<tr> <th class="table-active">{% trans "Status" %}</th>
{% if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET %} <td>{{ ticket.get_status }}</td>
<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 %}
{% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %} {% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %}
<th class="table-active">{% trans "Total time spent" %}</th> <th class="table-active">{% trans "Total time spent" %}</th>
<td>{{ ticket.time_spent_formated }}</td> <td>{{ ticket.time_spent_formated }}</td>
@ -142,8 +127,64 @@
<th class="table-active"></th> <th class="table-active"></th>
<td></td> <td></td>
{% endif %} {% endif %}
</tr> </tr>
{% endif %} {% 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 %} {% if ticket.kbitem %}
<tr> <tr>
<th class="table-active">{% trans "Knowlegebase item" %}</th> <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, staff.ticket_dependency_del,
name="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( path(
"tickets/<int:ticket_id>/attachment_delete/<int:attachment_id>/", "tickets/<int:ticket_id>/attachment_delete/<int:attachment_id>/",
staff.attachment_del, 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.exceptions import PermissionDenied
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 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.forms import HiddenInput, inlineformset_factory, TextInput
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
@ -54,6 +54,7 @@ from helpdesk.forms import (
TicketCCUserForm, TicketCCUserForm,
TicketDependencyForm, TicketDependencyForm,
TicketForm, TicketForm,
TicketResolvesForm,
UserSettingsForm UserSettingsForm
) )
from helpdesk.lib import queue_template_context, safe_template_context 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) 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', { return render(request, 'helpdesk/ticket.html', {
'ticket': ticket, 'ticket': ticket,
'dependencies': dependencies,
'submitter_userprofile_url': submitter_userprofile_url, 'submitter_userprofile_url': submitter_userprofile_url,
'form': form, 'form': form,
'active_users': users, 'active_users': users,
@ -1664,15 +1673,15 @@ def ticket_dependency_add(request, ticket_id):
ticket = get_object_or_404(Ticket, id=ticket_id) ticket = get_object_or_404(Ticket, id=ticket_id)
ticket_perm_check(request, ticket) ticket_perm_check(request, ticket)
if request.method == 'POST': if request.method == 'POST':
form = TicketDependencyForm(request.POST) form = TicketDependencyForm(ticket, request.POST)
if form.is_valid(): if form.is_valid():
ticketdependency = form.save(commit=False) ticketdependency = form.save(commit=False)
ticketdependency.ticket = ticket ticketdependency.ticket = ticket
if ticketdependency.ticket != ticketdependency.depends_on: if ticketdependency.ticket != ticketdependency.depends_on:
ticketdependency.save() ticketdependency.save()
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id])) return redirect(ticket.get_absolute_url())
else: else:
form = TicketDependencyForm() form = TicketDependencyForm(ticket)
return render(request, 'helpdesk/ticket_dependency_add.html', { return render(request, 'helpdesk/ticket_dependency_add.html', {
'ticket': ticket, 'ticket': ticket,
'form': form, 'form': form,
@ -1695,6 +1704,42 @@ def ticket_dependency_del(request, ticket_id, dependency_id):
ticket_dependency_del = staff_member_required(ticket_dependency_del) 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 @helpdesk_staff_member_required
def attachment_del(request, ticket_id, attachment_id): def attachment_del(request, ticket_id, attachment_id):
ticket = get_object_or_404(Ticket, id=ticket_id) ticket = get_object_or_404(Ticket, id=ticket_id)