diff --git a/helpdesk/email.py b/helpdesk/email.py index 2e076585..483d96b1 100644 --- a/helpdesk/email.py +++ b/helpdesk/email.py @@ -471,9 +471,12 @@ def object_from_message(message, queue, logger): if part.get_content_maintype() == 'text' and name is None: if part.get_content_subtype() == 'plain': - body = EmailReplyParser.parse_reply( - decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) - ) + body = part.get_payload(decode=True) + # https://github.com/django-helpdesk/django-helpdesk/issues/732 + if part['Content-Transfer-Encoding'] == '8bit' and part.get_content_charset() == 'utf-8': + body = body.decode('unicode_escape') + body = decodeUnknown(part.get_content_charset(), body) + body = EmailReplyParser.parse_reply(body) # workaround to get unicode text out rather than escaped text try: body = body.encode('ascii').decode('unicode_escape') @@ -481,9 +484,15 @@ def object_from_message(message, queue, logger): body.encode('utf-8') logger.debug("Discovered plain text MIME part") else: - payload = encoding.smart_bytes(part.get_payload(decode=True)) + payload = """ + + + + +%s +""" % encoding.smart_text(part.get_payload(decode=True)) files.append( - SimpleUploadedFile(_("email_html_body.html"), payload, 'text/html') + SimpleUploadedFile(_("email_html_body.html"), payload.encode("utf-8"), 'text/html') ) logger.debug("Discovered HTML MIME part") else: diff --git a/helpdesk/models.py b/helpdesk/models.py index 73b29f06..6264cf84 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -748,6 +748,10 @@ class Ticket(models.Model): def get_markdown(self): return get_markdown(self.description) + @property + def get_resolution_markdown(self): + return get_markdown(self.resolution) + class FollowUpManager(models.Manager): diff --git a/helpdesk/serializers.py b/helpdesk/serializers.py index abd95581..bacd96cc 100644 --- a/helpdesk/serializers.py +++ b/helpdesk/serializers.py @@ -19,6 +19,7 @@ class TicketSerializer(serializers.ModelSerializer): status = serializers.SerializerMethodField() row_class = serializers.SerializerMethodField() time_spent = serializers.SerializerMethodField() + queue = serializers.SerializerMethodField() class Meta: model = Ticket @@ -27,6 +28,9 @@ class TicketSerializer(serializers.ModelSerializer): 'created', 'due_date', 'assigned_to', 'row_class', 'time_spent') + def get_queue(self, obj): + return ({"title": obj.queue.title, "id": obj.queue.id}) + def get_ticket(self, obj): return (str(obj.id) + " " + obj.ticket) diff --git a/helpdesk/static/helpdesk/helpdesk-extend.css b/helpdesk/static/helpdesk/helpdesk-extend.css index 4fa6c7dd..8b2e5c69 100644 --- a/helpdesk/static/helpdesk/helpdesk-extend.css +++ b/helpdesk/static/helpdesk/helpdesk-extend.css @@ -87,3 +87,11 @@ pre { padding: 1em; border: 1pt solid white; } + +table .tickettitle { + max-width: 250px; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/helpdesk/templates/helpdesk/include/tickets.html b/helpdesk/templates/helpdesk/include/tickets.html index 745b246b..bc0effd0 100644 --- a/helpdesk/templates/helpdesk/include/tickets.html +++ b/helpdesk/templates/helpdesk/include/tickets.html @@ -8,12 +8,11 @@
- - +
+ - - - + + @@ -22,9 +21,8 @@ {% for ticket in ticket_list %} - + - diff --git a/helpdesk/templates/helpdesk/include/unassigned.html b/helpdesk/templates/helpdesk/include/unassigned.html index e13bcb79..9632ab36 100644 --- a/helpdesk/templates/helpdesk/include/unassigned.html +++ b/helpdesk/templates/helpdesk/include/unassigned.html @@ -8,28 +8,26 @@
-
#{% trans "Pr" %}{% trans "Title" %}{% trans "Ticket" %}{% trans "Priority" %} {% trans "Queue" %} {% trans "Status" %} {% trans "Last Update" %}
{{ ticket.ticket }}{{ ticket.id }}. {{ ticket.title }} {{ ticket.priority }}{{ ticket.title }} {{ ticket.queue }} {{ ticket.get_status }} {{ ticket.modified|naturaltime }}
- +
+ - - - + + - - + + {% for ticket in unassigned_tickets %} - + - - {% empty %} diff --git a/helpdesk/templates/helpdesk/kb_category.html b/helpdesk/templates/helpdesk/kb_category.html index f47d0f9f..c34ea65c 100644 --- a/helpdesk/templates/helpdesk/kb_category.html +++ b/helpdesk/templates/helpdesk/kb_category.html @@ -24,12 +24,13 @@ {% cycle 'one' 'two' 'three' as itemnumperrow silent %} {% ifequal itemnumperrow 'one' %}
{% endifequal %}
-
+
{{ item.title }}
+
+

