Associate tickets with KB items

This commit is contained in:
Timothy Hobbs 2020-01-08 18:39:41 +01:00
parent 7fe6444f8f
commit 6579ac0e6f
10 changed files with 118 additions and 61 deletions

View File

@ -177,6 +177,17 @@ class AbstractTicketForm(CustomFieldMixin, forms.Form):
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
)
def __init__(self, kbcategory=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if kbcategory:
self.fields['kbitem'] = forms.ChoiceField(
widget=forms.Select(attrs={'class': 'form-control'}),
required=False,
label=_('Knowedge Base Item'),
choices=[(kbi.pk, kbi.title) for kbi in KBItem.objects.filter(category=kbcategory.pk)],
)
def _add_form_custom_fields(self, staff_only_filter=None):
if staff_only_filter is None:
queryset = CustomField.objects.all()
@ -184,11 +195,8 @@ class AbstractTicketForm(CustomFieldMixin, forms.Form):
queryset = CustomField.objects.filter(staff_only=staff_only_filter)
for field in queryset:
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
instanceargs = { 'label': field.label, 'help_text':
field.help_text, 'required': field.required, }
self.customfield_to_field(field, instanceargs)
@ -341,22 +349,13 @@ class PublicTicketForm(AbstractTicketForm):
help_text=_('We will e-mail you when your ticket is updated.'),
)
def __init__(self, hidden_fields=(), readonly_fields=(), kbcategory=None, *args, **kwargs):
def __init__(self, hidden_fields=(), readonly_fields=(), *args, **kwargs):
"""
Add any (non-staff) custom fields that are defined to the form
"""
super(PublicTicketForm, self).__init__(*args, **kwargs)
self._add_form_custom_fields(False)
if kbcategory:
self.fields['kbitem'] = forms.ChoiceField(
widget=forms.Select(attrs={'class': 'form-control'}),
required=False,
label=_('Knowedge Base Item'),
choices=[(kbi.pk, kbi.title) for kbi in KBItem.objects.filter(category=kbcategory.pk)],
)
field_hide_table = {
'queue': 'HELPDESK_PUBLIC_TICKET_QUEUE',
'priority': 'HELPDESK_PUBLIC_TICKET_PRIORITY',

View File

@ -1320,6 +1320,11 @@ class KBItem(models.Model):
from django.urls import reverse
return str(reverse('helpdesk:kb_category', args=(self.category.slug,)))+"?kbitem="+str(self.pk)
def query_url(self):
from django.urls import reverse
return str(reverse('helpdesk:list')) +"?kbitem="+str(self.pk)
def get_markdown(self):
return get_markdown(self.answer)

View File

@ -0,0 +1,15 @@
{% load i18n humanize %}
{% load static from staticfiles %}
{% load in_list %}
<div class="form-row">
<div class="col col-sm-3">
<label for='id_statuses'>{% trans "Knowledge base item(s)" %}:</label>
</div>
<div class="col col-sm-3">
<select id='id_kbitems' name='kbitem' multiple='selected' size='5'>{% for s in kbitem_choices %}<option value='{{ s.0 }}'{% if s.0|in_list:query_params.filtering.kbitem__in %} selected='selected'{% endif %}>{{ s.1 }}</option>{% endfor %}</select>
</div>
<div class="col col-sm-6">
<button class="filterBuilderRemove btn btn-danger btn-sm float-right"><i class="fas fa-trash-alt"></i></button>
</div>
<div class='form-row filterHelp'>{% trans "Ctrl-click to select multiple options" %}</div>
</div>

View File

@ -43,6 +43,7 @@
</div>
{% blocktrans with recommendations=item.recommendations votes=item.votes %}{{ recommendations }} people found this answer useful of {{votes}}. {% endblocktrans %}
{% endif %}
<a href='{% url 'helpdesk:submit' %}?{% if category.queue %}queue={{category.queue.pk}};_readonly_fields_=queue;{%endif%}kbitem={{item.id}};{{query_param_string}}'><button type="button" class="btn btn-success btn-circle btn-xl float-right"><i class="fa fa-envelope fa-lg"></i> {% trans 'Get help with this topic' %}</button></a>
</div>
</div>
@ -51,7 +52,7 @@
{% endfor %}
</div>
{% if category.queue %}
<a href='{% url 'helpdesk:submit' %}?queue={{category.queue.pk}};_readonly_fields_=queue'><button type="button" class="btn btn-success btn-circle btn-xl float-right"><i class="fa fa-envelope fa-lg"></i> {% trans 'Get help with this topic' %}</button></a>
<a href='{% url 'helpdesk:submit' %}?queue={{category.queue.pk}};_readonly_fields_=queue;{{query_param_string}}'><button type="button" class="btn btn-success btn-circle btn-xl float-right"><i class="fa fa-envelope fa-lg"></i> {% trans 'Get help with this topic' %}</button></a>
{% endif %}
{% endblock %}

View File

@ -68,6 +68,12 @@
<th class="table-active">{% trans "Total time spent" %}</th>
<td>{{ ticket.time_spent_formated }}</td>
</tr>
{% if ticket.kbitem %}
<tr>
<th class="table-active">{% trans "Knowlegebase item" %}</th>
<td> <a href ="{{ticket.kbitem.query_url}}"> {{ticket.kbitem.title}} </a> </td>
</tr>
{% endif %}
<tr>
<th class="table-active">{% trans "Attachments" %}</th>
<td colspan="3">

View File

@ -171,6 +171,7 @@
<option id="filterBuilderSelect-Status" value="Status">{% trans "Status" %}</option>
<option id="filterBuilderSelect-Keywords" value="Keywords">{% trans "Keywords" %}</option>
<option id="filterBuilderSelect-Dates" value="Dates">{% trans "Date Range" %}</option>
<option id="filterBuilderSelect-KBItems" value="KBItems">{% trans "Knowledge base items" %}</option>
</select>
{% csrf_token %}
</form>
@ -196,6 +197,9 @@
<li class="list-group-item filterBox{% if query_params.search_string %} filterBoxShow{% endif %}" id="filterBoxKeywords">
{% include './filters/keywords.html' %}
</li>
<li class="list-group-item filterBox{% if query_params.filtering.kbitem__in %} filterBoxShow{% endif %}" id="filterBoxKBItems">
{% include './filters/kbitems.html' %}
</li>
</ul>
<input class="btn btn-primary btn-sm" type='submit' value='{% trans "Apply Filters" %}' />
@ -396,6 +400,9 @@
{% if query_params.search_string %}
$("#filterBuilderSelect-Keywords")[0].disabled = "disabled";
{% endif %}
{% if query_params.filtering.kbitem__in %}
$("#filterBuilderSelect-KBItems")[0].disabled = "disabled";
{% endif %}
});
{% for f in query_params.filtering %}

