mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2025-06-20 01:27:44 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
2e9208116b
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
|||||||
Copyright (c) 2008 Ross Poulton (Trading as Jutda),
|
Copyright (c) 2008 Ross Poulton (Trading as Jutda),
|
||||||
Copyright (c) 2008-2019 django-helpdesk contributors.
|
Copyright (c) 2008-2020 django-helpdesk contributors.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
5
Makefile
5
Makefile
@ -78,8 +78,9 @@ readme:
|
|||||||
#: demo - Setup demo project using Python3.
|
#: demo - Setup demo project using Python3.
|
||||||
.PHONY: demo
|
.PHONY: demo
|
||||||
demo:
|
demo:
|
||||||
$(PIP) install -e . --user
|
# running it with and without --user flag because it started to be problematic for some setups
|
||||||
$(PIP) install -e demo --user
|
$(PIP) install -e . --user || $(PIP) install -e .
|
||||||
|
$(PIP) install -e demo --user || $(PIP) install -e demo
|
||||||
demodesk migrate --noinput
|
demodesk migrate --noinput
|
||||||
# Create superuser; user will be prompted to manually set a password
|
# Create superuser; user will be prompted to manually set a password
|
||||||
# When you get a prompt, enter a password of your choosing.
|
# When you get a prompt, enter a password of your choosing.
|
||||||
|
@ -38,7 +38,12 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
'bootstrap4form',
|
'bootstrap4form',
|
||||||
'helpdesk'
|
|
||||||
|
'account', # Required by pinax-teams
|
||||||
|
'pinax.invitations', # required by pinax-teams
|
||||||
|
'pinax.teams', # team support
|
||||||
|
'helpdesk', # This is us!
|
||||||
|
'reversion', # required by pinax-teams
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -19,9 +19,9 @@ AUTHOR = 'django-helpdesk team'
|
|||||||
URL = 'https://github.com/django-helpdesk/django-helpdesk'
|
URL = 'https://github.com/django-helpdesk/django-helpdesk'
|
||||||
CLASSIFIERS = ['Development Status :: 4 - Beta',
|
CLASSIFIERS = ['Development Status :: 4 - Beta',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Programming Language :: Python :: 3.5',
|
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
'Framework :: Django :: 2.0',
|
'Framework :: Django :: 2.0',
|
||||||
'Framework :: Django :: 2.1',
|
'Framework :: Django :: 2.1',
|
||||||
'Framework :: Django :: 2.2']
|
'Framework :: Django :: 2.2']
|
||||||
|
@ -21,6 +21,8 @@ Before django-helpdesk will be much use, you need to do some basic configuration
|
|||||||
|
|
||||||
If you wish to use `celery` instead of cron, you must add 'django_celery_beat' to `INSTALLED_APPS` and add a periodic celery task through the Django admin.
|
If you wish to use `celery` instead of cron, you must add 'django_celery_beat' to `INSTALLED_APPS` and add a periodic celery task through the Django admin.
|
||||||
|
|
||||||
|
You will need to create a support queue, and associated login/host values, in the Django admin interface, in order for mail to be picked-up from the mail server and placed in the tickets table of your database. The values in the settings file alone, will not create the necessary values to trigger the get_email function.
|
||||||
|
|
||||||
4. If you wish to automatically escalate tickets based on their age, set up a cronjob to run the escalation command on a regular basis::
|
4. If you wish to automatically escalate tickets based on their age, set up a cronjob to run the escalation command on a regular basis::
|
||||||
|
|
||||||
0 * * * * /path/to/helpdesksite/manage.py escalate_tickets
|
0 * * * * /path/to/helpdesksite/manage.py escalate_tickets
|
||||||
|
@ -101,11 +101,11 @@ errors with trying to create User settings.
|
|||||||
|
|
||||||
(substitute www-data for the user / group that your web server runs as, eg 'apache' or 'httpd')
|
(substitute www-data for the user / group that your web server runs as, eg 'apache' or 'httpd')
|
||||||
|
|
||||||
If all else fails ensure all users can write to it::
|
If all else fails, you could ensure all users can write to it::
|
||||||
|
|
||||||
chmod 777 attachments/
|
chmod 777 attachments/
|
||||||
|
|
||||||
This is NOT recommended, especially if you're on a shared server.
|
But this is NOT recommended, especially if you're on a shared server.
|
||||||
|
|
||||||
6. Ensure that your ``attachments`` folder has directory listings turned off, to ensure users don't download files that they are not specifically linked to from their tickets.
|
6. Ensure that your ``attachments`` folder has directory listings turned off, to ensure users don't download files that they are not specifically linked to from their tickets.
|
||||||
|
|
||||||
|
@ -82,6 +82,10 @@ These changes are visible throughout django-helpdesk
|
|||||||
|
|
||||||
**Default:** ``HELPDESK_EMAIL_FALLBACK_LOCALE = "en"``
|
**Default:** ``HELPDESK_EMAIL_FALLBACK_LOCALE = "en"``
|
||||||
|
|
||||||
|
- **HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE** Maximum size, in bytes, of file attachments that will be sent via email
|
||||||
|
|
||||||
|
**Default:** ``HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE = 512000``
|
||||||
|
|
||||||
- **QUEUE_EMAIL_BOX_UPDATE_ONLY** Only process mail with a valid tracking ID; all other mail will be ignored instead of creating a new ticket.
|
- **QUEUE_EMAIL_BOX_UPDATE_ONLY** Only process mail with a valid tracking ID; all other mail will be ignored instead of creating a new ticket.
|
||||||
|
|
||||||
**Default:** ``QUEUE_EMAIL_BOX_UPDATE_ONLY = False``
|
**Default:** ``QUEUE_EMAIL_BOX_UPDATE_ONLY = False``
|
||||||
|
@ -74,6 +74,8 @@ def process_email(quiet=False):
|
|||||||
if quiet:
|
if quiet:
|
||||||
logger.propagate = False # do not propagate to root logger that would log to console
|
logger.propagate = False # do not propagate to root logger that would log to console
|
||||||
logdir = q.logging_dir or '/var/log/helpdesk/'
|
logdir = q.logging_dir or '/var/log/helpdesk/'
|
||||||
|
|
||||||
|
try:
|
||||||
handler = logging.FileHandler(join(logdir, q.slug + '_get_email.log'))
|
handler = logging.FileHandler(join(logdir, q.slug + '_get_email.log'))
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
@ -86,6 +88,15 @@ def process_email(quiet=False):
|
|||||||
process_queue(q, logger=logger)
|
process_queue(q, logger=logger)
|
||||||
q.email_box_last_check = timezone.now()
|
q.email_box_last_check = timezone.now()
|
||||||
q.save()
|
q.save()
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
handler.close()
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
try:
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
|
||||||
|
|
||||||
def pop3_sync(q, logger, server):
|
def pop3_sync(q, logger, server):
|
||||||
@ -428,7 +439,13 @@ def object_from_message(message, queue, logger):
|
|||||||
|
|
||||||
sender = message.get('from', _('Unknown Sender'))
|
sender = message.get('from', _('Unknown Sender'))
|
||||||
sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
|
sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
|
||||||
sender_email = email.utils.parseaddr(sender)[1]
|
# to address bug #832, we wrap all the text in front of the email address in
|
||||||
|
# double quotes by using replace() on the email string. Then,
|
||||||
|
# take first item of list, second item of tuple is the actual email address.
|
||||||
|
# Note that the replace won't work on just an email with no real name,
|
||||||
|
# but the getaddresses() function seems to be able to handle just unclosed quotes
|
||||||
|
# correctly. Not ideal, but this seems to work for now.
|
||||||
|
sender_email = email.utils.getaddresses(['\"' + sender.replace('<', '\" <')])[0][1]
|
||||||
|
|
||||||
body_plain, body_html = '', ''
|
body_plain, body_html = '', ''
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ def text_is_spam(text, request):
|
|||||||
|
|
||||||
|
|
||||||
def process_attachments(followup, attached_files):
|
def process_attachments(followup, attached_files):
|
||||||
max_email_attachment_size = getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000)
|
max_email_attachment_size = getattr(settings, 'HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE', 512000)
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
for attached in attached_files:
|
for attached in attached_files:
|
||||||
@ -149,7 +149,7 @@ def process_attachments(followup, attached_files):
|
|||||||
|
|
||||||
if attached.size < max_email_attachment_size:
|
if attached.size < max_email_attachment_size:
|
||||||
# Only files smaller than 512kb (or as defined in
|
# Only files smaller than 512kb (or as defined in
|
||||||
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
|
# settings.HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
|
||||||
attachments.append([filename, att.file])
|
attachments.append([filename, att.file])
|
||||||
|
|
||||||
return attachments
|
return attachments
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1334,8 +1334,9 @@ class KBItem(models.Model):
|
|||||||
return super(KBItem, self).save(*args, **kwargs)
|
return super(KBItem, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def _score(self):
|
def _score(self):
|
||||||
|
""" Return a score out of 10 or Unrated if no votes """
|
||||||
if self.votes > 0:
|
if self.votes > 0:
|
||||||
return int(self.recommendations / self.votes)
|
return (self.recommendations / self.votes) * 10
|
||||||
else:
|
else:
|
||||||
return _('Unrated')
|
return _('Unrated')
|
||||||
score = property(_score)
|
score = property(_score)
|
||||||
|
@ -121,6 +121,10 @@ if HELPDESK_EMAIL_SUBJECT_TEMPLATE.find("ticket.ticket") < 0:
|
|||||||
# default fallback locale when queue locale not found
|
# default fallback locale when queue locale not found
|
||||||
HELPDESK_EMAIL_FALLBACK_LOCALE = getattr(settings, 'HELPDESK_EMAIL_FALLBACK_LOCALE', 'en')
|
HELPDESK_EMAIL_FALLBACK_LOCALE = getattr(settings, 'HELPDESK_EMAIL_FALLBACK_LOCALE', 'en')
|
||||||
|
|
||||||
|
# default maximum email attachment size, in bytes
|
||||||
|
# only attachments smaller than this size will be sent via email
|
||||||
|
HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE = getattr(settings, 'HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE', 512000)
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# options for staff.create_ticket view #
|
# options for staff.create_ticket view #
|
||||||
|
@ -24,18 +24,21 @@
|
|||||||
|
|
||||||
{% if all_tickets_reported_by_current_user %}
|
{% if all_tickets_reported_by_current_user %}
|
||||||
{% trans "All Tickets submitted by you" as ticket_list_caption %}
|
{% trans "All Tickets submitted by you" as ticket_list_caption %}
|
||||||
{% include 'helpdesk/include/tickets.html' with ticket_list=all_tickets_reported_by_current_user ticket_list_empty_message="" %}
|
{% trans "atrbcu_page" as page_var %}
|
||||||
|
{% include 'helpdesk/include/tickets.html' with ticket_list=all_tickets_reported_by_current_user ticket_list_empty_message="" page_var=page_var %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% trans "Open Tickets assigned to you (you are working on this ticket)" as ticket_list_caption %}
|
{% trans "Open Tickets assigned to you (you are working on this ticket)" as ticket_list_caption %}
|
||||||
{% trans "You have no tickets assigned to you." as no_assigned_tickets %}
|
{% trans "You have no tickets assigned to you." as no_assigned_tickets %}
|
||||||
{% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets ticket_list_empty_message=no_assigned_tickets %}
|
{% trans "ut_page" as page_var %}
|
||||||
|
{% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets ticket_list_empty_message=no_assigned_tickets page_var=page_var %}
|
||||||
|
|
||||||
{% include 'helpdesk/include/unassigned.html' %}
|
{% include 'helpdesk/include/unassigned.html' %}
|
||||||
|
|
||||||
{% if user_tickets_closed_resolved %}
|
{% if user_tickets_closed_resolved %}
|
||||||
{% trans "Closed & resolved Tickets you used to work on" as ticket_list_caption %}
|
{% trans "Closed & resolved Tickets you used to work on" as ticket_list_caption %}
|
||||||
{% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets_closed_resolved ticket_list_empty_message="" %}
|
{% trans "utcr_page" as page_var %}
|
||||||
|
{% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets_closed_resolved ticket_list_empty_message="" page_var=page_var %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<div class='buttons form-group'>
|
<div class='buttons form-group'>
|
||||||
<input type='submit' class="btn btn-primary btn-sm" value='{% trans "Save Changes" %}' />
|
<input type='submit' class="btn btn-primary btn-sm" value='{% trans "Save Changes" %}' />
|
||||||
|
<a href='{{ ticket.get_absolute_url }}'><button class="btn btn-danger">{% trans "Cancel Changes" %}</button></a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% load i18n humanize %}
|
{% load i18n humanize %}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
<!-- DataTables Example -->
|
<!-- DataTables Example -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -33,6 +34,37 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- /.table-responsive -->
|
||||||
|
{% if ticket_list.has_other_pages %}
|
||||||
|
<ul class="pagination">
|
||||||
|
<!-- if we aren't on page one, go back to start and go back one controls -->
|
||||||
|
{% if ticket_list.has_previous %}
|
||||||
|
<li><a href="?{{ page_var }}=1">««</a></li>
|
||||||
|
<li><a href="?{{ page_var }}={{ ticket_list.previous_page_number }}">«</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="disabled"><span>««</span></li>
|
||||||
|
<li class="disabled"><span>«</span></li>
|
||||||
|
{% endif %}
|
||||||
|
<!-- other pages, set thresh to the number to show before and after active -->
|
||||||
|
{% with 5 as thresh %}
|
||||||
|
{% for i in ticket_list.paginator.page_range %}
|
||||||
|
{% if ticket_list.number == i %}
|
||||||
|
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
|
||||||
|
{% elif i <= ticket_list.number|add:5 and i >= ticket_list.number|add:-5 %}
|
||||||
|
<li><a href="?{{ page_var }}={{ i }}">{{ i }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
<!-- if we aren't on the last page, go forward one and go to end controls -->
|
||||||
|
{% if ticket_list.has_next %}
|
||||||
|
<li><a href="?{{ page_var }}={{ ticket_list.next_page_number }}">»</a></li>
|
||||||
|
<li><a href="?{{ page_var }}={{ ticket_list.paginator.num_pages }}">»»</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="disabled"><span>»</span></li>
|
||||||
|
<li class="disabled"><span>»»</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer small text-muted">Listing {{ ticket_list|length }} ticket(s).</div>
|
<div class="card-footer small text-muted">Listing {{ ticket_list|length }} ticket(s).</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
{% block helpdesk_title %}{% trans "Tickets" %}{% endblock %}
|
{% block helpdesk_title %}{% trans "Tickets" %}{% endblock %}
|
||||||
|
|
||||||
{% block helpdesk_head %}
|
{% block helpdesk_head %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,6 +205,79 @@ class GetEmailParametricTemplate(object):
|
|||||||
self.assertEqual(ticket2.title, test_email_subject)
|
self.assertEqual(ticket2.title, test_email_subject)
|
||||||
self.assertEqual(ticket2.description, test_email_body)
|
self.assertEqual(ticket2.description, test_email_body)
|
||||||
|
|
||||||
|
def test_commas_in_mail_headers(self):
|
||||||
|
"""Tests correctly decoding mail headers when a comma is encoded into
|
||||||
|
UTF-8. See bug report #832."""
|
||||||
|
|
||||||
|
# example email text from Django docs: https://docs.djangoproject.com/en/1.10/ref/unicode/
|
||||||
|
test_email_from = "Bernard-Bouissières, Benjamin <bbb@example.com>"
|
||||||
|
test_email_subject = "Commas in From lines"
|
||||||
|
test_email_body = "Testing commas in from email UTF-8."
|
||||||
|
test_email = "To: helpdesk@example.com\nFrom: " + test_email_from + "\nSubject: " + test_email_subject + "\n\n" + test_email_body
|
||||||
|
test_mail_len = len(test_email)
|
||||||
|
|
||||||
|
if self.socks:
|
||||||
|
from socks import ProxyConnectionError
|
||||||
|
with self.assertRaisesRegex(ProxyConnectionError, '%s:%s' % (unrouted_socks_server, unused_port)):
|
||||||
|
call_command('get_email')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Test local email reading
|
||||||
|
if self.method == 'local':
|
||||||
|
with mock.patch('helpdesk.management.commands.get_email.listdir') as mocked_listdir, \
|
||||||
|
mock.patch('helpdesk.management.commands.get_email.isfile') as mocked_isfile, \
|
||||||
|
mock.patch('builtins.open' if six.PY3 else '__builtin__.open', mock.mock_open(read_data=test_email)):
|
||||||
|
mocked_isfile.return_value = True
|
||||||
|
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||||
|
|
||||||
|
call_command('get_email')
|
||||||
|
|
||||||
|
mocked_listdir.assert_called_with('/var/lib/mail/helpdesk/')
|
||||||
|
mocked_isfile.assert_any_call('/var/lib/mail/helpdesk/filename1')
|
||||||
|
mocked_isfile.assert_any_call('/var/lib/mail/helpdesk/filename2')
|
||||||
|
|
||||||
|
elif self.method == 'pop3':
|
||||||
|
# mock poplib.POP3's list and retr methods to provide responses as per RFC 1939
|
||||||
|
pop3_emails = {
|
||||||
|
'1': ("+OK", test_email.split('\n')),
|
||||||
|
'2': ("+OK", test_email.split('\n')),
|
||||||
|
}
|
||||||
|
pop3_mail_list = ("+OK 2 messages", ("1 %d" % test_mail_len, "2 %d" % test_mail_len))
|
||||||
|
mocked_poplib_server = mock.Mock()
|
||||||
|
mocked_poplib_server.list = mock.Mock(return_value=pop3_mail_list)
|
||||||
|
mocked_poplib_server.retr = mock.Mock(side_effect=lambda x: pop3_emails[x])
|
||||||
|
with mock.patch('helpdesk.management.commands.get_email.poplib', autospec=True) as mocked_poplib:
|
||||||
|
mocked_poplib.POP3 = mock.Mock(return_value=mocked_poplib_server)
|
||||||
|
call_command('get_email')
|
||||||
|
|
||||||
|
elif self.method == 'imap':
|
||||||
|
# mock imaplib.IMAP4's search and fetch methods with responses from RFC 3501
|
||||||
|
imap_emails = {
|
||||||
|
"1": ("OK", (("1", test_email),)),
|
||||||
|
"2": ("OK", (("2", test_email),)),
|
||||||
|
}
|
||||||
|
imap_mail_list = ("OK", ("1 2",))
|
||||||
|
mocked_imaplib_server = mock.Mock()
|
||||||
|
mocked_imaplib_server.search = mock.Mock(return_value=imap_mail_list)
|
||||||
|
|
||||||
|
# we ignore the second arg as the data item/mime-part is constant (RFC822)
|
||||||
|
mocked_imaplib_server.fetch = mock.Mock(side_effect=lambda x, _: imap_emails[x])
|
||||||
|
with mock.patch('helpdesk.management.commands.get_email.imaplib', autospec=True) as mocked_imaplib:
|
||||||
|
mocked_imaplib.IMAP4 = mock.Mock(return_value=mocked_imaplib_server)
|
||||||
|
call_command('get_email')
|
||||||
|
|
||||||
|
ticket1 = get_object_or_404(Ticket, pk=1)
|
||||||
|
self.assertEqual(ticket1.ticket_for_url, "QQ-%s" % ticket1.id)
|
||||||
|
self.assertEqual(ticket1.submitter_email, 'bbb@example.com')
|
||||||
|
self.assertEqual(ticket1.title, test_email_subject)
|
||||||
|
self.assertEqual(ticket1.description, test_email_body)
|
||||||
|
|
||||||
|
ticket2 = get_object_or_404(Ticket, pk=2)
|
||||||
|
self.assertEqual(ticket2.ticket_for_url, "QQ-%s" % ticket2.id)
|
||||||
|
self.assertEqual(ticket2.submitter_email, 'bbb@example.com')
|
||||||
|
self.assertEqual(ticket2.title, test_email_subject)
|
||||||
|
self.assertEqual(ticket2.description, test_email_body)
|
||||||
|
|
||||||
def test_read_email_with_template_tag(self):
|
def test_read_email_with_template_tag(self):
|
||||||
"""Tests reading plain text emails from a queue and creating tickets,
|
"""Tests reading plain text emails from a queue and creating tickets,
|
||||||
except this time the email body contains a Django template tag.
|
except this time the email body contains a Django template tag.
|
||||||
|
@ -97,7 +97,7 @@ class BaseCreateTicketView(abstract_views.AbstractCreateTicketMixin, FormView):
|
|||||||
# This submission is spam. Let's not save it.
|
# This submission is spam. Let's not save it.
|
||||||
return render(request, template_name='helpdesk/public_spam.html')
|
return render(request, template_name='helpdesk/public_spam.html')
|
||||||
else:
|
else:
|
||||||
ticket = form.save()
|
ticket = form.save(user=self.request.user if self.request.user.is_authenticated else None)
|
||||||
try:
|
try:
|
||||||
return HttpResponseRedirect('%s?ticket=%s&email=%s&key=%s' % (
|
return HttpResponseRedirect('%s?ticket=%s&email=%s&key=%s' % (
|
||||||
reverse('helpdesk:public_view'),
|
reverse('helpdesk:public_view'),
|
||||||
|
@ -16,6 +16,7 @@ from django.contrib.auth.decorators import user_passes_test
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.core.exceptions import ValidationError, PermissionDenied
|
from django.core.exceptions import ValidationError, PermissionDenied
|
||||||
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponseRedirect, Http404, HttpResponse, JsonResponse
|
from django.http import HttpResponseRedirect, Http404, HttpResponse, JsonResponse
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
@ -31,6 +32,7 @@ from helpdesk.query import (
|
|||||||
query_to_dict,
|
query_to_dict,
|
||||||
query_to_base64,
|
query_to_base64,
|
||||||
query_from_base64,
|
query_from_base64,
|
||||||
|
apply_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
from helpdesk.user import HelpdeskUser
|
from helpdesk.user import HelpdeskUser
|
||||||
@ -157,6 +159,41 @@ def dashboard(request):
|
|||||||
else:
|
else:
|
||||||
where_clause = """WHERE q.id = t.queue_id"""
|
where_clause = """WHERE q.id = t.queue_id"""
|
||||||
|
|
||||||
|
# get user assigned tickets page
|
||||||
|
paginator = Paginator(
|
||||||
|
tickets, tickets_per_page)
|
||||||
|
try:
|
||||||
|
tickets = paginator.page(user_tickets_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
tickets = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
tickets = paginator.page(
|
||||||
|
paginator.num_pages)
|
||||||
|
|
||||||
|
# get user completed tickets page
|
||||||
|
paginator = Paginator(
|
||||||
|
tickets_closed_resolved, tickets_per_page)
|
||||||
|
try:
|
||||||
|
tickets_closed_resolved = paginator.page(
|
||||||
|
user_tickets_closed_resolved_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
tickets_closed_resolved = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
tickets_closed_resolved = paginator.page(
|
||||||
|
paginator.num_pages)
|
||||||
|
|
||||||
|
# get user submitted tickets page
|
||||||
|
paginator = Paginator(
|
||||||
|
all_tickets_reported_by_current_user, tickets_per_page)
|
||||||
|
try:
|
||||||
|
all_tickets_reported_by_current_user = paginator.page(
|
||||||
|
all_tickets_reported_by_current_user_page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
all_tickets_reported_by_current_user = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
all_tickets_reported_by_current_user = paginator.page(
|
||||||
|
paginator.num_pages)
|
||||||
|
|
||||||
return render(request, 'helpdesk/dashboard.html', {
|
return render(request, 'helpdesk/dashboard.html', {
|
||||||
'user_tickets': tickets,
|
'user_tickets': tickets,
|
||||||
'user_tickets_closed_resolved': tickets_closed_resolved,
|
'user_tickets_closed_resolved': tickets_closed_resolved,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Django>=2.2.9,<3
|
Django>=2.2.13,<3
|
||||||
django-bootstrap4-form
|
django-bootstrap4-form
|
||||||
celery
|
celery
|
||||||
django-celery-beat
|
django-celery-beat
|
||||||
@ -12,4 +12,6 @@ pytz
|
|||||||
six
|
six
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-model-utils
|
django-model-utils
|
||||||
pinax-teams @ git+https://github.com/auto-mat/pinax-teams.git@slugify#egg=pinax-teams
|
|
||||||
|
# specific commit because the current release has no required library upgrade
|
||||||
|
pinax-teams @ git+https://github.com/pinax/pinax-teams.git@dd75e1c#egg=pinax-teams
|
||||||
|
2
setup.py
2
setup.py
@ -126,9 +126,9 @@ setup(
|
|||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
"Framework :: Django :: 2.0",
|
"Framework :: Django :: 2.0",
|
||||||
"Framework :: Django :: 2.1",
|
"Framework :: Django :: 2.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user