Refactor ticket list code

This commit is contained in:
Timothy Hobbs 2019-10-11 12:41:06 +02:00
parent b862732512
commit 03d1c66dd6
5 changed files with 68 additions and 71 deletions

View File

@ -38,7 +38,11 @@ def query_from_base64(b64data):
""" """
Converts base64-encoded bytes object back to a query dict object. Converts base64-encoded bytes object back to a query dict object.
""" """
return json.loads(b64decode(b64data).decode('utf-8')) query = {'search_string': ''}
query.update(json.loads(b64decode(b64data).decode('utf-8')))
if query['search_string'] is None:
query['search_string'] = ''
return query
def query_to_dict(results, descriptions): def query_to_dict(results, descriptions):
@ -81,7 +85,7 @@ def apply_query(queryset, params):
filter = {key: params['filtering'][key]} filter = {key: params['filtering'][key]}
queryset = queryset.filter(**filter) queryset = queryset.filter(**filter)
search = params.get('search_string', None) search = params.get('search_string', '')
if search: if search:
qset = ( qset = (
Q(title__icontains=search) | Q(title__icontains=search) |
@ -257,7 +261,7 @@ def query_tickets_by_args(objects, order_by, **kwargs):
function filters existing dataset on search string and returns a filtered function filters existing dataset on search string and returns a filtered
filtered list. The `draw`, `length` etc parameters are for datatables to filtered list. The `draw`, `length` etc parameters are for datatables to
display meta data on the table contents. The returning queryset is passed display meta data on the table contents. The returning queryset is passed
to a Serializer called TicketSerializer in serializers.py. to a Serializer called DatatablesTicketSerializer in serializers.py.
""" """
draw = int(kwargs.get('draw', None)[0]) draw = int(kwargs.get('draw', None)[0])
length = int(kwargs.get('length', None)[0]) length = int(kwargs.get('length', None)[0])

View File

@ -12,7 +12,7 @@ datatables for ticket_list.html. Called from staff.datatables_ticket_list.
""" """
class TicketSerializer(serializers.ModelSerializer): class DatatablesTicketSerializer(serializers.ModelSerializer):
ticket = serializers.SerializerMethodField() ticket = serializers.SerializerMethodField()
assigned_to = serializers.SerializerMethodField() assigned_to = serializers.SerializerMethodField()
created = serializers.SerializerMethodField() created = serializers.SerializerMethodField()

View File

@ -4,7 +4,7 @@
<label for='id_query'>{% trans "Keywords" %}</label> <label for='id_query'>{% trans "Keywords" %}</label>
</div> </div>
<div class="col col-sm-3"> <div class="col col-sm-3">
<input type='text' name='q' value='{{ query }}' id='id_query' /> <input type='text' name='q' value='{{ query_params.search_string }}' id='id_query' />
</div> </div>
<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>

View File

@ -84,7 +84,7 @@
<li class="list-group-item filterBox{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} filterBoxShow{% endif %}" id='filterBoxDates'> <li class="list-group-item filterBox{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} filterBoxShow{% endif %}" id='filterBoxDates'>
{% include './filters/date.html' %} {% include './filters/date.html' %}
</li> </li>
<li class="list-group-item filterBox{% if query %} filterBoxShow{% endif %}" id="filterBoxKeywords"> <li class="list-group-item filterBox{% if query_params.search_string %} filterBoxShow{% endif %}" id="filterBoxKeywords">
{% include './filters/keywords.html' %} {% include './filters/keywords.html' %}
</li> </li>
</ul> </ul>
@ -342,7 +342,7 @@
{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} {% if query_params.filtering.created__gte or query_params.filtering.created__lte %}
$("#filterBuilderSelect-Dates")[0].disabled = "disabled"; $("#filterBuilderSelect-Dates")[0].disabled = "disabled";
{% endif %} {% endif %}
{% if query %} {% if query_params.search_string %}
$("#filterBuilderSelect-Keywords")[0].disabled = "disabled"; $("#filterBuilderSelect-Keywords")[0].disabled = "disabled";
{% endif %} {% endif %}
}); });

View File