View File

@ -0,0 +1,37 @@
from django.views.generic.edit import FormView
from helpdesk.models import CustomField, KBItem, Queue
class AbstractCreateTicketMixin():
def get_initial(self):
initial_data = {}
request = self.request
try:
initial_data['queue'] = Queue.objects.get(slug=request.GET.get('queue', None)).id
except Queue.DoesNotExist:
pass
if request.user.is_authenticated and request.user.usersettings_helpdesk.use_email_as_submitter and request.user.email:
initial_data['submitter_email'] = request.user.email
query_param_fields = ['submitter_email', 'title', 'body', 'queue', 'kbitem']
custom_fields = ["custom_%s" % f.name for f in CustomField.objects.filter(staff_only=False)]
query_param_fields += custom_fields
for qpf in query_param_fields:
initial_data[qpf] = request.GET.get(qpf, initial_data.get(qpf, ""))
return initial_data
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
kbitem = self.request.GET.get(
'kbitem',
self.request.POST.get('kbitem', None),
)
if kbitem:
try:
kwargs['kbcategory'] = KBItem.objects.get(pk=int(kbitem)).category
except (ValueError, KBItem.DoesNotExist):
pass
return kwargs

View File

@ -30,12 +30,18 @@ def category(request, slug):
selected_item = request.GET.get('kbitem', None)
try:
selected_item = int(selected_item)
except ValueError:
except TypeError:
pass
qparams = request.GET.copy()
try:
del qparams['kbitem']
except KeyError:
pass
return render(request, 'helpdesk/kb_category.html', {
'category': category,
'items': items,
'selected_item': selected_item,
'query_param_string': qparams.urlencode(),
'helpdesk_settings': helpdesk_settings,
})

View File