{{ item.question }}

-

- {% blocktrans with item.get_absolute_url as url %}View Answer {% endblocktrans %} + {% blocktrans with item.get_absolute_url as url %} Go to answer {% endblocktrans %}

{% trans 'Rating' %}: {{ item.score }}

diff --git a/helpdesk/templates/helpdesk/kb_index.html b/helpdesk/templates/helpdesk/kb_index.html index 7fb96f03..2516dbbc 100644 --- a/helpdesk/templates/helpdesk/kb_index.html +++ b/helpdesk/templates/helpdesk/kb_index.html @@ -18,10 +18,12 @@ {% cycle 'one' 'two' 'three' as catnumperrow silent %} {% if catnumperrow == 'one' %}
{% endif %}
+
+
{{ category.title }}
+
-
{{ category.title }}

{{ category.description }}

-

{% trans 'View articles' %}

+

{% trans 'View articles' %}

{% if catnumperrow == 'three' %}
{% endif %} diff --git a/helpdesk/templates/helpdesk/kb_item.html b/helpdesk/templates/helpdesk/kb_item.html index bdd8457b..c75c0777 100644 --- a/helpdesk/templates/helpdesk/kb_item.html +++ b/helpdesk/templates/helpdesk/kb_item.html @@ -11,7 +11,7 @@ {% endblock %} {% block helpdesk_body %} -

{% trans 'Knowledgebase' %}:{% blocktrans with item.title as item %}{{ item }}{% endblocktrans %}

+

{% trans 'Knowledgebase' %}: {% blocktrans with item.title as item %}{{ item }}{% endblocktrans %}

diff --git a/helpdesk/templates/helpdesk/ticket_desc_table.html b/helpdesk/templates/helpdesk/ticket_desc_table.html index af619690..2725753c 100644 --- a/helpdesk/templates/helpdesk/ticket_desc_table.html +++ b/helpdesk/templates/helpdesk/ticket_desc_table.html @@ -33,7 +33,7 @@
- + {% endif %} diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html index 46db483e..2398e6c9 100644 --- a/helpdesk/templates/helpdesk/ticket_list.html +++ b/helpdesk/templates/helpdesk/ticket_list.html @@ -41,7 +41,7 @@
- @@ -51,7 +51,9 @@
- @@ -59,10 +61,13 @@ - + {% trans "Add another filter to list of filers for finer selection of displayed tickets" %} + {% csrf_token %} +
+
+ {% if from_saved_query and saved_query.user == user %}

{% blocktrans with saved_query.title as query_name %}You are currently viewing saved query "{{ query_name }}".{% endblocktrans %} {% trans "Delete Saved Query" %}

{% endif %} {% if from_saved_query %}

{% blocktrans with saved_query.id as query_id %}Run a report on this query to see stats and charts for the data listed below.{% endblocktrans %}