@ -7,6 +7,7 @@ views/staff.py - The bulk of the application - provides most business logic and
renders all staff-facing views. renders all staff-facing views.
""" """
from copy import deepcopy from copy import deepcopy
import json
from django import VERSION as DJANGO_VERSION from django import VERSION as DJANGO_VERSION
from django.conf import settings from django.conf import settings
@ -26,8 +27,9 @@ from django.utils import timezone
from django.views.generic.edit import FormView, UpdateView from django.views.generic.edit import FormView, UpdateView
from django.core.cache import cache from django.core.cache import cache
from helpdesk.lib import query_tickets_by_args from helpdesk.lib import query_tickets_by_args, query_to_base64, query_from_base64
from helpdesk.serializers import TicketSerializer
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,
@ -838,16 +840,15 @@ def ticket_list(request):
'filtering': {}, 'filtering': {},
'sorting': None, 'sorting': None,
'sortreverse': False, 'sortreverse': False,
'keyword': None, 'search_string': '',
'search_string': None,
} }
default_query_params = { default_query_params = {
'filtering': {'status__in': [1, 2, 3]}, 'filtering': {'status__in': [1, 2, 3]},
'sorting': 'created', 'sorting': 'created',
'search_string': '',
'sortreverse': False,
} }
from_saved_query = False
# If the user is coming from the header/navigation search box, lets' first # If the user is coming from the header/navigation search box, lets' first
# look at their query to see if they have entered a valid ticket number. If # look at their query to see if they have entered a valid ticket number. If
# they have, just redirect to that ticket number. Otherwise, we treat it as # they have, just redirect to that ticket number. Otherwise, we treat it as
@ -882,27 +883,13 @@ def ticket_list(request):
# Go on to standard keyword searching # Go on to standard keyword searching
pass pass
saved_query = None try:
if request.GET.get('saved_query', None): saved_query, query_params = load_saved_query(request, query_params)
from_saved_query = True except QueryLoadError:
try: return HttpResponseRedirect(reverse('helpdesk:list'))
saved_query = SavedSearch.objects.get(pk=request.GET.get('saved_query'))
except SavedSearch.DoesNotExist:
return HttpResponseRedirect(reverse('helpdesk:list'))
if not (saved_query.shared or saved_query.user == request.user):
return HttpResponseRedirect(reverse('helpdesk:list'))
import json
from helpdesk.lib import query_from_base64
try:
# we get a string like: b'stuff'
# so leave of the first two chars (b') and last (')
b64query = saved_query.query[2:-1]
query_params = query_from_base64(b64query)
except ValueError:
# Query deserialization failed. (E.g. was a pickled query)
return HttpResponseRedirect(reverse('helpdesk:list'))
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'}.intersection(request.GET):
# Fall-back if no querying is being done # Fall-back if no querying is being done
all_queues = Queue.objects.all() all_queues = Queue.objects.all()
@ -932,11 +919,9 @@ def ticket_list(request):
query_params['filtering']['created__lte'] = date_to query_params['filtering']['created__lte'] = date_to
# KEYWORD SEARCHING # KEYWORD SEARCHING
q = request.GET.get('q', None) q = request.GET.get('q', '')
context['query'] = q
if q: query_params['search_string'] = q
context = dict(context, query=q)
query_params['search_string'] = q
# SORTING # SORTING
sort = request.GET.get('sort', None) sort = request.GET.get('sort', None)
@ -955,8 +940,14 @@ def ticket_list(request):
# invalid parameters in query, return default query # invalid parameters in query, return default query
ticket_qs = apply_query(tickets, default_query_params) ticket_qs = apply_query(tickets, default_query_params)
urlsafe_query = query_to_base64(query_params)
cache.set('ticket_qs', ticket_qs)
user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
search_message = '' search_message = ''
if 'query' in context and settings.DATABASES['default']['ENGINE'].endswith('sqlite'): if query_params['search_string'] and settings.DATABASES['default']['ENGINE'].endswith('sqlite'):
search_message = _( search_message = _(
'<p><strong>Note:</strong> Your keyword search is case sensitive ' '<p><strong>Note:</strong> Your keyword search is case sensitive '
'because of your database. This means the search will <strong>not</strong> ' 'because of your database. This means the search will <strong>not</strong> '
@ -965,13 +956,6 @@ def ticket_list(request):
'<a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">' '<a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">'
'Django Documentation on string matching in SQLite</a>.') 'Django Documentation on string matching in SQLite</a>.')
import json
from helpdesk.lib import query_to_base64
urlsafe_query = query_to_base64(query_params)
user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
cache.set('ticket_qs', ticket_qs)
return render(request, 'helpdesk/ticket_list.html', dict( return render(request, 'helpdesk/ticket_list.html', dict(
context, context,
@ -982,7 +966,7 @@ def ticket_list(request):
urlsafe_query=urlsafe_query, urlsafe_query=urlsafe_query,
user_saved_queries=user_saved_queries, user_saved_queries=user_saved_queries,
query_params=query_params, query_params=query_params,
from_saved_query=from_saved_query, from_saved_query=saved_query is not None,
saved_query=saved_query, saved_query=saved_query,
search_message=search_message, search_message=search_message,
)) ))
@ -991,18 +975,43 @@ def ticket_list(request):
ticket_list = staff_member_required(ticket_list) ticket_list = staff_member_required(ticket_list)
class QueryLoadError(Exception):
pass
def load_saved_query(request, query_params=None):
saved_query = None
if request.GET.get('saved_query', None):
try:
saved_query = SavedSearch.objects.get(pk=request.GET.get('saved_query'))
except SavedSearch.DoesNotExist:
raise QueryLoadError()
if not (saved_query.shared or saved_query.user == request.user):
raise QueryLoadError()
try:
# we get a string like: b'stuff'
# so leave of the first two chars (b') and last (')
b64query = saved_query.query[2:-1]
query_params = query_from_base64(b64query)
except json.JSONDecodeError:
raise QueryLoadError()
return (saved_query, query_params)
@helpdesk_staff_member_required @helpdesk_staff_member_required
@api_view(['GET', 'POST']) @api_view(['GET'])
def datatables_ticket_list(request): def datatables_ticket_list(request):
""" """
Datatable on ticket_list.html uses this view from to get objects to display 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 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
""" """
try: try:
objects = cache.get('ticket_qs') objects = cache.get('ticket_qs')
model_object = query_tickets_by_args(objects, '-date_created', **request.query_params) model_object = query_tickets_by_args(objects, '-date_created', **request.query_params)
serializer = TicketSerializer(model_object['items'], many=True) serializer = DatatablesTicketSerializer(model_object['items'], many=True)
result = dict() result = dict()
result['data'] = serializer.data result['data'] = serializer.data
result['draw'] = model_object['draw'] result['draw'] = model_object['draw']
@ -1188,28 +1197,12 @@ def run_report(request, report):
queue__in=_get_user_queues(request.user) queue__in=_get_user_queues(request.user)
) )
from_saved_query = False try:
saved_query = None saved_query, query_params = load_saved_query(request)
except QueryLoadError:
return HttpResponseRedirect(reverse('helpdesk:report_index'))
if request.GET.get('saved_query', None): if request.GET.get('saved_query', None):
from_saved_query = True
try:
saved_query = SavedSearch.objects.get(pk=request.GET.get('saved_query'))
except SavedSearch.DoesNotExist:
return HttpResponseRedirect(reverse('helpdesk:report_index'))
if not (saved_query.shared or saved_query.user == request.user):
return HttpResponseRedirect(reverse('helpdesk:report_index'))
import json
from helpdesk.lib import query_from_base64
try:
# we get a string like: b'stuff'
# so leave of the first two chars (b') and last (')
b64query = saved_query.query[2:-1]
query_params = query_from_base64(b64query)
except json.JSONDecodeError:
return HttpResponseRedirect(reverse('helpdesk:report_index'))
report_queryset = apply_query(report_queryset, query_params) report_queryset = apply_query(report_queryset, query_params)
from collections import defaultdict from collections import defaultdict
@ -1372,7 +1365,7 @@ def run_report(request, report):
'headings': column_headings, 'headings': column_headings,
'series_names': series_names, 'series_names': series_names,
'morrisjs_data': morrisjs_data, 'morrisjs_data': morrisjs_data,
'from_saved_query': from_saved_query, 'from_saved_query': saved_query is not None,
'saved_query': saved_query, 'saved_query': saved_query,
}) })