mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2024-12-26 16:48:50 +01:00
Refactor query code into customizable class
This commit is contained in:
parent
6eee6d196c
commit
b96d725239
@ -1,12 +1,16 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.urls import reverse
|
||||||
from model_utils import Choices
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from model_utils import Choices
|
||||||
|
|
||||||
|
from helpdesk.serializers import DatatablesTicketSerializer
|
||||||
|
|
||||||
|
|
||||||
def query_to_base64(query):
|
def query_to_base64(query):
|
||||||
"""
|
"""
|
||||||
@ -47,60 +51,26 @@ def query_to_dict(results, descriptions):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def apply_query(queryset, params):
|
def get_search_filter_args(search):
|
||||||
"""
|
if search.startswith('queue:'):
|
||||||
Apply a dict-based set of filters & parameters to a queryset.
|
return Q(queue__title__icontains=search[len('queue:'):])
|
||||||
|
if search.startswith('priority:'):
|
||||||
queryset is a Django queryset, eg MyModel.objects.all() or
|
return Q(priority__icontains=search[len('priority:'):])
|
||||||
MyModel.objects.filter(user=request.user)
|
return (
|
||||||
|
Q(id__icontains=search) |
|
||||||
params is a dictionary that contains the following:
|
Q(title__icontains=search) |
|
||||||
filtering: A dict of Django ORM filters, eg:
|
Q(description__icontains=search) |
|
||||||
{'user__id__in': [1, 3, 103], 'title__contains': 'foo'}
|
Q(priority__icontains=search) |
|
||||||
|
Q(resolution__icontains=search) |
|
||||||
search_string: A freetext search string
|
Q(submitter_email__icontains=search) |
|
||||||
|
Q(assigned_to__email__icontains=search) |
|
||||||
sorting: The name of the column to sort by
|
Q(ticketcustomfieldvalue__value__icontains=search) |
|
||||||
"""
|
Q(created__icontains=search) |
|
||||||
for key in params['filtering'].keys():
|
Q(due_date__icontains=search)
|
||||||
filter = {key: params['filtering'][key]}
|
)
|
||||||
queryset = queryset.filter(**filter)
|
|
||||||
|
|
||||||
search = params.get('search_string', '')
|
|
||||||
if search:
|
|
||||||
qset = (
|
|
||||||
Q(title__icontains=search) |
|
|
||||||
Q(description__icontains=search) |
|
|
||||||
Q(resolution__icontains=search) |
|
|
||||||
Q(submitter_email__icontains=search) |
|
|
||||||
Q(ticketcustomfieldvalue__value__icontains=search)
|
|
||||||
)
|
|
||||||
|
|
||||||
queryset = queryset.filter(qset)
|
|
||||||
|
|
||||||
sorting = params.get('sorting', None)
|
|
||||||
if sorting:
|
|
||||||
sortreverse = params.get('sortreverse', None)
|
|
||||||
if sortreverse:
|
|
||||||
sorting = "-%s" % sorting
|
|
||||||
queryset = queryset.order_by(sorting)
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
def get_query(query, huser):
|
DATATABLES_ORDER_COLUMN_CHOICES = Choices(
|
||||||
# Prefilter the allowed tickets
|
|
||||||
objects = cache.get(huser.user.email + query)
|
|
||||||
if objects is not None:
|
|
||||||
return objects
|
|
||||||
tickets = huser.get_tickets_in_queues().select_related()
|
|
||||||
query_params = query_from_base64(query)
|
|
||||||
ticket_qs = apply_query(tickets, query_params)
|
|
||||||
cache.set(huser.user.email + query, ticket_qs, timeout=3600)
|
|
||||||
return ticket_qs
|
|
||||||
|
|
||||||
|
|
||||||
ORDER_COLUMN_CHOICES = Choices(
|
|
||||||
('0', 'id'),
|
('0', 'id'),
|
||||||
('2', 'priority'),
|
('2', 'priority'),
|
||||||
('3', 'title'),
|
('3', 'title'),
|
||||||
@ -112,45 +82,132 @@ ORDER_COLUMN_CHOICES = Choices(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def query_tickets_by_args(objects, order_by, **kwargs):
|
def get_query_class():
|
||||||
"""
|
from django.conf import settings
|
||||||
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 DatatablesTicketSerializer 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]
|
def _get_query_class():
|
||||||
# django orm '-' -> desc
|
return __Query__
|
||||||
if order == 'desc':
|
return getattr(settings,
|
||||||
order_column = '-' + order_column
|
'HELPDESK_QUERY_CLASS',
|
||||||
|
_get_query_class)()
|
||||||
|
|
||||||
queryset = objects.all().order_by(order_by)
|
|
||||||
total = queryset.count()
|
|
||||||
|
|
||||||
if search_value:
|
class __Query__:
|
||||||
queryset = queryset.filter(Q(id__icontains=search_value) |
|
def __init__(self, huser, base64query=None, query_params=None):
|
||||||
Q(priority__icontains=search_value) |
|
self.huser = huser
|
||||||
Q(title__icontains=search_value) |
|
self.params = query_params if query_params else query_from_base64(base64query)
|
||||||
Q(queue__title__icontains=search_value) |
|
self.base64 = base64query if base64query else query_to_base64(query_params)
|
||||||
Q(status__icontains=search_value) |
|
self.result = None
|
||||||
Q(created__icontains=search_value) |
|
|
||||||
Q(due_date__icontains=search_value) |
|
|
||||||
Q(assigned_to__email__icontains=search_value))
|
|
||||||
|
|
||||||
count = queryset.count()
|
def get_search_filter_args(self):
|
||||||
queryset = queryset.order_by(order_column)[start:start + length]
|
search = self.params.get('search_string', '')
|
||||||
return {
|
return get_search_filter_args(search)
|
||||||
'items': queryset,
|
|
||||||
'count': count,
|
def __run__(self, queryset):
|
||||||
'total': total,
|
"""
|
||||||
'draw': draw
|
Apply a dict-based set of filters & parameters to a queryset.
|
||||||
}
|
|
||||||
|
queryset is a Django queryset, eg MyModel.objects.all() or
|
||||||
|
MyModel.objects.filter(user=request.user)
|
||||||
|
|
||||||
|
params is a dictionary that contains the following:
|
||||||
|
filtering: A dict of Django ORM filters, eg:
|
||||||
|
{'user__id__in': [1, 3, 103], 'title__contains': 'foo'}
|
||||||
|
|
||||||
|
search_string: A freetext search string
|
||||||
|
|
||||||
|
sorting: The name of the column to sort by
|
||||||
|
"""
|
||||||
|
for key in self.params['filtering'].keys():
|
||||||
|
filter = {key: self.params['filtering'][key]}
|
||||||
|
queryset = queryset.filter(**filter)
|
||||||
|
queryset = queryset.filter(self.get_search_filter_args())
|
||||||
|
sorting = self.params.get('sorting', None)
|
||||||
|
if sorting:
|
||||||
|
sortreverse = self.params.get('sortreverse', None)
|
||||||
|
if sortreverse:
|
||||||
|
sorting = "-%s" % sorting
|
||||||
|
queryset = queryset.order_by(sorting)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def refresh_query(self):
|
||||||
|
tickets = self.huser.get_tickets_in_queues().select_related()
|
||||||
|
ticket_qs = self.__run__(tickets)
|
||||||
|
cache.set(self.huser.user.email + self.base64, ticket_qs, timeout=3600)
|
||||||
|
return ticket_qs
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
# Prefilter the allowed tickets
|
||||||
|
objects = cache.get(self.huser.user.email + self.base64)
|
||||||
|
if objects is not None:
|
||||||
|
return objects
|
||||||
|
return self.refresh_query()
|
||||||
|
|
||||||
|
def get_datatables_context(self, **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 DatatablesTicketSerializer in serializers.py.
|
||||||
|
"""
|
||||||
|
objects = self.get()
|
||||||
|
order_by = '-date_created'
|
||||||
|
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 = DATATABLES_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(get_search_filter_args(search_value))
|
||||||
|
|
||||||
|
count = queryset.count()
|
||||||
|
queryset = queryset.order_by(order_column)[start:start + length]
|
||||||
|
return {
|
||||||
|
'data': DatatablesTicketSerializer(queryset, many=True).data,
|
||||||
|
'recordsFiltered': count,
|
||||||
|
'recordsTotal': total,
|
||||||
|
'draw': draw
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_timeline_context(self):
|
||||||
|
events = []
|
||||||
|
|
||||||
|
for ticket in self.get():
|
||||||
|
for followup in ticket.followup_set.all():
|
||||||
|
event = {
|
||||||
|
'start_date': self.mk_timeline_date(followup.date),
|
||||||
|
'text': {
|
||||||
|
'headline': ticket.title + '<br/>' + followup.title,
|
||||||
|
'text': (followup.comment if followup.comment else _('No text')) + '<br/> <a href="%s" class="btn" role="button">%s</a>' %
|
||||||
|
(reverse('helpdesk:view', kwargs={'ticket_id': ticket.pk}), _("View ticket")),
|
||||||
|
},
|
||||||
|
'group': _('Messages'),
|
||||||
|
}
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'events': events,
|
||||||
|
}
|
||||||
|
|
||||||
|
def mk_timeline_date(self, date):
|
||||||
|
return {
|
||||||
|
'year': date.year,
|
||||||
|
'month': date.month,
|
||||||
|
'day': date.day,
|
||||||
|
'hour': date.hour,
|
||||||
|
'minute': date.minute,
|
||||||
|
'second': date.second,
|
||||||
|
'second': date.second,
|
||||||
|
}
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
<div class="col col-sm-6">
|
<div class="col col-sm-6">
|
||||||
<button class='filterBuilderRemove btn btn-danger btn-sm float-right'><i class="fas fa-trash-alt"></i></button>
|
<button class='filterBuilderRemove btn btn-danger btn-sm float-right'><i class="fas fa-trash-alt"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<p class='filterHelp'>{% trans "Keywords are case-insensitive, and will be looked for in the title, body and submitter fields." %}</p>
|
<p class='filterHelp'>{% trans "Keywords are case-insensitive, and will be looked for pretty much everywhere possible. Prepend with 'queue:' or 'priority:' to search by queue or priority." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -125,8 +125,8 @@
|
|||||||
if(!timeline_loaded){
|
if(!timeline_loaded){
|
||||||
timeline = new TL.Timeline(
|
timeline = new TL.Timeline(
|
||||||
'timeline-embed',
|
'timeline-embed',
|
||||||
'{% url 'helpdesk:timeline_ticket_list' urlsafe_query %}',
|
'{% url 'helpdesk:timeline_ticket_list' urlsafe_query %}'
|
||||||
)
|
);
|
||||||
timeline_loaded = true;
|
timeline_loaded = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ from django.test.client import Client
|
|||||||
|
|
||||||
from helpdesk.models import Queue, Ticket
|
from helpdesk.models import Queue, Ticket
|
||||||
from helpdesk import settings
|
from helpdesk import settings
|
||||||
from helpdesk.query import get_query
|
from helpdesk.query import __Query__
|
||||||
from helpdesk.user import HelpdeskUser
|
from helpdesk.user import HelpdeskUser
|
||||||
|
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
for identifier in self.IDENTIFIERS:
|
for identifier in self.IDENTIFIERS:
|
||||||
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
||||||
response = self.client.get(reverse('helpdesk:list'))
|
response = self.client.get(reverse('helpdesk:list'))
|
||||||
tickets = get_query(response.context['urlsafe_query'], HelpdeskUser(self.identifier_users[identifier]))
|
tickets = __Query__(HelpdeskUser(self.identifier_users[identifier]), base64query = response.context['urlsafe_query']).get()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(tickets),
|
len(tickets),
|
||||||
identifier * 2,
|
identifier * 2,
|
||||||
@ -186,7 +186,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
# Superuser
|
# Superuser
|
||||||
self.client.login(username='superuser', password='superuser')
|
self.client.login(username='superuser', password='superuser')
|
||||||
response = self.client.get(reverse('helpdesk:list'))
|
response = self.client.get(reverse('helpdesk:list'))
|
||||||
tickets = get_query(response.context['urlsafe_query'], HelpdeskUser(self.superuser))
|
tickets = __Query__(HelpdeskUser(self.superuser), base64query = response.context['urlsafe_query']).get()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(tickets),
|
len(tickets),
|
||||||
6,
|
6,
|
||||||
|
@ -27,18 +27,14 @@ from django.utils import timezone
|
|||||||
from django.views.generic.edit import FormView, UpdateView
|
from django.views.generic.edit import FormView, UpdateView
|
||||||
|
|
||||||
from helpdesk.query import (
|
from helpdesk.query import (
|
||||||
|
get_query_class,
|
||||||
query_to_dict,
|
query_to_dict,
|
||||||
get_query,
|
|
||||||
apply_query,
|
|
||||||
query_tickets_by_args,
|
|
||||||
query_to_base64,
|
query_to_base64,
|
||||||
query_from_base64,
|
query_from_base64,
|
||||||
)
|
)
|
||||||
|
|
||||||
from helpdesk.user import HelpdeskUser
|
from helpdesk.user import HelpdeskUser
|
||||||
|
|
||||||
from helpdesk.serializers import DatatablesTicketSerializer
|
|
||||||
|
|
||||||
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
|
||||||
@ -71,6 +67,7 @@ import re
|
|||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
Query = get_query_class()
|
||||||
|
|
||||||
if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
||||||
# treat 'normal' users like 'staff'
|
# treat 'normal' users like 'staff'
|
||||||
@ -896,7 +893,7 @@ def ticket_list(request):
|
|||||||
|
|
||||||
urlsafe_query = query_to_base64(query_params)
|
urlsafe_query = query_to_base64(query_params)
|
||||||
|
|
||||||
get_query(urlsafe_query, huser)
|
Query(huser, base64query=urlsafe_query).refresh_query()
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
@ -964,55 +961,16 @@ def datatables_ticket_list(request, query):
|
|||||||
on the table. query_tickets_by_args is at lib.py, DatatablesTicketSerializer is in
|
on the table. query_tickets_by_args is at lib.py, DatatablesTicketSerializer is in
|
||||||
serializers.py. The serializers and this view use django-rest_framework methods
|
serializers.py. The serializers and this view use django-rest_framework methods
|
||||||
"""
|
"""
|
||||||
objects = get_query(query, HelpdeskUser(request.user))
|
query = Query(HelpdeskUser(request.user), base64query=query)
|
||||||
model_object = query_tickets_by_args(objects, '-date_created', **request.query_params)
|
result = query.get_datatables_context(**request.query_params)
|
||||||
serializer = DatatablesTicketSerializer(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 (JsonResponse(result, status=status.HTTP_200_OK))
|
return (JsonResponse(result, status=status.HTTP_200_OK))
|
||||||
|
|
||||||
|
|
||||||
@helpdesk_staff_member_required
|
@helpdesk_staff_member_required
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def timeline_ticket_list(request, query):
|
def timeline_ticket_list(request, query):
|
||||||
"""
|
query = Query(HelpdeskUser(request.user), base64query=query)
|
||||||
Datatable on ticket_list.html uses this view from to get objects to display
|
return (JsonResponse(query.get_timeline_context(), status=status.HTTP_200_OK))
|
||||||
on the table. query_tickets_by_args is at lib.py, DatatablesTicketSerializer is in
|
|
||||||
serializers.py. The serializers and this view use django-rest_framework methods
|
|
||||||
"""
|
|
||||||
tickets = get_query(query, HelpdeskUser(request.user))
|
|
||||||
events = []
|
|
||||||
|
|
||||||
def mk_timeline_date(date):
|
|
||||||
return {
|
|
||||||
'year': date.year,
|
|
||||||
'month': date.month,
|
|
||||||
'day': date.day,
|
|
||||||
'hour': date.hour,
|
|
||||||
'minute': date.minute,
|
|
||||||
'second': date.second,
|
|
||||||
'second': date.second,
|
|
||||||
}
|
|
||||||
for ticket in tickets:
|
|
||||||
for followup in ticket.followup_set.all():
|
|
||||||
event = {
|
|
||||||
'start_date': mk_timeline_date(followup.date),
|
|
||||||
'text': {
|
|
||||||
'headline': ticket.title + '<br/>' + followup.title,
|
|
||||||
'text': (followup.comment if followup.comment else _('No text')) + '<br/> <a href="%s" class="btn" role="button">%s</a>' %
|
|
||||||
(reverse('helpdesk:view', kwargs={'ticket_id': ticket.pk}), _("View ticket")),
|
|
||||||
},
|
|
||||||
'group': _('Messages'),
|
|
||||||
}
|
|
||||||
events.append(event)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'events': events,
|
|
||||||
}
|
|
||||||
return (JsonResponse(result, status=status.HTTP_200_OK))
|
|
||||||
|
|
||||||
|
|
||||||
@helpdesk_staff_member_required
|
@helpdesk_staff_member_required
|
||||||
|
Loading…
Reference in New Issue
Block a user