diff --git a/helpdesk/query.py b/helpdesk/query.py
index a2263326..6a56baea 100644
--- a/helpdesk/query.py
+++ b/helpdesk/query.py
@@ -1,12 +1,16 @@
from django.db.models import Q
from django.core.cache import cache
-
-from model_utils import Choices
+from django.urls import reverse
+from django.utils.translation import ugettext as _
from base64 import b64encode
from base64 import b64decode
import json
+from model_utils import Choices
+
+from helpdesk.serializers import DatatablesTicketSerializer
+
def query_to_base64(query):
"""
@@ -47,60 +51,26 @@ def query_to_dict(results, descriptions):
return output
-def apply_query(queryset, params):
- """
- 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 params['filtering'].keys():
- 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_search_filter_args(search):
+ if search.startswith('queue:'):
+ return Q(queue__title__icontains=search[len('queue:'):])
+ if search.startswith('priority:'):
+ return Q(priority__icontains=search[len('priority:'):])
+ return (
+ Q(id__icontains=search) |
+ Q(title__icontains=search) |
+ Q(description__icontains=search) |
+ Q(priority__icontains=search) |
+ Q(resolution__icontains=search) |
+ Q(submitter_email__icontains=search) |
+ Q(assigned_to__email__icontains=search) |
+ Q(ticketcustomfieldvalue__value__icontains=search) |
+ Q(created__icontains=search) |
+ Q(due_date__icontains=search)
+ )
-def get_query(query, huser):
- # 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(
+DATATABLES_ORDER_COLUMN_CHOICES = Choices(
('0', 'id'),
('2', 'priority'),
('3', 'title'),
@@ -112,45 +82,132 @@ ORDER_COLUMN_CHOICES = Choices(
)
-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 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]
+def get_query_class():
+ from django.conf import settings
- order_column = ORDER_COLUMN_CHOICES[order_column]
- # django orm '-' -> desc
- if order == 'desc':
- order_column = '-' + order_column
+ def _get_query_class():
+ return __Query__
+ return getattr(settings,
+ 'HELPDESK_QUERY_CLASS',
+ _get_query_class)()
- 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))
+class __Query__:
+ def __init__(self, huser, base64query=None, query_params=None):
+ self.huser = huser
+ self.params = query_params if query_params else query_from_base64(base64query)
+ self.base64 = base64query if base64query else query_to_base64(query_params)
+ self.result = None
- count = queryset.count()
- queryset = queryset.order_by(order_column)[start:start + length]
- return {
- 'items': queryset,
- 'count': count,
- 'total': total,
- 'draw': draw
- }
+ def get_search_filter_args(self):
+ search = self.params.get('search_string', '')
+ return get_search_filter_args(search)
+
+ def __run__(self, queryset):
+ """
+ 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 + '
' + followup.title,
+ 'text': (followup.comment if followup.comment else _('No text')) + '
%s' %
+ (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,
+ }
diff --git a/helpdesk/templates/helpdesk/filters/keywords.html b/helpdesk/templates/helpdesk/filters/keywords.html
index 60af3fd6..adf37a7e 100644
--- a/helpdesk/templates/helpdesk/filters/keywords.html
+++ b/helpdesk/templates/helpdesk/filters/keywords.html
@@ -9,5 +9,5 @@
{% trans "Keywords are case-insensitive, and will be looked for in the title, body and submitter fields." %}
+{% 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." %}
diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index f9818f99..3e56eb7c 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -125,8 +125,8 @@ if(!timeline_loaded){ timeline = new TL.Timeline( 'timeline-embed', - '{% url 'helpdesk:timeline_ticket_list' urlsafe_query %}', - ) + '{% url 'helpdesk:timeline_ticket_list' urlsafe_query %}' + ); timeline_loaded = true; } }); diff --git a/helpdesk/tests/test_per_queue_staff_permission.py b/helpdesk/tests/test_per_queue_staff_permission.py index b70d7816..5c036322 100644 --- a/helpdesk/tests/test_per_queue_staff_permission.py +++ b/helpdesk/tests/test_per_queue_staff_permission.py @@ -6,7 +6,7 @@ from django.test.client import Client from helpdesk.models import Queue, Ticket from helpdesk import settings -from helpdesk.query import get_query +from helpdesk.query import __Query__ from helpdesk.user import HelpdeskUser @@ -166,7 +166,7 @@ class PerQueueStaffMembershipTestCase(TestCase): for identifier in self.IDENTIFIERS: self.client.login(username='User_%d' % identifier, password=str(identifier)) 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( len(tickets), identifier * 2, @@ -186,7 +186,7 @@ class PerQueueStaffMembershipTestCase(TestCase): # Superuser self.client.login(username='superuser', password='superuser') 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( len(tickets), 6, diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 87e1b68a..c374b51b 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -27,18 +27,14 @@ from django.utils import timezone from django.views.generic.edit import FormView, UpdateView from helpdesk.query import ( + get_query_class, query_to_dict, - get_query, - apply_query, - query_tickets_by_args, query_to_base64, query_from_base64, ) from helpdesk.user import HelpdeskUser -from helpdesk.serializers import DatatablesTicketSerializer - from helpdesk.decorators import ( helpdesk_staff_member_required, helpdesk_superuser_required, is_helpdesk_staff @@ -71,6 +67,7 @@ import re User = get_user_model() +Query = get_query_class() if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: # treat 'normal' users like 'staff' @@ -896,7 +893,7 @@ def ticket_list(request): 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)) @@ -964,55 +961,16 @@ def datatables_ticket_list(request, query): 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 """ - objects = get_query(query, HelpdeskUser(request.user)) - model_object = query_tickets_by_args(objects, '-date_created', **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'] + query = Query(HelpdeskUser(request.user), base64query=query) + result = query.get_datatables_context(**request.query_params) return (JsonResponse(result, status=status.HTTP_200_OK)) @helpdesk_staff_member_required @api_view(['GET']) def timeline_ticket_list(request, query): - """ - 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, 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 + '