Merge 0.2.8 bugfixes

This commit is contained in:
Garret Wassermann 2018-07-03 00:59:33 -04:00
commit dff14d40d3
13 changed files with 85 additions and 63 deletions

View File

@ -7,7 +7,7 @@ python:
- "3.6" - "3.6"
env: env:
- DJANGO=1.11.9 - DJANGO=1.11.13
install: install:
- pip install -q Django==$DJANGO - pip install -q Django==$DJANGO
@ -15,7 +15,7 @@ install:
- pip install -q -r requirements-testing.txt - pip install -q -r requirements-testing.txt
before_script: before_script:
- "pycodestyle --exclude=migrations --ignore=E501 helpdesk" - "pycodestyle --exclude=migrations --ignore=E501,W503,W504 helpdesk"
script: script:
- coverage run --source='.' quicktest.py helpdesk - coverage run --source='.' quicktest.py helpdesk

View File

@ -19,11 +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 :: 2.7',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0'] 'Framework :: Django :: 2.0']
KEYWORDS = [] KEYWORDS = []
PACKAGES = ['demodesk'] PACKAGES = ['demodesk']

View File

@ -215,4 +215,4 @@ The following settings were defined in previous versions and are no longer suppo
- **HELPDESK_FOOTER_SHOW_CHANGE_LANGUAGE_LINK** Is never shown. Use your own template if required. - **HELPDESK_FOOTER_SHOW_CHANGE_LANGUAGE_LINK** Is never shown. Use your own template if required.
- **HELPDESK_ENABLE_PER_QUEUE_MEMBERSHIP** Discontinued in favor of HELPDESK_ENABLE_PER_QUEUE_PERMISSION. - **HELPDESK_ENABLE_PER_QUEUE_MEMBERSHIP** Discontinued in favor of HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION.

View File

@ -12,6 +12,9 @@
# Released subject to the BSD License # Released subject to the BSD License
# See http://www.voidspace.org.uk/python/license.shtml # See http://www.voidspace.org.uk/python/license.shtml
# Updated by django-helpdesk developers, 2018
# to be compatible with python 3
""" """
A python interface to the `Akismet <http://akismet.com>`_ API. A python interface to the `Akismet <http://akismet.com>`_ API.
@ -56,7 +59,10 @@ Usage example::
import os import os
from urllib import urlencode try:
from urllib import urlencode # python2
except ImportError:
from urllib.parse import urlencode # python3
import socket import socket
if hasattr(socket, 'setdefaulttimeout'): if hasattr(socket, 'setdefaulttimeout'):

View File

@ -216,7 +216,7 @@ def process_queue(q, logger):
q.email_box_pass or q.email_box_pass or
settings.QUEUE_EMAIL_BOX_PASSWORD) settings.QUEUE_EMAIL_BOX_PASSWORD)
server.select(q.email_box_imap_folder) server.select(q.email_box_imap_folder)
except imaplib.IMAP.abort: except imaplib.IMAP4.abort:
logger.error("IMAP login failed. Check that the server is accessible and that the username and password are correct.") logger.error("IMAP login failed. Check that the server is accessible and that the username and password are correct.")
server.logout() server.logout()
sys.exit() sys.exit()
@ -332,7 +332,7 @@ def ticket_from_message(message, queue, logger):
return False return False
return True return True
matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject) matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject)
if matchobj: if matchobj:
# This is a reply or forward. # This is a reply or forward.
ticket = matchobj.group('id') ticket = matchobj.group('id')

View File

@ -17,6 +17,7 @@ from django.utils import timezone
from django.utils import six from django.utils import six
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
import re
import six import six
@ -279,6 +280,12 @@ class Queue(models.Model):
in the sender name field, so hopefully the admin can see and fix it. in the sender name field, so hopefully the admin can see and fix it.
""" """
if not self.email_address: if not self.email_address:
# must check if given in format "Foo <foo@example.com>"
default_email = re.match(".*<(?P<email>.*@*.)>", settings.DEFAULT_FROM_EMAIL)
if default_email is not None:
# already in the right format, so just include it here
return u'NO QUEUE EMAIL ADDRESS DEFINED %s' % settings.DEFAULT_FROM_EMAIL
else:
return u'NO QUEUE EMAIL ADDRESS DEFINED <%s>' % settings.DEFAULT_FROM_EMAIL return u'NO QUEUE EMAIL ADDRESS DEFINED <%s>' % settings.DEFAULT_FROM_EMAIL
else: else:
return u'%s <%s>' % (self.title, self.email_address) return u'%s <%s>' % (self.title, self.email_address)

View File