@ -19,6 +19,7 @@ from django.views.generic.edit import FormView
from helpdesk import settings as helpdesk_settings
from helpdesk.decorators import protect_view, is_helpdesk_staff
import helpdesk.views.staff as staff
import helpdesk.views.abstract_views as abstract_views
from helpdesk.forms import PublicTicketForm
from helpdesk.lib import text_is_spam
from helpdesk.models import CustomField, Ticket, Queue, UserSettings, KBCategory, KBItem
@ -31,7 +32,7 @@ def create_ticket(request, *args, **kwargs):
return CreateTicketView.as_view()(request, *args, **kwargs)
class BaseCreateTicketView(FormView):
class BaseCreateTicketView(abstract_views.AbstractCreateTicketMixin, FormView):
form_class = PublicTicketForm
def dispatch(self, *args, **kwargs):
@ -51,54 +52,27 @@ class BaseCreateTicketView(FormView):
return HttpResponseRedirect(reverse('helpdesk:dashboard'))
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['kb_categories'] = KBCategory.objects.all()
return context
def get_initial(self):
request = self.request
initial_data = {}
try:
queue = Queue.objects.get(slug=request.GET.get('queue', None))
except Queue.DoesNotExist:
queue = None
initial_data = super().get_initial()
# add pre-defined data for public ticket
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_QUEUE'):
# get the requested queue; return an error if queue not found
try:
queue = Queue.objects.get(slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE)
initial_data['queue'] = Queue.objects.get(slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE).id
except Queue.DoesNotExist:
return HttpResponse(status=500)
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_PRIORITY'):
initial_data['priority'] = settings.HELPDESK_PUBLIC_TICKET_PRIORITY
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_DUE_DATE'):
initial_data['due_date'] = settings.HELPDESK_PUBLIC_TICKET_DUE_DATE
if queue:
initial_data['queue'] = queue.id
if request.user.is_authenticated and request.user.email:
initial_data['submitter_email'] = request.user.email
query_param_fields = ['submitter_email', 'title', 'body', 'queue', 'kbitem']
custom_fields = ["custom_%s" % f.name for f in CustomField.objects.filter(staff_only=False)]
query_param_fields += custom_fields
for qpf in query_param_fields:
initial_data[qpf] = request.GET.get(qpf, initial_data.get(qpf, ""))
return initial_data
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
kwargs['hidden_fields'] = self.request.GET.get('_hide_fields_', '').split(',')
kwargs['readonly_fields'] = self.request.GET.get('_readonly_fields_', '').split(',')
kbitem = self.request.GET.get('kbitem', None)
if kbitem:
try:
kwargs['kbcategory'] = KBItem.objects.get(pk=int(kbitem))
except (ValueError, KBItem.DoesNotExist):
pass
return kwargs
def form_valid(self, form):
@ -134,6 +108,11 @@ class CreateTicketView(BaseCreateTicketView):
class Homepage(CreateTicketView):
template_name = 'helpdesk/public_homepage.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['kb_categories'] = KBCategory.objects.all()
return context
def search_for_ticket(request, error_message=None):
if hasattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC') and settings.HELPDESK_VIEW_A_TICKET_PUBLIC:

View File

@ -52,9 +52,10 @@ from helpdesk.lib import (
)
from helpdesk.models import (
Ticket, Queue, FollowUp, TicketChange, PreSetReply, FollowUpAttachment, SavedSearch,
IgnoreEmail, TicketCC, TicketDependency, UserSettings,
IgnoreEmail, TicketCC, TicketDependency, UserSettings, KBItem,
)
from helpdesk import settings as helpdesk_settings
import helpdesk.views.abstract_views as abstract_views
from helpdesk.views.permissions import MustBeStaffMixin
from ..lib import format_time_spent
@ -802,7 +803,9 @@ def ticket_list(request):
'search_string': '',
}
default_query_params = {
'filtering': {'status__in': [1, 2, 3]},
'filtering': {
'status__in': [1, 2, 3],
},
'sorting': 'created',
'search_string': '',
'sortreverse': False,
@ -849,7 +852,7 @@ def ticket_list(request):
if saved_query:
pass
elif not {'queue', 'assigned_to', 'status', 'q', 'sort', 'sortreverse'}.intersection(request.GET):
elif not {'queue', 'assigned_to', 'status', 'q', 'sort', 'sortreverse', 'kbitem'}.intersection(request.GET):
# Fall-back if no querying is being done
all_queues = Queue.objects.all()
query_params = deepcopy(default_query_params)
@ -858,6 +861,7 @@ def ticket_list(request):
('queue', 'queue__id__in'),
('assigned_to', 'assigned_to__id__in'),
('status', 'status__in'),
('kbitem', 'kbitem__in'),
]
for param, filter_command in filter_in_params:
@ -884,7 +888,7 @@ def ticket_list(request):
# SORTING
sort = request.GET.get('sort', None)
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority'):
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority', 'kbitem'):
sort = 'created'
query_params['sorting'] = sort
@ -907,12 +911,15 @@ def ticket_list(request):
'<a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">'
'Django Documentation on string matching in SQLite</a>.')
kbitem_choices = [(item.pk, item.title) for item in KBItem.objects.all()]
return render(request, 'helpdesk/ticket_list.html', dict(
context,
default_tickets_per_page=request.user.usersettings_helpdesk.tickets_per_page,
user_choices=User.objects.filter(is_active=True, is_staff=True),
queue_choices=huser.get_queues(),
status_choices=Ticket.STATUS_CHOICES,
kbitem_choices=kbitem_choices,
urlsafe_query=urlsafe_query,
user_saved_queries=user_saved_queries,
query_params=query_params,
@ -992,17 +999,12 @@ def edit_ticket(request, ticket_id):
edit_ticket = staff_member_required(edit_ticket)
class CreateTicketView(MustBeStaffMixin, FormView):
class CreateTicketView(MustBeStaffMixin, abstract_views.AbstractCreateTicketMixin, FormView):
template_name = 'helpdesk/create_ticket.html'
form_class = TicketForm
def get_initial(self):
initial_data = {}
request = self.request
if request.user.usersettings_helpdesk.use_email_as_submitter and request.user.email:
initial_data['submitter_email'] = request.user.email
if 'queue' in request.GET:
initial_data['queue'] = request.GET['queue']
initial_data = super().get_initial()
return initial_data
def get_form_kwargs(self):