mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2025-06-06 09:46:51 +02:00
Merge pull request #1241 from django-helpdesk/ticket_query_fixes
Ticket query fixes
This commit is contained in:
commit
3a33f7fef9
@ -28,14 +28,18 @@ def query_from_base64(b64data):
|
|||||||
|
|
||||||
|
|
||||||
def get_search_filter_args(search):
|
def get_search_filter_args(search):
|
||||||
|
if not search:
|
||||||
|
return Q()
|
||||||
if search.startswith('queue:'):
|
if search.startswith('queue:'):
|
||||||
return Q(queue__title__icontains=search[len('queue:'):])
|
return Q(queue__title__icontains=search[len('queue:'):])
|
||||||
if search.startswith('priority:'):
|
if search.startswith('priority:'):
|
||||||
return Q(priority__icontains=search[len('priority:'):])
|
return Q(priority__icontains=search[len('priority:'):])
|
||||||
filter = Q()
|
my_filter = Q()
|
||||||
for subsearch in search.split("OR"):
|
for subsearch in search.split("OR"):
|
||||||
subsearch = subsearch.strip()
|
subsearch = subsearch.strip()
|
||||||
filter = (
|
if not subsearch:
|
||||||
|
continue
|
||||||
|
my_filter = (
|
||||||
filter |
|
filter |
|
||||||
Q(id__icontains=subsearch) |
|
Q(id__icontains=subsearch) |
|
||||||
Q(title__icontains=subsearch) |
|
Q(title__icontains=subsearch) |
|
||||||
@ -48,7 +52,7 @@ def get_search_filter_args(search):
|
|||||||
Q(created__icontains=subsearch) |
|
Q(created__icontains=subsearch) |
|
||||||
Q(due_date__icontains=subsearch)
|
Q(due_date__icontains=subsearch)
|
||||||
)
|
)
|
||||||
return filter
|
return my_filter
|
||||||
|
|
||||||
|
|
||||||
DATATABLES_ORDER_COLUMN_CHOICES = Choices(
|
DATATABLES_ORDER_COLUMN_CHOICES = Choices(
|
||||||
@ -91,23 +95,49 @@ class __Query__:
|
|||||||
|
|
||||||
def __run__(self, queryset):
|
def __run__(self, queryset):
|
||||||
"""
|
"""
|
||||||
Apply a dict-based set of filters & parameters to a queryset.
|
Apply a dict-based set of value_filters & parameters to a queryset.
|
||||||
|
|
||||||
queryset is a Django queryset, eg MyModel.objects.all() or
|
queryset is a Django queryset, eg MyModel.objects.all() or
|
||||||
MyModel.objects.filter(user=request.user)
|
MyModel.objects.filter(user=request.user)
|
||||||
|
|
||||||
params is a dictionary that contains the following:
|
params is a dictionary that contains the following:
|
||||||
filtering: A dict of Django ORM filters, eg:
|
filtering: A dict of Django ORM value_filters, eg:
|
||||||
{'user__id__in': [1, 3, 103], 'title__contains': 'foo'}
|
{'user__id__in': [1, 3, 103], 'title__contains': 'foo'}
|
||||||
|
|
||||||
search_string: A freetext search string
|
search_string: A freetext search string
|
||||||
|
|
||||||
sorting: The name of the column to sort by
|
sorting: The name of the column to sort by
|
||||||
"""
|
"""
|
||||||
filter = self.params.get('filtering', {})
|
q_args = []
|
||||||
filter_or = self.params.get('filtering_or', {})
|
value_filters = self.params.get('filtering', {})
|
||||||
|
null_filters = self.params.get('filtering_null', {})
|
||||||
|
if null_filters:
|
||||||
|
if value_filters:
|
||||||
|
# Check if any of the value value_filters are for the same field as the
|
||||||
|
# ISNULL filter so that an OR filter can be set up
|
||||||
|
matched_null_keys = []
|
||||||
|
for null_key in null_filters:
|
||||||
|
field_path = null_key[:-8] # Chop off the "__isnull"
|
||||||
|
matched_key = None
|
||||||
|
for val_key in value_filters:
|
||||||
|
if val_key.startswith(field_path):
|
||||||
|
matched_key = val_key
|
||||||
|
break
|
||||||
|
if matched_key:
|
||||||
|
# Remove the matching filters into a Q param
|
||||||
|
matched_null_keys.append(null_key)
|
||||||
|
# Create an OR query for the selected value(s) OR if the field is NULL
|
||||||
|
v = {}
|
||||||
|
v[val_key] = value_filters[val_key]
|
||||||
|
n = {}
|
||||||
|
n[null_key] = null_filters[null_key]
|
||||||
|
q_args.append((Q(**v) | Q(**n)))
|
||||||
|
del value_filters[matched_key]
|
||||||
|
# Now remove the matched null keys
|
||||||
|
for null_key in matched_null_keys:
|
||||||
|
del null_filters[null_key]
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
(Q(**filter) | Q(**filter_or)) & self.get_search_filter_args())
|
*q_args, (Q(**value_filters) & Q(**null_filters)) & self.get_search_filter_args())
|
||||||
sorting = self.params.get('sorting', None)
|
sorting = self.params.get('sorting', None)
|
||||||
if sorting:
|
if sorting:
|
||||||
sortreverse = self.params.get('sortreverse', None)
|
sortreverse = self.params.get('sortreverse', None)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="col col-sm-3">
|
<div class="col col-sm-3">
|
||||||
<select id='id_kbitems' name='kbitem' multiple='selected' size='5'>
|
<select id='id_kbitems' name='kbitem' multiple='selected' size='5'>
|
||||||
{% with magic_number=-1 %}
|
{% with magic_number=-1 %}
|
||||||
<option value='{{magic_number}}'{% if magic_number|in_list:query_params.filtering.kbitem__in %} selected='selected'{% endif %}>
|
<option value='{{magic_number}}'{% if query_params.filtering_null.kbitem__isnull %} selected='selected'{% endif %}>
|
||||||
{% trans "Uncategorized" %}
|
{% trans "Uncategorized" %}
|
||||||
</option>
|
</option>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="col col-sm-3">
|
<div class="col col-sm-3">
|
||||||
<select id='id_owners' name='assigned_to' multiple='selected' size='5'>
|
<select id='id_owners' name='assigned_to' multiple='selected' size='5'>
|
||||||
{% with magic_number=-1 %}
|
{% with magic_number=-1 %}
|
||||||
<option value='{{magic_number}}'{% if magic_number|in_list:query_params.filtering.assigned_to__id__in %} selected='selected'{% endif %}>
|
<option value='{{magic_number}}'{% if query_params.filtering_null.assigned_to__id__isnull %} selected='selected'{% endif %}>
|
||||||
{% trans "Unassigned" %}
|
{% trans "Unassigned" %}
|
||||||
</option>
|
</option>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -7,6 +7,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-sm-3">
|
<div class="col col-sm-3">
|
||||||
<select id='id_queues' name='queue' multiple='selected' size='5'>
|
<select id='id_queues' name='queue' multiple='selected' size='5'>
|
||||||
|
{% with magic_number=-1 %}
|
||||||
|
<option value='{{magic_number}}'{% if query_params.filtering_null.queue__id__isnull %} selected='selected'{% endif %}>
|
||||||
|
{% trans "Unassigned" %}
|
||||||
|
</option>
|
||||||
|
{% endwith %}
|
||||||
{% for q in queue_choices %}
|
{% for q in queue_choices %}
|
||||||
<option value='{{ q.id }}'{% if q.id|in_list:query_params.filtering.queue__id__in %} selected="selected"{% endif %}>{{ q.title }}</option>
|
<option value='{{ q.id }}'{% if q.id|in_list:query_params.filtering.queue__id__in %} selected="selected"{% endif %}>{{ q.title }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -170,10 +170,10 @@
|
|||||||
<option id="filterBuilderSelect-Sort" value="Sort"{% if query_params.sorting %} disabled{% endif %}>
|
<option id="filterBuilderSelect-Sort" value="Sort"{% if query_params.sorting %} disabled{% endif %}>
|
||||||
{% trans "Sorting" %}
|
{% trans "Sorting" %}
|
||||||
</option>
|
</option>
|
||||||
<option id="filterBuilderSelect-Owner" value="Owner"{% if query_params.filtering.assigned_to__id__in %} disabled{% endif %}>
|
<option id="filterBuilderSelect-Owner" value="Owner"{% if query_params.filtering.assigned_to__id__in or query_params.filtering_null.assigned_to__id__isnull %} disabled{% endif %}>
|
||||||
{% trans "Owner" %}
|
{% trans "Owner" %}
|
||||||
</option>
|
</option>
|
||||||
<option id="filterBuilderSelect-Queue" value="Queue"{% if query_params.filtering.queue__id__in %} disabled{% endif %}>
|
<option id="filterBuilderSelect-Queue" value="Queue"{% if query_params.filtering.queue__id__in or query_params.filtering_null.queue__id__isnull %} disabled{% endif %}>
|
||||||
{% trans "Queue" %}
|
{% trans "Queue" %}
|
||||||
</option>
|
</option>
|
||||||
<option id="filterBuilderSelect-Status" value="Status"{% if query_params.filtering.status__in %} disabled{% endif %}>
|
<option id="filterBuilderSelect-Status" value="Status"{% if query_params.filtering.status__in %} disabled{% endif %}>
|
||||||
@ -185,7 +185,7 @@
|
|||||||
<option id="filterBuilderSelect-Dates" value="Dates"{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} disabled{% endif %}>
|
<option id="filterBuilderSelect-Dates" value="Dates"{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} disabled{% endif %}>
|
||||||
{% trans "Date Range" %}
|
{% trans "Date Range" %}
|
||||||
</option>
|
</option>
|
||||||
<option id="filterBuilderSelect-KBItems" value="KBItems"{% if query_params.filtering.kbitem__in %} disabled{% endif %}>
|
<option id="filterBuilderSelect-KBItems" value="KBItems"{% if query_params.filtering.kbitem__in or query_params.filtering_null.kbitem__isnull %} disabled{% endif %}>
|
||||||
{% trans "Knowledge base items" %}
|
{% trans "Knowledge base items" %}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -200,11 +200,11 @@
|
|||||||
id="filterBoxSort">
|
id="filterBoxSort">
|
||||||
{% include 'helpdesk/filters/sorting.html' %}
|
{% include 'helpdesk/filters/sorting.html' %}
|
||||||
</li>
|
</li>
|
||||||
<li class="filterBox{% if query_params.filtering.assigned_to__id__in %} filterBoxShow{% endif %} list-group-item"
|
<li class="filterBox{% if query_params.filtering.assigned_to__id__in or query_params.filtering_null.assigned_to__id__isnull %} filterBoxShow{% endif %} list-group-item"
|
||||||
id=filterBoxOwner>
|
id=filterBoxOwner>
|
||||||
{% include 'helpdesk/filters/owner.html' %}
|
{% include 'helpdesk/filters/owner.html' %}
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item filterBox{% if query_params.filtering.queue__id__in %} filterBoxShow{% endif %}"
|
<li class="list-group-item filterBox{% if query_params.filtering.queue__id__in or query_params.filtering_null.queue__id__isnull %} filterBoxShow{% endif %}"
|
||||||
id="filterBoxQueue">
|
id="filterBoxQueue">
|
||||||
{% include 'helpdesk/filters/queue.html' %}
|
{% include 'helpdesk/filters/queue.html' %}
|
||||||
</li>
|
</li>
|
||||||
@ -220,7 +220,7 @@
|
|||||||
id="filterBoxKeywords">
|
id="filterBoxKeywords">
|
||||||
{% include 'helpdesk/filters/keywords.html' %}
|
{% include 'helpdesk/filters/keywords.html' %}
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item filterBox{% if query_params.filtering.kbitem__in %} filterBoxShow{% endif %}"
|
<li class="list-group-item filterBox{% if query_params.filtering.kbitem__in or query_params.filtering_null.kbitem__isnull %} filterBoxShow{% endif %}"
|
||||||
id="filterBoxKBItems">
|
id="filterBoxKBItems">
|
||||||
{% include 'helpdesk/filters/kbitems.html' %}
|
{% include 'helpdesk/filters/kbitems.html' %}
|
||||||
</li>
|
</li>
|
||||||
|
@ -8,6 +8,7 @@ from helpdesk.tests.helpers import get_staff_user
|
|||||||
|
|
||||||
class QueryTests(TestCase):
|
class QueryTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# self.maxDiff = None
|
||||||
self.queue = Queue.objects.create(
|
self.queue = Queue.objects.create(
|
||||||
title="Test queue",
|
title="Test queue",
|
||||||
slug="test_queue",
|
slug="test_queue",
|
||||||
@ -94,7 +95,7 @@ class QueryTests(TestCase):
|
|||||||
def test_query_by_no_kbitem(self):
|
def test_query_by_no_kbitem(self):
|
||||||
self.loginUser()
|
self.loginUser()
|
||||||
query = query_to_base64(
|
query = query_to_base64(
|
||||||
{'filtering_or': {'kbitem__in': [self.kbitem1.pk]}}
|
{'filtering_null': {'kbitem__isnull': True}}
|
||||||
)
|
)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse('helpdesk:datatables_ticket_list', args=[query]))
|
reverse('helpdesk:datatables_ticket_list', args=[query]))
|
||||||
@ -103,8 +104,8 @@ class QueryTests(TestCase):
|
|||||||
resp_json,
|
resp_json,
|
||||||
{
|
{
|
||||||
"data":
|
"data":
|
||||||
[{"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open",
|
[{"ticket": "1 [test_queue-1]", "id": 1, "priority": 3, "title": "unassigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open",
|
||||||
"created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}],
|
"created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "row_class": "", "time_spent": "", "kbitem": ""}],
|
||||||
"recordsFiltered": 1,
|
"recordsFiltered": 1,
|
||||||
"recordsTotal": 1,
|
"recordsTotal": 1,
|
||||||
"draw": 0,
|
"draw": 0,
|
||||||
|
@ -991,7 +991,7 @@ def ticket_list(request):
|
|||||||
# a query, to be saved if needed:
|
# a query, to be saved if needed:
|
||||||
query_params = {
|
query_params = {
|
||||||
'filtering': {},
|
'filtering': {},
|
||||||
'filtering_or': {},
|
'filtering_null': {},
|
||||||
'sorting': None,
|
'sorting': None,
|
||||||
'sortreverse': False,
|
'sortreverse': False,
|
||||||
'search_string': '',
|
'search_string': '',
|
||||||
@ -1035,12 +1035,20 @@ def ticket_list(request):
|
|||||||
for param, filter_command in filter_in_params:
|
for param, filter_command in filter_in_params:
|
||||||
if request.GET.get(param) is not None:
|
if request.GET.get(param) is not None:
|
||||||
patterns = request.GET.getlist(param)
|
patterns = request.GET.getlist(param)
|
||||||
|
if not patterns:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
minus_1_ndx = patterns.index("-1")
|
||||||
|
# Must have the value so remove it and configure to use OR filter on NULL
|
||||||
|
patterns.pop(minus_1_ndx)
|
||||||
|
query_params['filtering_null'][filter_null_params[param]] = True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if not patterns:
|
||||||
|
# Caters for the case where the filter is only a null filter
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
pattern_pks = [int(pattern) for pattern in patterns]
|
pattern_pks = [int(pattern) for pattern in patterns]
|
||||||
if -1 in pattern_pks:
|
|
||||||
query_params['filtering_or'][filter_null_params[param]] = True
|
|
||||||
else:
|
|
||||||
query_params['filtering_or'][filter_command] = pattern_pks
|
|
||||||
query_params['filtering'][filter_command] = pattern_pks
|
query_params['filtering'][filter_command] = pattern_pks
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user