@ -11,12 +11,11 @@
<p>{% blocktrans %}You have shared this query, so other users may be using it. If you delete it, they will have to manually create their own query.{% endblocktrans %}</p> <p>{% blocktrans %}You have shared this query, so other users may be using it. If you delete it, they will have to manually create their own query.{% endblocktrans %}</p>
{% endif %} {% endif %}
<p><a href='{% url 'helpdesk:list' %}?saved_query={{ query.id }}'><button class="btn btn-primary btn-lg">{% trans "No, Don't Delete It" %}</button></a></p>
<p><a href='../'><button class="btn btn-primary btn-lg">{% trans "No, Don't Delete It" %}</button></a></p> <form method='post' action='./'>{% csrf_token %}
<form method='post' action='./'>
<button class="btn btn-danger" type='submit'>{% trans "Yes I Understand - Delete It Anyway" %}</button> <button class="btn btn-danger" type='submit'>{% trans "Yes I Understand - Delete It Anyway" %}</button>
{% csrf_token %}</form> </form>
{% endblock %} {% endblock %}

View File

@ -37,4 +37,11 @@
</div> </div>
</div> </div>
</div> </div>
<script>
$( function() {
$( "#id_due_date" ).datepicker();
} );
</script>
{% endblock %} {% endblock %}

View File

