Create a page to edit checklist name and tasks + another page for deletion

This commit is contained in:
Benbb96 2023-04-29 00:24:06 +02:00
parent b8d06a0fb1
commit af1ba5f205
No known key found for this signature in database
9 changed files with 262 additions and 21 deletions

View File

@ -607,23 +607,34 @@ class MultipleTicketSelectForm(forms.Form):
class ChecklistForm(forms.ModelForm): class ChecklistForm(forms.ModelForm):
checklist_template = forms.ModelChoiceField(
label=_("Template"),
queryset=ChecklistTemplate.objects.all(),
widget=forms.Select(attrs={'class': 'form-wontrol'}),
required=False,
)
name = forms.CharField( name = forms.CharField(
widget=forms.TextInput(attrs={'class': 'form-wontrol'}), widget=forms.TextInput(attrs={'class': 'form-control'}),
required=False, required=True,
) )
class Meta: class Meta:
model = Checklist model = Checklist
fields = ('name',) fields = ('name',)
class CreateChecklistForm(ChecklistForm):
checklist_template = forms.ModelChoiceField(
label=_("Template"),
queryset=ChecklistTemplate.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].required = False
def clean(self): def clean(self):
if not self.cleaned_data.get('checklist_template') and not self.cleaned_data.get('name'): if not self.cleaned_data.get('checklist_template') and not self.cleaned_data.get('name'):
raise ValidationError(_('Please choose at least a name or a template for the new checklist')) raise ValidationError(_('Please choose at least a name or a template for the new checklist'))
if self.cleaned_data.get('checklist_template') and self.cleaned_data.get('name'): if self.cleaned_data.get('checklist_template') and self.cleaned_data.get('name'):
raise ValidationError(_('Please choose either a name or a template for the new checklist')) raise ValidationError(_('Please choose either a name or a template for the new checklist'))
class FormControlDeleteFormSet(forms.BaseInlineFormSet):
deletion_widget = forms.CheckboxInput(attrs={'class': 'form-control'})

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2 on 2023-04-22 20:50 # Generated by Django 4.2 on 2023-04-28 21:23
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -42,11 +42,13 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.CharField(max_length=250, verbose_name='Description')), ('description', models.CharField(max_length=250, verbose_name='Description')),
('completion_date', models.DateTimeField(blank=True, null=True, verbose_name='Completion Date')), ('completion_date', models.DateTimeField(blank=True, null=True, verbose_name='Completion Date')),
('position', models.PositiveSmallIntegerField(db_index=True, verbose_name='Position')),
('checklist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='helpdesk.checklist', verbose_name='Checklist')), ('checklist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='helpdesk.checklist', verbose_name='Checklist')),
], ],
options={ options={
'verbose_name': 'Checklist Task', 'verbose_name': 'Checklist Task',
'verbose_name_plural': 'Checklist Tasks', 'verbose_name_plural': 'Checklist Tasks',
'ordering': ('position',),
}, },
), ),
] ]

View File

@ -2030,8 +2030,8 @@ class ChecklistTemplate(models.Model):
def create_checklist_for_ticket(self, ticket): def create_checklist_for_ticket(self, ticket):
checklist = ticket.checklists.create(name=self.name) checklist = ticket.checklists.create(name=self.name)
for task in self.task_list: for position, task in enumerate(self.task_list):
checklist.tasks.create(description=task) checklist.tasks.create(description=task, position=position)
return checklist return checklist
@ -2079,12 +2079,17 @@ class ChecklistTask(models.Model):
null=True, null=True,
blank=True blank=True
) )
position = models.PositiveSmallIntegerField(
verbose_name=_('Position'),
db_index=True
)
objects = ChecklistTaskQuerySet.as_manager() objects = ChecklistTaskQuerySet.as_manager()
class Meta: class Meta:
verbose_name = _('Checklist Task') verbose_name = _('Checklist Task')
verbose_name_plural = _('Checklist Tasks') verbose_name_plural = _('Checklist Tasks')
ordering = ('position',)
def __str__(self): def __str__(self):
return self.description return self.description

View File

