From 921d4242399fd7379d8daaa1d643c4be2e07775e Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 11 Mar 2025 23:01:38 +0000 Subject: [PATCH 01/26] last followups show in all tickets but cant be sorted by --- helpdesk/lib.py | 1 + helpdesk/query.py | 5 +++-- helpdesk/serializers.py | 13 ++++++++---- .../helpdesk/filters/followupdates.html | 20 +++++++++++++++++++ helpdesk/templates/helpdesk/ticket_list.html | 17 ++++++++++++++++ helpdesk/views/staff.py | 13 +++++++++++- 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 helpdesk/templates/helpdesk/filters/followupdates.html diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 83f5b1a8..03fe22fd 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -34,6 +34,7 @@ def ticket_template_context(ticket): else: context[field] = attr context['assigned_to'] = context['_get_assigned_to'] + context['last_followup'] = ticket.followups.latest('date') return context diff --git a/helpdesk/query.py b/helpdesk/query.py index 9c5a0930..d186d147 100644 --- a/helpdesk/query.py +++ b/helpdesk/query.py @@ -65,8 +65,9 @@ DATATABLES_ORDER_COLUMN_CHOICES = Choices( ('6', 'due_date'), ('7', 'assigned_to'), ('8', 'submitter_email'), - # ('9', 'time_spent'), - ('10', 'kbitem'), + ('9', 'last_followup'), + # ('10', 'time_spent'), + ('11', 'kbitem'), ) diff --git a/helpdesk/serializers.py b/helpdesk/serializers.py index c8c4dbb7..8d899454 100644 --- a/helpdesk/serializers.py +++ b/helpdesk/serializers.py @@ -1,5 +1,6 @@ from django.contrib.auth import get_user_model from django.contrib.humanize.templatetags import humanize +from django.utils.timezone import localtime from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -18,6 +19,7 @@ class DatatablesTicketSerializer(serializers.ModelSerializer): ticket = serializers.SerializerMethodField() assigned_to = serializers.SerializerMethodField() submitter = serializers.SerializerMethodField() + last_followup = serializers.SerializerMethodField() created = serializers.SerializerMethodField() due_date = serializers.SerializerMethodField() status = serializers.SerializerMethodField() @@ -30,8 +32,8 @@ class DatatablesTicketSerializer(serializers.ModelSerializer): model = Ticket # fields = '__all__' fields = ('ticket', 'id', 'priority', 'title', 'queue', 'status', - 'created', 'due_date', 'assigned_to', 'submitter', 'row_class', - 'time_spent', 'kbitem') + 'created', 'due_date', 'assigned_to', 'submitter', 'last_followup', + 'row_class', 'time_spent', 'kbitem') def get_queue(self, obj): return {"title": obj.queue.title, "id": obj.queue.id} @@ -70,8 +72,11 @@ class DatatablesTicketSerializer(serializers.ModelSerializer): def get_kbitem(self, obj): return obj.kbitem.title if obj.kbitem else "" - - + + def get_last_followup(self, obj): + followup = obj.followup_set.latest('date') + return localtime(followup.date).strftime('%Y-%m-%d %H:%M:%S') if followup else "" + class FollowUpAttachmentSerializer(serializers.ModelSerializer): class Meta: model = FollowUpAttachment diff --git a/helpdesk/templates/helpdesk/filters/followupdates.html b/helpdesk/templates/helpdesk/filters/followupdates.html new file mode 100644 index 00000000..d8e9e8df --- /dev/null +++ b/helpdesk/templates/helpdesk/filters/followupdates.html @@ -0,0 +1,20 @@ +{% load i18n humanize %} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +

{% trans "Use YYYY-MM-DD date format, eg 2018-01-30. This only searches when the last followup occured" %}

+
diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index 2e644c7f..298c0cc5 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -76,6 +76,7 @@ {% trans "Due Date" %} {% trans "Owner" %} {% trans "Submitter" %} + {% trans "Last Followup" %} {% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %}{% trans "Time Spent" %}{% endif %} {% if helpdesk_settings.HELPDESK_KB_ENABLED %}{% trans "KB item" %}{% endif %} @@ -188,6 +189,9 @@ + @@ -224,6 +228,10 @@ id="filterBoxKBItems"> {% include 'helpdesk/filters/kbitems.html' %} +
  • + {% include 'helpdesk/filters/followupdates.html' %} +
  • @@ -415,6 +423,15 @@ } }, {data: "submitter"}, + { + data: "last_followup", + render: function (data, type, row, meta) { + if (data && data !== "None") { + return data; + } + return ""; + } + }, {% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %} {data: "time_spent", "visible": false}, {% endif %} diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 89aea899..04babdda 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -1025,12 +1025,14 @@ def ticket_list(request): ('assigned_to', 'assigned_to__id__in'), ('status', 'status__in'), ('kbitem', 'kbitem__in'), + ('last_followup', 'last_followup__in'), ] filter_null_params = dict([ ('queue', 'queue__id__isnull'), ('assigned_to', 'assigned_to__id__isnull'), ('status', 'status__isnull'), ('kbitem', 'kbitem__isnull'), + ('last_followup', 'last_followup__isnull'), ]) for param, filter_command in filter_in_params: if request.GET.get(param) is not None: @@ -1061,6 +1063,14 @@ def ticket_list(request): if date_to: query_params['filtering']['created__lte'] = date_to + followup_from = request.GET.get('followup_from') + if followup_from: + query_params['filtering']['followup__gte'] = followup_from + + followup_to = request.GET.get('followup_to') + if followup_to: + query_params['filtering']['followup__lte'] = followup_to + # KEYWORD SEARCHING q = request.GET.get('q', '') context['query'] = q @@ -1068,7 +1078,7 @@ def ticket_list(request): # SORTING sort = request.GET.get('sort', None) - if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority', 'kbitem'): + if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority', 'last_followup', 'kbitem'): sort = 'created' query_params['sorting'] = sort @@ -1158,6 +1168,7 @@ def datatables_ticket_list(request, query): """ query = Query(HelpdeskUser(request.user), base64query=query) result = query.get_datatables_context(**request.query_params) + print(result) return JsonResponse(result, status=status.HTTP_200_OK) From b7b04166fc1a1678ea32c7cfb905f34c71b1faa7 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 12 Mar 2025 02:46:08 +0000 Subject: [PATCH 02/26] sorting on Last Followup works --- helpdesk/query.py | 4 +++- helpdesk/serializers.py | 10 ++++++++-- helpdesk/views/staff.py | 1 - 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/helpdesk/query.py b/helpdesk/query.py index d186d147..914a92e2 100644 --- a/helpdesk/query.py +++ b/helpdesk/query.py @@ -4,6 +4,7 @@ from django.db.models import Q from django.urls import reverse from django.utils.html import escape from django.utils.translation import gettext as _ +from django.db.models import Max from helpdesk.serializers import DatatablesTicketSerializer import json from model_utils import Choices @@ -170,13 +171,14 @@ class __Query__: search_value = kwargs.get('search[value]', [""])[0] order_column = kwargs.get('order[0][column]', ['5'])[0] order = kwargs.get('order[0][dir]', ["asc"])[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) + queryset = queryset.annotate(last_followup=Max('followup__date')) total = queryset.count() if search_value: # Dead code currently diff --git a/helpdesk/serializers.py b/helpdesk/serializers.py index 8d899454..42453ba2 100644 --- a/helpdesk/serializers.py +++ b/helpdesk/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth import get_user_model from django.contrib.humanize.templatetags import humanize from django.utils.timezone import localtime +from django.core.exceptions import ObjectDoesNotExist from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -74,8 +75,13 @@ class DatatablesTicketSerializer(serializers.ModelSerializer): return obj.kbitem.title if obj.kbitem else "" def get_last_followup(self, obj): - followup = obj.followup_set.latest('date') - return localtime(followup.date).strftime('%Y-%m-%d %H:%M:%S') if followup else "" + try: + followup = obj.followup_set.latest('date') + followup = obj.followup_set.latest('date') + return localtime(followup.date).strftime('%Y-%m-%d %H:%M:%S') if followup else "" + except ObjectDoesNotExist: + return None # Return None or an empty string if no follow-ups exist + class FollowUpAttachmentSerializer(serializers.ModelSerializer): class Meta: diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 04babdda..4cf47c92 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -1168,7 +1168,6 @@ def datatables_ticket_list(request, query): """ query = Query(HelpdeskUser(request.user), base64query=query) result = query.get_datatables_context(**request.query_params) - print(result) return JsonResponse(result, status=status.HTTP_200_OK) From e5906184df56c7f47ea733f02e6a3846207145cd Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 12 Mar 2025 02:59:54 +0000 Subject: [PATCH 03/26] Removed filtering for last followup --- .../helpdesk/filters/followupdates.html | 20 ------------------- helpdesk/templates/helpdesk/ticket_list.html | 7 ------- 2 files changed, 27 deletions(-) delete mode 100644 helpdesk/templates/helpdesk/filters/followupdates.html diff --git a/helpdesk/templates/helpdesk/filters/followupdates.html b/helpdesk/templates/helpdesk/filters/followupdates.html deleted file mode 100644 index d8e9e8df..00000000 --- a/helpdesk/templates/helpdesk/filters/followupdates.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load i18n humanize %} -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - -

    {% trans "Use YYYY-MM-DD date format, eg 2018-01-30. This only searches when the last followup occured" %}

    -
    diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index 298c0cc5..ce686692 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -189,9 +189,6 @@ - @@ -228,10 +225,6 @@ id="filterBoxKBItems"> {% include 'helpdesk/filters/kbitems.html' %} -
  • - {% include 'helpdesk/filters/followupdates.html' %} -
  • From 81340d104949741ae8074890d64cc54a6103de88 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 12 Mar 2025 03:13:23 +0000 Subject: [PATCH 04/26] add last_followup to unit tests --- helpdesk/tests/test_query.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helpdesk/tests/test_query.py b/helpdesk/tests/test_query.py index f86faf55..7003f38f 100644 --- a/helpdesk/tests/test_query.py +++ b/helpdesk/tests/test_query.py @@ -100,12 +100,13 @@ class QueryTests(TestCase): response = self.client.get( reverse('helpdesk:datatables_ticket_list', args=[query])) resp_json = response.json() + print(resp_json) self.assertEqual( resp_json, { "data": [{"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": ""}], + "created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": ""}], "recordsFiltered": 1, "recordsTotal": 1, "draw": 0, From 8233c0fc45aad27b7c3fd0d35d67b2dbd4c9231b Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 12 Mar 2025 03:24:02 +0000 Subject: [PATCH 05/26] fx lib error and unit test query add followup --- helpdesk/lib.py | 1 - helpdesk/tests/test_query.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 03fe22fd..83f5b1a8 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -34,7 +34,6 @@ def ticket_template_context(ticket): else: context[field] = attr context['assigned_to'] = context['_get_assigned_to'] - context['last_followup'] = ticket.followups.latest('date') return context diff --git a/helpdesk/tests/test_query.py b/helpdesk/tests/test_query.py index 7003f38f..bd241df3 100644 --- a/helpdesk/tests/test_query.py +++ b/helpdesk/tests/test_query.py @@ -64,8 +64,8 @@ class QueryTests(TestCase): resp_json, { "data": - [{"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": ""}, - {"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open", "created": resp_json["data"][1]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], + [{"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, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": ""}, + {"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open", "created": resp_json["data"][1]["created"], "due_date": None, "assigned_to": "None", "submitter": None, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], "recordsFiltered": 2, "recordsTotal": 2, "draw": 0, @@ -85,7 +85,7 @@ class QueryTests(TestCase): { "data": [{"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned 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, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], "recordsFiltered": 1, "recordsTotal": 1, "draw": 0, @@ -100,7 +100,6 @@ class QueryTests(TestCase): response = self.client.get( reverse('helpdesk:datatables_ticket_list', args=[query])) resp_json = response.json() - print(resp_json) self.assertEqual( resp_json, { From f6277382aa6bd335efd5e5ea522f4033f2ca3018 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 19:26:00 +0000 Subject: [PATCH 06/26] query speedups in query and serializers --- helpdesk/query.py | 6 ++---- helpdesk/serializers.py | 17 ++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/helpdesk/query.py b/helpdesk/query.py index 914a92e2..7e1eb77b 100644 --- a/helpdesk/query.py +++ b/helpdesk/query.py @@ -1,10 +1,9 @@ from base64 import b64decode, b64encode -from django.db.models import Q +from django.db.models import Q, Max from django.urls import reverse from django.utils.html import escape from django.utils.translation import gettext as _ -from django.db.models import Max from helpdesk.serializers import DatatablesTicketSerializer import json from model_utils import Choices @@ -177,8 +176,7 @@ class __Query__: if order == 'desc': order_column = '-' + order_column - queryset = objects.all().order_by(order_by) - queryset = queryset.annotate(last_followup=Max('followup__date')) + queryset = objects.annotate(last_followup=Max('followup__date')).order_by(order_by) total = queryset.count() if search_value: # Dead code currently diff --git a/helpdesk/serializers.py b/helpdesk/serializers.py index 42453ba2..9c1e9303 100644 --- a/helpdesk/serializers.py +++ b/helpdesk/serializers.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from django.contrib.humanize.templatetags import humanize +from django.db.models import Max, F, Window from django.utils.timezone import localtime -from django.core.exceptions import ObjectDoesNotExist from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -75,12 +75,15 @@ class DatatablesTicketSerializer(serializers.ModelSerializer): return obj.kbitem.title if obj.kbitem else "" def get_last_followup(self, obj): - try: - followup = obj.followup_set.latest('date') - followup = obj.followup_set.latest('date') - return localtime(followup.date).strftime('%Y-%m-%d %H:%M:%S') if followup else "" - except ObjectDoesNotExist: - return None # Return None or an empty string if no follow-ups exist + followup = obj.followup_set.order_by().annotate( + last_followup=Window( + expression=Max("date"), + partition_by=[F("ticket_id"),], + order_by="-date" + ) + ).values("last_followup").distinct().values_list("last_followup", flat=True) + # If there are no followups for the ticket then the result will be empty + return localtime(followup[0]).strftime('%Y-%m-%d %H:%M:%S') if followup else "" class FollowUpAttachmentSerializer(serializers.ModelSerializer): From 57fe30ca00e6b0f6c0a76322fba26d975f733a7f Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 19:38:18 +0000 Subject: [PATCH 07/26] test fix: the field last_followup now defaults to an empty string instead of none --- helpdesk/tests/test_query.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helpdesk/tests/test_query.py b/helpdesk/tests/test_query.py index bd241df3..5ee8ef56 100644 --- a/helpdesk/tests/test_query.py +++ b/helpdesk/tests/test_query.py @@ -64,8 +64,8 @@ class QueryTests(TestCase): resp_json, { "data": - [{"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, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": ""}, - {"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open", "created": resp_json["data"][1]["created"], "due_date": None, "assigned_to": "None", "submitter": None, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], + [{"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, "last_followup": "", "row_class": "", "time_spent": "", "kbitem": ""}, + {"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open", "created": resp_json["data"][1]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "last_followup": "", "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], "recordsFiltered": 2, "recordsTotal": 2, "draw": 0, @@ -85,7 +85,7 @@ class QueryTests(TestCase): { "data": [{"ticket": "2 [test_queue-2]", "id": 2, "priority": 3, "title": "assigned to kbitem", "queue": {"title": "Test queue", "id": 1}, "status": "Open", - "created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], + "created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "last_followup": "", "row_class": "", "time_spent": "", "kbitem": "KBItem 1"}], "recordsFiltered": 1, "recordsTotal": 1, "draw": 0, @@ -105,7 +105,7 @@ class QueryTests(TestCase): { "data": [{"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, 'last_followup': None, "row_class": "", "time_spent": "", "kbitem": ""}], + "created": resp_json["data"][0]["created"], "due_date": None, "assigned_to": "None", "submitter": None, "last_followup": "", "row_class": "", "time_spent": "", "kbitem": ""}], "recordsFiltered": 1, "recordsTotal": 1, "draw": 0, From 753b053074c402ecb4a343747085b68fe2505a28 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:22:29 +0000 Subject: [PATCH 08/26] removing caching to see if new libs replicate test failures --- .github/workflows/pythonpackage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index a174ab2f..eb68f22f 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -21,7 +21,8 @@ jobs: - uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-testing.txt')}}-${{ hashFiles('tox.ini') }}-${{ matrix.python-version }}-${{ matrix.django-version }} + key: no-cache-${{ github.run_id }} + #key: ${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-testing.txt')}}-${{ hashFiles('tox.ini') }}-${{ matrix.python-version }}-${{ matrix.django-version }} - name: Install dependencies run: | From f128c79856f2c8e7ddd2722d64f7052419beecb4 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:24:58 +0000 Subject: [PATCH 09/26] removing 3.2 django builds from ci/cd --- .github/workflows/pythonpackage.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index eb68f22f..3a3719f8 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] - django-version: ["32","4"] + django-version: ["4"] steps: - uses: actions/checkout@v4 @@ -21,8 +21,7 @@ jobs: - uses: actions/cache@v4 with: path: ~/.cache/pip - key: no-cache-${{ github.run_id }} - #key: ${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-testing.txt')}}-${{ hashFiles('tox.ini') }}-${{ matrix.python-version }}-${{ matrix.django-version }} + key: ${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-testing.txt')}}-${{ hashFiles('tox.ini') }}-${{ matrix.python-version }}-${{ matrix.django-version }} - name: Install dependencies run: | From b2375381a1493c3ce6ac311f1888bd872897a681 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:32:46 +0000 Subject: [PATCH 10/26] Remove filter and uneeded render code --- helpdesk/templates/helpdesk/ticket_list.html | 10 +--------- helpdesk/views/staff.py | 2 -- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index ce686692..91e69c12 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -416,15 +416,7 @@ } }, {data: "submitter"}, - { - data: "last_followup", - render: function (data, type, row, meta) { - if (data && data !== "None") { - return data; - } - return ""; - } - }, + {data: "last_followup"}, {% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %} {data: "time_spent", "visible": false}, {% endif %} diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 4cf47c92..f19deae0 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -1025,14 +1025,12 @@ def ticket_list(request): ('assigned_to', 'assigned_to__id__in'), ('status', 'status__in'), ('kbitem', 'kbitem__in'), - ('last_followup', 'last_followup__in'), ] filter_null_params = dict([ ('queue', 'queue__id__isnull'), ('assigned_to', 'assigned_to__id__isnull'), ('status', 'status__isnull'), ('kbitem', 'kbitem__isnull'), - ('last_followup', 'last_followup__isnull'), ]) for param, filter_command in filter_in_params: if request.GET.get(param) is not None: From a939d2ff3d52ff43d0f1f301e68f23acdc6c7a34 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:42:26 +0000 Subject: [PATCH 11/26] humanize last_followup date --- helpdesk/templates/helpdesk/ticket_list.html | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index 91e69c12..2033487f 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -416,7 +416,23 @@ } }, {data: "submitter"}, - {data: "last_followup"}, + { + data: "last_followup", + render: function (data, type, row) { + if (isNaN(Date.parse(data))) return "No followup found"; + + let date = new Date(data); + return date.toLocaleString("en-US", { + weekday: "short", + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: true + }); + } + }, {% if helpdesk_settings.HELPDESK_ENABLE_TIME_SPENT_ON_TICKET %} {data: "time_spent", "visible": false}, {% endif %} From 0de74082a3c9244b81b8f786a6273a6d7d187eab Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:44:37 +0000 Subject: [PATCH 12/26] Try adding django 5 to ci/cd --- .github/workflows/pythonpackage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3a3719f8..28f2e30f 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] - django-version: ["4"] + django-version: ["4", "5"] steps: - uses: actions/checkout@v4 From 86752d57e04f1e2ea43acb08072d8dde7e8899a8 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:47:12 +0000 Subject: [PATCH 13/26] add constraints-Django5.txt --- constraints-Django5.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 constraints-Django5.txt diff --git a/constraints-Django5.txt b/constraints-Django5.txt new file mode 100644 index 00000000..a4769e29 --- /dev/null +++ b/constraints-Django5.txt @@ -0,0 +1 @@ +Django >=5,<6 From 1da3178a70dd8d84ad3028ebbf42a831e3efe510 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:53:08 +0000 Subject: [PATCH 14/26] swapping python 3.8 and 3.9 for 3.12 --- .github/workflows/pythonpackage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 28f2e30f..f4cf7f57 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] django-version: ["4", "5"] steps: - uses: actions/checkout@v4 From 1eae15bb0f6c144abddd1aa594f46ebf066d3704 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 22:57:23 +0000 Subject: [PATCH 15/26] include python 3.8/3.9 only with django 4 --- .github/workflows/pythonpackage.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index f4cf7f57..e21724a6 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,7 +8,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + include: + # Explicitly include Python 3.8 and 3.9 only with Django 4 + - python-version: "3.8" + django-version: "4" + - python-version: "3.9" + django-version: "4" + # Define the general matrix for Python with Django + python-version: ["3.10", "3.11"] django-version: ["4", "5"] steps: - uses: actions/checkout@v4 From fc53e317693dd2d9be5c5a0d689db47680ad58f3 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 23:01:23 +0000 Subject: [PATCH 16/26] fix indentation --- .github/workflows/pythonpackage.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index e21724a6..d6e5ce66 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,14 +8,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - include: - # Explicitly include Python 3.8 and 3.9 only with Django 4 - - python-version: "3.8" - django-version: "4" - - python-version: "3.9" - django-version: "4" - # Define the general matrix for Python with Django - python-version: ["3.10", "3.11"] + include: + # Explicitly include Python 3.8 and 3.9 only with Django 4 + - python-version: "3.8" + django-version: "4" + - python-version: "3.9" + django-version: "4" + # Define the general matrix for Python with Django + python-version: ["3.10", "3.11", "3.12"] django-version: ["4", "5"] steps: - uses: actions/checkout@v4 From acccec47a3e574ae46e9d1aac1a412d0076f7882 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 23:04:42 +0000 Subject: [PATCH 17/26] check if asserEqual is backward compatible --- helpdesk/tests/test_get_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/tests/test_get_email.py b/helpdesk/tests/test_get_email.py index bdd4274a..4f02ed96 100644 --- a/helpdesk/tests/test_get_email.py +++ b/helpdesk/tests/test_get_email.py @@ -280,7 +280,7 @@ class GetEmailCommonTests(TestCase): self.assertTrue(att_retrieved.filename.endswith(att_filename), "Filename of attached multipart not detected: %s" % (att_retrieved.filename)) with att_retrieved.file.open('r') as f: retrieved_content = f.read() - self.assertEquals(att_content, retrieved_content, "Retrieved attachment content different to original :\n\n%s\n\n%s" % (att_content, retrieved_content)) + self.assertEqual(att_content, retrieved_content, "Retrieved attachment content different to original :\n\n%s\n\n%s" % (att_content, retrieved_content)) def test_email_with_inline_and_multipart_as_attachments(self): """ From 8c27f74605da1addf7bcd73c834a539995f87056 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Tue, 18 Mar 2025 23:16:27 +0000 Subject: [PATCH 18/26] back off ci/dc for django 5 --- .github/workflows/pythonpackage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index d6e5ce66..444b9e25 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -16,7 +16,7 @@ jobs: django-version: "4" # Define the general matrix for Python with Django python-version: ["3.10", "3.11", "3.12"] - django-version: ["4", "5"] + django-version: ["4"] steps: - uses: actions/checkout@v4 From 01c28a4d629804c44f2d60cc80eafd83d23695c5 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 19 Mar 2025 23:09:54 +0000 Subject: [PATCH 19/26] faster query for followup_date --- helpdesk/query.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/helpdesk/query.py b/helpdesk/query.py index 7e1eb77b..ea69a133 100644 --- a/helpdesk/query.py +++ b/helpdesk/query.py @@ -1,6 +1,8 @@ from base64 import b64decode, b64encode from django.db.models import Q, Max +from django.db.models import F, Window, Subquery, OuterRef +from .models import FollowUp from django.urls import reverse from django.utils.html import escape from django.utils.translation import gettext as _ @@ -175,8 +177,21 @@ class __Query__: # django orm '-' -> desc if order == 'desc': order_column = '-' + order_column - - queryset = objects.annotate(last_followup=Max('followup__date')).order_by(order_by) + + queryset = objects.annotate( + last_followup=Subquery( + FollowUp.objects.order_by().annotate( + last_followup=Window( + expression=Max("date"), + partition_by=[F("ticket_id"),], + order_by="-date" + ) + ).filter( + ticket_id=OuterRef("id") + ).values("last_followup").distinct() + ) + ).order_by(order_by) + total = queryset.count() if search_value: # Dead code currently From 9c8ca60f3cf94e87d3c5b745a3bc734f1d91bce4 Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Wed, 19 Mar 2025 23:29:43 +0000 Subject: [PATCH 20/26] Use locale for last_followup --- helpdesk/templates/helpdesk/ticket_list.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index 2033487f..e4b9c631 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -419,10 +419,15 @@ { data: "last_followup", render: function (data, type, row) { - if (isNaN(Date.parse(data))) return "No followup found"; + let locale = window.django_settings ? window.django_settings.LANGUAGE_CODE : 'en-US'; // Default to en-US if settings are not found + + if (isNaN(Date.parse(data))) { + let noFollowup = window.django_settings && window.django_settings.TRANSLATIONS && window.django_settings.TRANSLATIONS['No followup found'] ? window.django_settings.TRANSLATIONS['No followup found'] : "No followup found"; + return noFollowup; + } let date = new Date(data); - return date.toLocaleString("en-US", { + return date.toLocaleString(locale, { weekday: "short", year: "numeric", month: "long", From cf2fb41bc71e0ee67cd6a3cac179c29df6db1eea Mon Sep 17 00:00:00 2001 From: DavidVadnais Date: Thu, 20 Mar 2025 00:04:05 +0000 Subject: [PATCH 21/26] Actual field parsing for last_followup --- helpdesk/templates/helpdesk/ticket_list.html | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index e4b9c631..bb9fe19b 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -331,7 +331,13 @@ {% else %} {% endif %} +