@ -214,6 +214,7 @@ $(document).on('change', ':file', function() {
<dt><label for='id_priority'>{% trans "Priority" %}</label></dt> <dt><label for='id_priority'>{% trans "Priority" %}</label></dt>
<dd><select id='id_priority' name='priority'>{% for p in priorities %}<option value='{{ p.0 }}'{% ifequal p.0 ticket.priority %} selected='selected'{% endifequal %}>{{ p.1 }}</option>{% endfor %}</select></dd> <dd><select id='id_priority' name='priority'>{% for p in priorities %}<option value='{{ p.0 }}'{% ifequal p.0 ticket.priority %} selected='selected'{% endifequal %}>{{ p.1 }}</option>{% endfor %}</select></dd>
<dt><label for='id_due_date'>{% trans "Due on" %}</label></dt> <dt><label for='id_due_date'>{% trans "Due on" %}</label></dt>
<dd>{{ form.due_date }}</dd> <dd>{{ form.due_date }}</dd>

View File

@ -38,6 +38,11 @@
<tr> <tr>
<td colspan='2'>{{ ticket.resolution|force_escape|urlizetrunc:50|linebreaksbr }}</td> <td colspan='2'>{{ ticket.resolution|force_escape|urlizetrunc:50|linebreaksbr }}</td>
</tr>{% endif %} </tr>{% endif %}
<tr>
<th>{% trans "Due Date" %}</th>
<td>{{ ticket.due_date|date:"r" }} ({{ ticket.due_date|naturaltime }})</td>
</tr>
<tr> <tr>
<th>{% trans "Submitted On" %}</th> <th>{% trans "Submitted On" %}</th>
<td>{{ ticket.created|date:"r" }} ({{ ticket.created|naturaltime }})</td> <td>{{ ticket.created|date:"r" }} ({{ ticket.created|naturaltime }})</td>

View File

@ -223,6 +223,7 @@ $(document).ready(function() {
<th>{% trans "Queue" %}</th> <th>{% trans "Queue" %}</th>
<th>{% trans "Status" %}</th> <th>{% trans "Status" %}</th>
<th>{% trans "Created" %}</th> <th>{% trans "Created" %}</th>
<th>{% trans "Due Date" %}</th>
<th>{% trans "Owner" %}</th> <th>{% trans "Owner" %}</th>
</tr> </tr>
</thead> </thead>
@ -236,6 +237,7 @@ $(document).ready(function() {
<td>{{ ticket.queue }}</td> <td>{{ ticket.queue }}</td>
<td>{{ ticket.get_status }}</td> <td>{{ ticket.get_status }}</td>
<td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td> <td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
<td data-order='{{ ticket.due_date|date:"U" }}'><span title='{{ ticket.due_date|date:"r" }}'>{{ ticket.due_date|naturaltime }}</span></td>
<td>{{ ticket.get_assigned_to }}</td> <td>{{ ticket.get_assigned_to }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -7,8 +7,10 @@ views/staff.py - The bulk of the application - provides most business logic and
renders all staff-facing views. renders all staff-facing views.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import datetime, timedelta from datetime import date, datetime, timedelta
import re
from django import VERSION as DJANGO_VERSION
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
@ -155,21 +157,6 @@ def dashboard(request):
else: else:
where_clause = """WHERE q.id = t.queue_id""" where_clause = """WHERE q.id = t.queue_id"""
cursor = connection.cursor()
cursor.execute("""
SELECT q.id as queue,
q.title AS name,
COUNT(CASE t.status WHEN '1' THEN t.id WHEN '2' THEN t.id END) AS open,
COUNT(CASE t.status WHEN '3' THEN t.id END) AS resolved,
COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed
%s
%s
GROUP BY queue, name
ORDER BY q.id;
""" % (from_clause, where_clause))
dash_tickets = query_to_dict(cursor.fetchall(), cursor.description)
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,
@ -404,6 +391,10 @@ def update_ticket(request, ticket_id, public=False):
ticket = get_object_or_404(Ticket, id=ticket_id) ticket = get_object_or_404(Ticket, id=ticket_id)
date_re = re.compile(
r'(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<year>\d{4})$'
)
comment = request.POST.get('comment', '') comment = request.POST.get('comment', '')
new_status = int(request.POST.get('new_status', ticket.status)) new_status = int(request.POST.get('new_status', ticket.status))
title = request.POST.get('title', '') title = request.POST.get('title', '')
@ -413,10 +404,24 @@ def update_ticket(request, ticket_id, public=False):
due_date_year = int(request.POST.get('due_date_year', 0)) due_date_year = int(request.POST.get('due_date_year', 0))
due_date_month = int(request.POST.get('due_date_month', 0)) due_date_month = int(request.POST.get('due_date_month', 0))
due_date_day = int(request.POST.get('due_date_day', 0)) due_date_day = int(request.POST.get('due_date_day', 0))
# NOTE: jQuery's default for dates is mm/dd/yy
# very US-centric but for now that's the only format supported
# until we clean up code to internationalize a little more
due_date = request.POST.get('due_date', None)
if due_date is not None:
# based on Django code to parse dates:
# https://docs.djangoproject.com/en/2.0/_modules/django/utils/dateparse/
match = date_re.match(due_date)
if match:
kw = {k: int(v) for k, v in match.groupdict().items()}
due_date = date(**kw)
else:
# old way, probably deprecated?
if not (due_date_year and due_date_month and due_date_day): if not (due_date_year and due_date_month and due_date_day):
due_date = ticket.due_date due_date = ticket.due_date
else: else:
# NOTE: must be an easier way to create a new date than doing it this way?
if ticket.due_date: if ticket.due_date:
due_date = ticket.due_date due_date = ticket.due_date
else: else:
@ -859,6 +864,10 @@ def ticket_list(request):
from helpdesk.lib import b64decode from helpdesk.lib import b64decode
try: try:
if six.PY3: if six.PY3:
if DJANGO_VERSION[0] > 1:
# if Django >= 2.0
query_params = json.loads(b64decode(str(saved_query.query).lstrip("b\\'")).decode())
else:
query_params = json.loads(b64decode(str(saved_query.query)).decode()) query_params = json.loads(b64decode(str(saved_query.query)).decode())
else: else:
query_params = json.loads(b64decode(str(saved_query.query))) query_params = json.loads(b64decode(str(saved_query.query)))
@ -1123,31 +1132,18 @@ def report_index(request):
# Open Resolved # Open Resolved
# Queue 1 10 4 # Queue 1 10 4
# Queue 2 4 12 # Queue 2 4 12
Queues = user_queues if user_queues else Queue.objects.all()
queues = _get_user_queues(request.user).values_list('id', flat=True) dash_tickets = []
for queue in Queues:
from_clause = """FROM helpdesk_ticket t, dash_ticket = {
helpdesk_queue q""" 'queue': queue.id,
if queues: 'name': queue.title,
where_clause = """WHERE q.id = t.queue_id AND 'open': queue.ticket_set.filter(status__in=[1, 2]).count(),
q.id IN (%s)""" % (",".join(("%d" % pk for pk in queues))) 'resolved': queue.ticket_set.filter(status=3).count(),
else: 'closed': queue.ticket_set.filter(status=4).count(),
where_clause = """WHERE q.id = t.queue_id""" }
dash_tickets.append(dash_ticket)
cursor = connection.cursor()
cursor.execute("""
SELECT q.id as queue,
q.title AS name,
COUNT(CASE t.status WHEN '1' THEN t.id WHEN '2' THEN t.id END) AS open,
COUNT(CASE t.status WHEN '3' THEN t.id END) AS resolved,
COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed
%s
%s
GROUP BY queue, name
ORDER BY q.id;
""" % (from_clause, where_clause))
dash_tickets = query_to_dict(cursor.fetchall(), cursor.description)
return render(request, 'helpdesk/report_index.html', { return render(request, 'helpdesk/report_index.html', {
'number_tickets': number_tickets, 'number_tickets': number_tickets,
@ -1187,6 +1183,10 @@ def run_report(request, report):
from helpdesk.lib import b64decode from helpdesk.lib import b64decode
try: try:
if six.PY3: if six.PY3:
if DJANGO_VERSION[0] > 1:
# if Django >= 2.0
query_params = json.loads(b64decode(str(saved_query.query).lstrip("b\\'")).decode())
else:
query_params = json.loads(b64decode(str(saved_query.query)).decode()) query_params = json.loads(b64decode(str(saved_query.query)).decode())
else: else:
query_params = json.loads(b64decode(str(saved_query.query))) query_params = json.loads(b64decode(str(saved_query.query)))

View File

@ -128,14 +128,11 @@ setup(
classifiers=[ classifiers=[
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 1.11",
"Framework :: Django :: 2.0", "Framework :: Django :: 2.0",
"Environment :: Web Environment", "Environment :: Web Environment",
"Operating System :: OS Independent", "Operating System :: OS Independent",