@ -0,0 +1,45 @@
{% extends "helpdesk/base.html" %}
{% load i18n %}
{% block helpdesk_title %}{% trans "Delete Checklist" %}{% 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' %}{{ ticket.id }}/">{{ ticket.queue.slug }}-{{ ticket.id }}</a>
</li>
<li class="breadcrumb-item active">{{ checklist.name }}</li>
{% endblock %}
{% block helpdesk_body %}
<div class="row">
<div class="col-sm-6 offset-sm-3 col-xs-12">
<div class="card">
<div class="card-header">
<h2>
{% trans "Delete Checklist" %}
</h2>
</div>
<div class="card-body">
<form method='post'>
{% csrf_token %}
<p>{% trans "Are you sure your want to delete checklist" %} {{ checklist.name }} ?</p>
<div class='buttons form-group text-center'>
<a class="btn btn-secondary" href='{{ ticket.get_absolute_url }}'>
<i class="fas fa-times"></i>
{% trans "Don't Delete" %}
</a>
<button type='submit' class="btn btn-danger">
<i class="fas fa-trash"></i>
{% trans "Yes, I Understand - Delete" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,106 @@
{% extends "helpdesk/base.html" %}
{% load i18n %}
{% block helpdesk_title %}{% trans "Edit Checklist" %}{% 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' %}{{ ticket.id }}/">{{ ticket.queue.slug }}-{{ ticket.id }}</a>
</li>
<li class="breadcrumb-item active">{{ checklist.name }}</li>
{% endblock %}
{% block helpdesk_body %}
<div class="card">
<div class="card-header">
<h2>
{% trans "Edit Checklist" %}
<a class="btn btn-danger float-right" href='{% url "helpdesk:delete_ticket_checklist" ticket.id checklist.id %}'>
<i class="fas fa-trash"></i>
{% trans "Delete checklist" %}
</a>
</h2>
</div>
<div class="card-body">
{% if form.non_field_errors %}
<p class="text-danger">
{{ form.non_field_errors }}
</p>
{% endif %}
<form method='post'>
{% csrf_token %}
<div class="row">
<div class="col-sm-6 col-xs-12 form-group">
{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.errors }}
</div>
</div>
<h3>Tasks</h3>
{{ formset.management_form }}
<table class="table">
<thead>
<tr>
<th>{% trans "Description" %}</th>
<th>{% trans "Position" %}</th>
<th class="text-center">{% trans "Delete?" %}</th>
</tr>
</thead>
<tbody id="tasks">
{% for form in formset %}
<tr>
{{ form.id }}
<td>
{{ form.description }}
{{ form.description.errors }}
</td>
<td>
{{ form.position }}
{{ form.position.errors }}
</td>
<td>
{{ form.DELETE }}
{{ form.DELETE.errors }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="button" class="btn btn-secondary" id="addRow">
<i class="fas fa-plus"></i>
{% trans "Add another task" %}
</button>
<div class='buttons form-group text-center'>
<a class="btn btn-link" href='{{ ticket.get_absolute_url }}'>
<i class="fas fa-times"></i>
{% trans "Cancel Changes" %}
</a>
<button type='submit' class="btn btn-primary">
<i class="fas fa-save"></i>
{% trans "Save Changes" %}
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block helpdesk_js %}
<script>
$(() => {
const idTasksTotalForms = $('#id_tasks-TOTAL_FORMS')
$('#addRow').on('click', function() {
const formCount = idTasksTotalForms.val()
const description = '{{ formset.empty_form.description|escapejs }}'.replace(/__prefix__/g, formCount);
const position = '{{ formset.empty_form.position|escapejs }}'.replace(/__prefix__/g, formCount);
const delete_checkbox = '{{ formset.empty_form.DELETE|escapejs }}'.replace(/__prefix__/g, formCount);
$('#tasks').append(`<tr><td>${description}</td><td>${position}</td><td>${delete_checkbox}</td></tr>`)
idTasksTotalForms.val(parseInt(formCount) + 1);
});
})
</script>
{% endblock %}

View File

@ -7,6 +7,11 @@
{% block helpdesk_title %}{{ ticket.queue.slug }}-{{ ticket.id }} : {% trans "View Ticket Details" %}{% endblock %} {% block helpdesk_title %}{{ ticket.queue.slug }}-{{ ticket.id }} : {% trans "View Ticket Details" %}{% endblock %}
{% block helpdesk_head %} {% block helpdesk_head %}
<style>
.checklist {
margin-bottom: 15px;
}
</style>
{% endblock %} {% endblock %}
{% block h1_title %}{{ ticket.ticket_for_url }}{% endblock %} {% block h1_title %}{{ ticket.ticket_for_url }}{% endblock %}

View File

@ -178,14 +178,17 @@
<div class="row"> <div class="row">
{% for checklist in ticket.checklists.all %} {% for checklist in ticket.checklists.all %}
<div class="col-sm-4 col-xs-12"> <div class="col-sm-4 col-xs-12">
<div class="card"> <div class="card checklist">
<div class="card-header" data-toggle="collapse" data-target="#checklist{{ checklist.id }}" onclick="$(this).find('i').toggleClass('fa-caret-down fa-caret-up')"> <div class="card-header">
<h4> <h5>
{{ checklist }} {{ checklist }}
<button class="btn btn-link float-right"> <a class="btn btn-link btn-sm" href="{% url 'helpdesk:edit_ticket_checklist' ticket.id checklist.id %}">
<i class="fas fa-edit"></i>
</a>
<button class="btn btn-secondary btn-sm float-right" data-toggle="collapse" data-target="#checklist{{ checklist.id }}" onclick="$(this).find('i').toggleClass('fa-caret-down fa-caret-up')">
<i class="fas fa-caret-down"></i> <i class="fas fa-caret-down"></i>
</button> </button>
</h4> </h5>
</div> </div>
<div class="card-body collapse" id="checklist{{ checklist.id }}"> <div class="card-body collapse" id="checklist{{ checklist.id }}">
<div class="list-group"> <div class="list-group">
@ -214,9 +217,9 @@
</div> </div>
{% endfor %} {% endfor %}
<div class="col-sm-4 col-xs-12"> <div class="col-sm-4 col-xs-12">
<div class="card"> <div class="card checklist">
<div class="card-header"> <div class="card-header">
<h4>Add a checklist</h4> <h5>Add a checklist</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="post"> <form method="post">

View File

@ -93,6 +93,16 @@ urlpatterns = [
staff.attachment_del, staff.attachment_del,
name="attachment_del", name="attachment_del",
), ),
path(
"tickets/<int:ticket_id>/checklists/<int:checklist_id>/",
staff.edit_ticket_checklist,
name="edit_ticket_checklist"
),
path(
"tickets/<int:ticket_id>/checklists/<int:checklist_id>/delete/",
staff.delete_ticket_checklist,
name="delete_ticket_checklist"
),
re_path(r"^raw/(?P<type>\w+)/$", staff.raw_details, name="raw"), re_path(r"^raw/(?P<type>\w+)/$", staff.raw_details, name="raw"),
path("rss/", staff.rss_list, name="rss_index"), path("rss/", staff.rss_list, name="rss_index"),
path("reports/", staff.report_index, name="report_index"), path("reports/", staff.report_index, name="report_index"),

View File

@ -20,6 +20,7 @@ from django.core.exceptions import PermissionDenied, ValidationError
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
from django.forms import inlineformset_factory, TextInput, NumberInput
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
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@ -49,7 +50,9 @@ from helpdesk.forms import (
TicketDependencyForm, TicketDependencyForm,
TicketForm, TicketForm,
UserSettingsForm, UserSettingsForm,
ChecklistForm CreateChecklistForm,
ChecklistForm,
FormControlDeleteFormSet
) )
from helpdesk.lib import process_attachments, queue_template_context, safe_template_context from helpdesk.lib import process_attachments, queue_template_context, safe_template_context
from helpdesk.models import ( from helpdesk.models import (
@ -65,7 +68,9 @@ from helpdesk.models import (
TicketChange, TicketChange,
TicketCustomFieldValue, TicketCustomFieldValue,
TicketDependency, TicketDependency,
UserSettings UserSettings,
Checklist,
ChecklistTask
) )
from helpdesk.query import get_query_class, query_from_base64, query_to_base64 from helpdesk.query import get_query_class, query_from_base64, query_to_base64
from helpdesk.user import HelpdeskUser from helpdesk.user import HelpdeskUser
@ -407,7 +412,7 @@ def view_ticket(request, ticket_id):
else: else:
submitter_userprofile_url = None submitter_userprofile_url = None
checklist_form = ChecklistForm(request.POST or None) checklist_form = CreateChecklistForm(request.POST or None)
if checklist_form.is_valid(): if checklist_form.is_valid():
checklist_template = checklist_form.cleaned_data.get('checklist_template') checklist_template = checklist_form.cleaned_data.get('checklist_template')
if checklist_template: if checklist_template:
@ -432,6 +437,55 @@ def view_ticket(request, ticket_id):
}) })
@helpdesk_staff_member_required
def edit_ticket_checklist(request, ticket_id, checklist_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
ticket_perm_check(request, ticket)
checklist = get_object_or_404(ticket.checklists.all(), id=checklist_id)
form = ChecklistForm(request.POST or None, instance=checklist)
TaskFormSet = inlineformset_factory(
Checklist,
ChecklistTask,
formset=FormControlDeleteFormSet,
fields=['description', 'position'],
widgets={
'description': TextInput(attrs={'class': 'form-control'}),
'position': NumberInput(attrs={'class': 'form-control'}),
},
can_delete=True,
extra=1
)
formset = TaskFormSet(request.POST or None, instance=checklist)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
return redirect(ticket)
return render(request, 'helpdesk/checklist_form.html', {
'ticket': ticket,
'checklist': checklist,
'form': form,
'formset': formset,
})
@helpdesk_staff_member_required
def delete_ticket_checklist(request, ticket_id, checklist_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
ticket_perm_check(request, ticket)
checklist = get_object_or_404(ticket.checklists.all(), id=checklist_id)
if request.POST:
checklist.delete()
return redirect(ticket)
return render(request, 'helpdesk/checklist_confirm_delete.html', {
'ticket': ticket,
'checklist': checklist,
})
def return_ticketccstring_and_show_subscribe(user, ticket): def return_ticketccstring_and_show_subscribe(user, ticket):
"""used in view_ticket() and followup_edit()""" """used in view_ticket() and followup_edit()"""
# create the ticketcc_string and check whether current user is already # create the ticketcc_string and check whether current user is already