mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2024-12-13 18:31:10 +01:00
Added optional serverside processing on datatables that lists all tickets - True by default
This commit is contained in:
parent
8c2009a871
commit
fc028334d9
@ -21,6 +21,8 @@ from helpdesk.models import Attachment, EmailTemplate
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from model_utils import Choices
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
from base64 import encodebytes as b64encode
|
from base64 import encodebytes as b64encode
|
||||||
from base64 import decodebytes as b64decode
|
from base64 import decodebytes as b64decode
|
||||||
@ -329,3 +331,58 @@ def process_attachments(followup, attached_files):
|
|||||||
attachments.append([filename, att.file])
|
attachments.append([filename, att.file])
|
||||||
|
|
||||||
return attachments
|
return attachments
|
||||||
|
|
||||||
|
|
||||||
|
ORDER_COLUMN_CHOICES = Choices(
|
||||||
|
('0', 'id'),
|
||||||
|
('2', 'priority'),
|
||||||
|
('3', 'title'),
|
||||||
|
('4', 'queue'),
|
||||||
|
('5', 'status'),
|
||||||
|
('6', 'created'),
|
||||||
|
('7', 'due_date'),
|
||||||
|
('8', 'assigned_to')
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_tickets_by_args(objects, order_by, **kwargs):
|
||||||
|
"""
|
||||||
|
This function takes in a list of ticket objects from the views and throws it
|
||||||
|
to the datatables on ticket_list.html. If a search string was entered, this
|
||||||
|
function filters existing dataset on search string and returns a filtered
|
||||||
|
filtered list. The `draw`, `length` etc parameters are for datatables to
|
||||||
|
display meta data on the table contents. The returning queryset is passed
|
||||||
|
to a Serializer called TicketSerializer in serializers.py.
|
||||||
|
"""
|
||||||
|
draw = int(kwargs.get('draw', None)[0])
|
||||||
|
length = int(kwargs.get('length', None)[0])
|
||||||
|
start = int(kwargs.get('start', None)[0])
|
||||||
|
search_value = kwargs.get('search[value]', None)[0]
|
||||||
|
order_column = kwargs.get('order[0][column]', None)[0]
|
||||||
|
order = kwargs.get('order[0][dir]', None)[0]
|
||||||
|
|
||||||
|
order_column = ORDER_COLUMN_CHOICES[order_column]
|
||||||
|
# django orm '-' -> desc
|
||||||
|
if order == 'desc':
|
||||||
|
order_column = '-' + order_column
|
||||||
|
|
||||||
|
queryset = objects.all().order_by(order_by)
|
||||||
|
total = queryset.count()
|
||||||
|
|
||||||
|
if search_value:
|
||||||
|
queryset = queryset.filter(Q(id__icontains=search_value) |
|
||||||
|
Q(priority__icontains=search_value) |
|
||||||
|
Q(title__icontains=search_value) |
|
||||||
|
Q(queue__title__icontains=search_value) |
|
||||||
|
Q(status__icontains=search_value) |
|
||||||
|
Q(created__icontains=search_value) |
|
||||||
|
Q(due_date__icontains=search_value) |
|
||||||
|
Q(assigned_to__email__icontains=search_value))
|
||||||
|
|
||||||
|
count = queryset.count()
|
||||||
|
queryset = queryset.order_by(order_column)[start:start + length]
|
||||||
|
return {
|
||||||
|
'items': queryset,
|
||||||
|
'count': count,
|
||||||
|
'total': total,
|
||||||
|
'draw': draw
|
||||||
|
}
|
||||||
|
48
helpdesk/serializers.py
Normal file
48
helpdesk/serializers.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import Ticket
|
||||||
|
|
||||||
|
from django.contrib.humanize.templatetags import humanize
|
||||||
|
|
||||||
|
"""
|
||||||
|
A serializer for the Ticket model, returns data in the format as required by
|
||||||
|
datatables for ticket_list.html. Called from staff.datatables_ticket_list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TicketSerializer(serializers.ModelSerializer):
|
||||||
|
ticket = serializers.SerializerMethodField()
|
||||||
|
assigned_to = serializers.SerializerMethodField()
|
||||||
|
created = serializers.SerializerMethodField()
|
||||||
|
due_date = serializers.SerializerMethodField()
|
||||||
|
status = serializers.SerializerMethodField()
|
||||||
|
row_class = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
# fields = '__all__'
|
||||||
|
fields = ('ticket', 'id', 'priority', 'title', 'queue', 'status', 'created', 'due_date', 'assigned_to', 'row_class')
|
||||||
|
|
||||||
|
def get_ticket(self, obj):
|
||||||
|
return (str(obj.id)+" "+obj.ticket)
|
||||||
|
|
||||||
|
def get_status(self, obj):
|
||||||
|
return (obj.get_status)
|
||||||
|
|
||||||
|
def get_created(self, obj):
|
||||||
|
return (humanize.naturaltime(obj.created))
|
||||||
|
|
||||||
|
def get_due_date(self, obj):
|
||||||
|
return (humanize.naturaltime(obj.due_date))
|
||||||
|
|
||||||
|
def get_assigned_to(self, obj):
|
||||||
|
if obj.assigned_to:
|
||||||
|
if obj.assigned_to.first_name:
|
||||||
|
return (obj.assigned_to.first_name)
|
||||||
|
else:
|
||||||
|
return (obj.assigned_to.email)
|
||||||
|
else:
|
||||||
|
return ("None")
|
||||||
|
|
||||||
|
def get_row_class(self, obj):
|
||||||
|
return (obj.get_priority_css_class)
|
@ -150,3 +150,7 @@ QUEUE_EMAIL_BOX_UPDATE_ONLY = getattr(settings, 'QUEUE_EMAIL_BOX_UPDATE_ONLY', F
|
|||||||
# only allow users to access queues that they are members of?
|
# only allow users to access queues that they are members of?
|
||||||
HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = getattr(
|
HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = getattr(
|
||||||
settings, 'HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION', False)
|
settings, 'HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION', False)
|
||||||
|
|
||||||
|
|
||||||
|
# Asynchronous Datatables - Optional
|
||||||
|
USE_SERVERSIDE_PROCESSING = True
|
||||||
|
@ -6,32 +6,100 @@
|
|||||||
|
|
||||||
<script src='{% static "helpdesk/filter.js" %}'></script>
|
<script src='{% static "helpdesk/filter.js" %}'></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
{% if server_side %}
|
||||||
|
$(document).ready(function()
|
||||||
|
{
|
||||||
|
let url = ""
|
||||||
|
//DataTables Initialization
|
||||||
|
let tasks_table = $('#ticketTable').DataTable({
|
||||||
|
"language": {
|
||||||
|
"emptyTable": "{% trans 'No Tickets Match Your Selection' %}"
|
||||||
|
},
|
||||||
|
"processing": true,
|
||||||
|
"serverSide": true,
|
||||||
|
"ajax": {
|
||||||
|
"url": "/datatables_ticket_list/",
|
||||||
|
"type": "GET",
|
||||||
|
},
|
||||||
|
createdRow: function( row, data, dataIndex )
|
||||||
|
{
|
||||||
|
$( row ).addClass(data.row_class);
|
||||||
|
},
|
||||||
|
|
||||||
$('#ticketTable').DataTable({
|
"columns": [
|
||||||
"oLanguage": {
|
{"data": "ticket",
|
||||||
"sEmptyTable": "{% trans 'No Tickets Match Your Selection' %}"
|
"render": function (data, type, row, meta)
|
||||||
},
|
{
|
||||||
"order": [],
|
var id = data.split(" ")[0];
|
||||||
responsive: true
|
var name = data.split(" ")[1];
|
||||||
});
|
let url = "{% url 'helpdesk:view' 1234 %}".replace(/1234/, id.toString());
|
||||||
|
if (type === 'display')
|
||||||
$("#select_all").click(function() {
|
{
|
||||||
$(".ticket_multi_select").attr('checked', true);
|
data = '<b><a href="' + url + '" >' + name + '</a></b>';
|
||||||
return false;
|
}
|
||||||
});
|
return data
|
||||||
$("#select_none").click(function() {
|
}
|
||||||
$(".ticket_multi_select").attr('checked', false);
|
},
|
||||||
return false;
|
{"data": "id",
|
||||||
});
|
"orderable": false,
|
||||||
$("#select_inverse").click(function() {
|
"render": function(data, type, row, meta)
|
||||||
$(".ticket_multi_select").each(function() {
|
{
|
||||||
$(this).attr('checked', !$(this).attr('checked'));
|
var pk = data;
|
||||||
|
if(type === 'display'){
|
||||||
|
data = "<input type='checkbox' name='ticket_id' value='"+pk+"'"+ "class='ticket_multi_select' />"
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"data": "priority"},
|
||||||
|
{"data": "title",
|
||||||
|
"render": function (data, type, row, meta)
|
||||||
|
{
|
||||||
|
if (type === 'display')
|
||||||
|
{
|
||||||
|
data = '<b><a href="' + url + '" >' + data + '</a></b>';
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"data": "queue"},
|
||||||
|
{"data": "status"},
|
||||||
|
{"data": "created"},
|
||||||
|
{"data": "due_date"},
|
||||||
|
{"data": "assigned_to"},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
})
|
||||||
|
{% else %}
|
||||||
|
$('#ticketTable').DataTable({
|
||||||
|
"oLanguage": {
|
||||||
|
"sEmptyTable": "{% trans 'No Tickets Match Your Selection' %}"
|
||||||
|
},
|
||||||
|
"order": [],
|
||||||
|
responsive: true
|
||||||
});
|
});
|
||||||
return false;
|
{% endif %}
|
||||||
});
|
$(document).ready(function()
|
||||||
});
|
{
|
||||||
|
$("#select_all").click(function() {
|
||||||
|
$(".ticket_multi_select").attr('checked', true);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$("#select_none").click(function() {
|
||||||
|
$(".ticket_multi_select").attr('checked', false);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$("#select_inverse").click(function() {
|
||||||
|
$(".ticket_multi_select").each(function() {
|
||||||
|
$(this).attr('checked', !$(this).attr('checked'));
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block h1_title %}Tickets
|
{% block h1_title %}Tickets
|
||||||
{% if from_saved_query %} [{{ saved_query.title }}]{% endif %}{% endblock %}
|
{% if from_saved_query %} [{{ saved_query.title }}]{% endif %}{% endblock %}
|
||||||
@ -227,21 +295,9 @@ $(document).ready(function() {
|
|||||||
<th>{% trans "Owner" %}</th>
|
<th>{% trans "Owner" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
{% if not server_side %}
|
||||||
{% for ticket in tickets %}
|
{% include 'helpdesk/ticket_list_table.html' %}
|
||||||
<tr class="{{ ticket.get_priority_css_class }}">
|
{% endif %}
|
||||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
|
|
||||||
<td><input type='checkbox' name='ticket_id' value='{{ ticket.id }}' class='ticket_multi_select' /></td>
|
|
||||||
<td>{{ ticket.priority }}</td>
|
|
||||||
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
|
|
||||||
<td>{{ ticket.queue }}</td>
|
|
||||||
<td>{{ ticket.get_status }}</td>
|
|
||||||
<td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
|
||||||
<td data-order='{{ ticket.due_date|date:"U" }}'><span title='{{ ticket.due_date|date:"r" }}'>{{ ticket.due_date|naturaltime }}</span></td>
|
|
||||||
<td>{{ ticket.get_assigned_to }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
17
helpdesk/templates/helpdesk/ticket_list_table.html
Normal file
17
helpdesk/templates/helpdesk/ticket_list_table.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% load i18n humanize %}
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for ticket in tickets %}
|
||||||
|
<tr class="{{ ticket.get_priority_css_class }}">
|
||||||
|
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
|
||||||
|
<td><input type='checkbox' name='ticket_id' value='{{ ticket.id }}' class='ticket_multi_select' /></td>
|
||||||
|
<td>{{ ticket.priority }}</td>
|
||||||
|
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
|
||||||
|
<td>{{ ticket.queue }}</td>
|
||||||
|
<td>{{ ticket.get_status }}</td>
|
||||||
|
<td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
||||||
|
<td data-order='{{ ticket.due_date|date:"U" }}'><span title='{{ ticket.due_date|date:"r" }}'>{{ ticket.due_date|naturaltime }}</span></td>
|
||||||
|
<td>{{ ticket.get_assigned_to }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
@ -141,6 +141,10 @@ urlpatterns = [
|
|||||||
url(r'^ignore/delete/(?P<id>[0-9]+)/$',
|
url(r'^ignore/delete/(?P<id>[0-9]+)/$',
|
||||||
staff.email_ignore_del,
|
staff.email_ignore_del,
|
||||||
name='email_ignore_del'),
|
name='email_ignore_del'),
|
||||||
|
|
||||||
|
url(r'^datatables_ticket_list/$',
|
||||||
|
staff.datatables_ticket_list,
|
||||||
|
name="datatables_ticket_list"),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
@ -30,6 +30,14 @@ from django.views.generic.edit import FormView
|
|||||||
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
# For datatables serverside
|
||||||
|
from django.core.cache import cache
|
||||||
|
from rest_framework import viewsets, status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from helpdesk.lib import query_tickets_by_args
|
||||||
|
from helpdesk.serializers import TicketSerializer
|
||||||
|
|
||||||
from helpdesk.decorators import (
|
from helpdesk.decorators import (
|
||||||
helpdesk_staff_member_required, helpdesk_superuser_required,
|
helpdesk_staff_member_required, helpdesk_superuser_required,
|
||||||
is_helpdesk_staff
|
is_helpdesk_staff
|
||||||
@ -980,6 +988,14 @@ def ticket_list(request):
|
|||||||
|
|
||||||
user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
|
user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
|
||||||
|
|
||||||
|
# Serverside processing on datatables is optional. Set
|
||||||
|
# USE_SERVERSIDE_PROCESSING to False in settings.py to disable
|
||||||
|
if helpdesk_settings.USE_SERVERSIDE_PROCESSING:
|
||||||
|
cache.set('ticket_qs', ticket_qs)
|
||||||
|
context['server_side'] = True
|
||||||
|
else:
|
||||||
|
context['server_side'] = False
|
||||||
|
|
||||||
return render(request, 'helpdesk/ticket_list.html', dict(
|
return render(request, 'helpdesk/ticket_list.html', dict(
|
||||||
context,
|
context,
|
||||||
tickets=ticket_qs,
|
tickets=ticket_qs,
|
||||||
@ -999,6 +1015,28 @@ def ticket_list(request):
|
|||||||
ticket_list = staff_member_required(ticket_list)
|
ticket_list = staff_member_required(ticket_list)
|
||||||
|
|
||||||
|
|
||||||
|
@helpdesk_staff_member_required
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
def datatables_ticket_list(request):
|
||||||
|
"""
|
||||||
|
Datatable on ticket_list.html uses this view from to get objects to display
|
||||||
|
on the table. query_tickets_by_args is at lib.py, TicketSerializer is in
|
||||||
|
serializers.py. The serializers and this view use django-rest_framework methods
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
model_object = query_tickets_by_args(cache.get('ticket_qs'), '-date_created', **request.query_params)
|
||||||
|
serializer = TicketSerializer(model_object['items'], many=True)
|
||||||
|
result = dict()
|
||||||
|
result['data'] = serializer.data
|
||||||
|
result['draw'] = model_object['draw']
|
||||||
|
result['recordsTotal'] = model_object['total']
|
||||||
|
result['recordsFiltered'] = model_object['count']
|
||||||
|
return (Response(result, status=status.HTTP_200_OK, template_name=None, content_type=None))
|
||||||
|
|
||||||
|
except TypeError as e:
|
||||||
|
return (Response(e, status=status.HTTP_404_NOT_FOUND, template_name=None, content_type=None))
|
||||||
|
|
||||||
|
|
||||||
@helpdesk_staff_member_required
|
@helpdesk_staff_member_required
|
||||||
def edit_ticket(request, ticket_id):
|
def edit_ticket(request, ticket_id):
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
|
@ -7,3 +7,5 @@ lxml
|
|||||||
simplejson
|
simplejson
|
||||||
pytz
|
pytz
|
||||||
six
|
six
|
||||||
|
djangorestframework
|
||||||
|
django-model-utils
|
Loading…
Reference in New Issue
Block a user