{% endif %} - {% csrf_token %} + {% csrf_token %} +
@@ -144,7 +151,7 @@
- @@ -175,7 +182,7 @@
- @@ -210,13 +217,12 @@
{{ search_message|safe }}
-
#{% trans "Pr" %}{% trans "Title" %}{% trans "Ticket" %}{% trans "Prority" %} {% trans "Queue" %}{% trans "Status" %} {% trans "Created" %}{% trans "Actions" %}
{{ ticket.ticket }}{{ ticket.id }}. {{ ticket.title }} {{ ticket.priority }}{{ ticket.title }} {{ ticket.queue }} {{ ticket.created|naturaltime }} - | - + + +
{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} {% endifequal %}
{{ ticket.resolution|force_escape|urlizetrunc:50|linebreaksbr }}{{ ticket.get_resolution_markdown|urlizetrunc:50|linebreaksbr }}
{% trans "Due Date" %}
- +
+ - - - + + @@ -301,18 +307,6 @@ }, "columns": [ - {"data": "ticket", - "render": function (data, type, row, meta) - { - var id = data.split(" ")[0]; - var name = data.split(" ")[1]; - if (type === 'display') - { - data = '' + name + ''; - } - return data - } - }, {"data": "id", "orderable": false, "render": function(data, type, row, meta) @@ -324,22 +318,49 @@ return data } }, - {"data": "priority"}, - {"data": "title", + {"data": "ticket", "render": function (data, type, row, meta) { - if (type === 'display') - { - data = '' + data + ''; - } - return data + var id = data.split(" ")[0]; + var name = data.split(" ")[1]; + if (type === 'display') + { + data = ''; + } + return data } }, - {"data": "queue"}, + {"data": "priority", + "render": function (data, type, row, meta) { + var priority = "success"; + if (data == 4 ) { + priority = "warning"; + } else if (data == 5) { + priority = "danger"; + } + return '

'+data+'

'; + } + }, + {"data": "queue", + "render": function(data, type, row, meta) { + return data.title; + } + }, {"data": "status"}, {"data": "created"}, {"data": "due_date"}, - {"data": "assigned_to"}, + {"data": "assigned_to", + "render": function(data, type, row, meta) { + if (data != "None") { + return data; + } + else { + return ""; + } + } + }, {"data": "time_spent"}, ] }); diff --git a/helpdesk/templates/helpdesk/ticket_list_table.html b/helpdesk/templates/helpdesk/ticket_list_table.html index fa4b9d69..f12c39e2 100644 --- a/helpdesk/templates/helpdesk/ticket_list_table.html +++ b/helpdesk/templates/helpdesk/ticket_list_table.html @@ -5,7 +5,7 @@ - + diff --git a/helpdesk/tests/test_get_email.py b/helpdesk/tests/test_get_email.py index 76184ec1..50a5f1e5 100644 --- a/helpdesk/tests/test_get_email.py +++ b/helpdesk/tests/test_get_email.py @@ -53,7 +53,7 @@ class GetEmailCommonTests(TestCase): with open(os.path.join(THIS_DIR, "test_files/blank-body-with-attachment.eml")) as fd: test_email = fd.read() ticket = helpdesk.email.object_from_message(test_email, self.queue_public, self.logger) - self.assertEqual(ticket.title, "FollowUpAttachment without body") + self.assertEqual(ticket.title, "Attachment without body") self.assertEqual(ticket.description, "") def test_email_with_quoted_printable_body(self): @@ -71,7 +71,7 @@ class GetEmailCommonTests(TestCase): attachments = FollowUpAttachment.objects.filter(followup=followup) self.assertEqual(len(attachments), 1) attachment = attachments[0] - self.assertEqual(attachment.file.read().decode("utf-8"), '
Tohle je test českých písmen odeslaných z gmailu.
\n') + self.assertIn('
Tohle je test českých písmen odeslaných z gmailu.
\n', attachment.file.read().decode("utf-8")) def test_email_with_8bit_encoding_and_utf_8(self): """ diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index b09d8cba..e8afc01e 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -138,13 +138,15 @@ def dashboard(request): showing ticket counts by queue/status, and a list of unassigned tickets with options for them to 'Take' ownership of said tickets. """ - # open & reopened tickets, assigned to current user - tickets = Ticket.objects.select_related('queue').filter( - assigned_to=request.user, - ).exclude( + active_tickets = Ticket.objects.select_related('queue').exclude( status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS], ) + # open & reopened tickets, assigned to current user + tickets = active_tickets.filter( + assigned_to=request.user, + ) + # closed & resolved tickets, assigned to current user tickets_closed_resolved = Ticket.objects.select_related('queue').filter( assigned_to=request.user, @@ -152,11 +154,9 @@ def dashboard(request): user_queues = _get_user_queues(request.user) - unassigned_tickets = Ticket.objects.select_related('queue').filter( + unassigned_tickets = active_tickets.filter( assigned_to__isnull=True, queue__in=user_queues - ).exclude( - status=Ticket.CLOSED_STATUS, ) # all tickets, reported by current user @@ -269,7 +269,7 @@ def followup_edit(request, ticket_id, followup_id): new_followup.user = followup.user new_followup.save() # get list of old attachments & link them to new_followup - attachments = FolllowUpAttachment.objects.filter(followup=followup) + attachments = FollowUpAttachment.objects.filter(followup=followup) for attachment in attachments: attachment.followup = new_followup attachment.save() @@ -1581,7 +1581,7 @@ def attachment_del(request, ticket_id, attachment_id): if not _is_my_ticket(request.user, ticket): raise PermissionDenied() - attachment = get_object_or_404(FolllowUpAttachment, id=attachment_id) + attachment = get_object_or_404(FollowUpAttachment, id=attachment_id) if request.method == 'POST': attachment.delete() return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id])) diff --git a/quicktest.py b/quicktest.py index 85eb9c41..c7912124 100644 --- a/quicktest.py +++ b/quicktest.py @@ -27,7 +27,8 @@ class QuickDjangoTest(object): 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.staticfiles', - 'bootstrap4form' + 'bootstrap4form', + 'helpdesk', ) MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', @@ -62,7 +63,7 @@ class QuickDjangoTest(object): def __init__(self, *args, **kwargs): - self.apps = args + self.tests = args self._tests() def _tests(self): @@ -79,7 +80,7 @@ class QuickDjangoTest(object): 'PORT': '', } }, - INSTALLED_APPS=self.INSTALLED_APPS + self.apps, + INSTALLED_APPS=self.INSTALLED_APPS, MIDDLEWARE=self.MIDDLEWARE, ROOT_URLCONF='helpdesk.tests.urls', STATIC_URL='/static/', @@ -92,7 +93,7 @@ class QuickDjangoTest(object): test_runner = DiscoverRunner(verbosity=1) django.setup() - failures = test_runner.run_tests(self.apps) + failures = test_runner.run_tests(self.tests) if failures: sys.exit(failures) @@ -102,13 +103,15 @@ if __name__ == '__main__': Example usage: - $ python quicktest.py app1 app2 + $ python quicktest.py test1 test2 """ parser = argparse.ArgumentParser( usage="[args]", - description="Run Django tests on the provided applications." + description="Run Django tests." ) - parser.add_argument('apps', nargs='+', type=str) + parser.add_argument('tests', nargs="*", type=str) args = parser.parse_args() - QuickDjangoTest(*args.apps) + if not args.tests: + args.tests = ['helpdesk'] + QuickDjangoTest(*args.tests)
#  {% trans "Pr" %}{% trans "Title" %}{% trans "Ticket" %}{% trans "Prority" %} {% trans "Queue" %} {% trans "Status" %} {% trans "Created" %}
{{ ticket.ticket }} {{ ticket.priority }}{{ ticket.priority }}||||| {{ ticket.title }} {{ ticket.queue }} {{ ticket.get_status }}