forked from extern/django-helpdesk
Associate tickets with KB items
This commit is contained in:
parent
7fe6444f8f
commit
6579ac0e6f
@ -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',
|
||||
|
@ -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)
|
||||
|
||||
|
15
helpdesk/templates/helpdesk/filters/kbitems.html
Normal file
15
helpdesk/templates/helpdesk/filters/kbitems.html
Normal 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>
|
@ -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 %}
|
||||
|
@ -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">
|
||||
|
@ -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" %}' />
|
||||
@ -318,8 +322,8 @@
|
||||
var name = data.split(" ")[1];
|
||||
if (type === 'display')
|
||||
{
|
||||
data = '<div class="tickettitle"><a href="' + get_url(row) + '" >' +
|
||||
row.id + '. ' +
|
||||
data = '<div class="tickettitle"><a href="' + get_url(row) + '" >' +
|
||||
row.id + '. ' +
|
||||
row.title + '</a></div>';
|
||||
}
|
||||
return data
|
||||
@ -348,7 +352,7 @@
|
||||
"render": function(data, type, row, meta) {
|
||||
if (data != "None") {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
@ -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 %}
|
||||
|
37
helpdesk/views/abstract_views.py
Normal file
37
helpdesk/views/abstract_views.py
Normal 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
|
@ -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,
|
||||
})
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user