Merge 0.2.7 bugfixes

This commit is contained in:
Garret Wassermann 2018-03-04 04:01:48 -05:00
commit 54a6b1d21b
19 changed files with 149 additions and 27 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.pyc
.eggs/
/dist/
django_helpdesk.egg-info
demo/*.egg-info

View File

@ -7,7 +7,7 @@ python:
- "3.6"
env:
- DJANGO=1.11.8
- DJANGO=1.11.9
install:
- pip install -q Django==$DJANGO

View File

@ -1,4 +1,5 @@
Copyright (c) 2008, Ross Poulton (Trading as Jutda)
Copyright (c) 2008 Ross Poulton (Trading as Jutda),
Copyright (c) 2008-2018 django-helpdesk contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

View File

@ -7,7 +7,7 @@ django-helpdesk - A Django powered ticket tracker for small businesses.
.. image:: https://codecov.io/gh/django-helpdesk/django-helpdesk/branch/master/graph/badge.svg
:target: https://codecov.io/gh/django-helpdesk/django-helpdesk
Copyright 2009-2017 Ross Poulton and django-helpdesk contributors. All Rights Reserved.
Copyright 2009-2018 Ross Poulton and django-helpdesk contributors. All Rights Reserved.
See LICENSE for details.
django-helpdesk was formerly known as Jutda Helpdesk, named after the
@ -62,7 +62,7 @@ Installation
`django-helpdesk` requires:
* Django 1.11.x *only*
* Django 1.11.x
* either Python 2.7 or 3.4+
**NOTE REGARDING PYTHON VERSION:**
@ -72,6 +72,11 @@ and Django itself (Django 2.0), so users and developers are encouraged to begin
transitioning to Python 3 if have not already. New projects should definitely
use Python 3!
**NOTE REGARDING DJANGO VERSION:**
The recommended release is Django 1.11. However, there initial support of
Django 2.0 as of version 0.2.7 if you'd like to try it out.
Please report any bugs you find!
You can quickly install the latest stable version of `django-helpdesk`
app via `pip`::

View File

@ -23,7 +23,8 @@ CLASSIFIERS = ['Development Status :: 4 - Beta',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Framework :: Django']
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0']
KEYWORDS = []
PACKAGES = ['demodesk']
REQUIREMENTS = [

View File

@ -71,5 +71,5 @@ Dependencies
1. Python 3.4+ (or 2.7, but deprecated and support will be removed next release)
2. Django 1.11 or newer
3. An existing **working** Django project with database etc. If you cannot log into the Admin, you won't get this product working! This means you **must** run `syncdb` **before** you add ``helpdesk`` to your ``INSTALLED_APPS``.
3. An existing **working** Django project with database etc. If you cannot log into the Admin, you won't get this product working! This means you **must** run `migrate` **before** you add ``helpdesk`` to your ``INSTALLED_APPS``.

View File

@ -105,6 +105,22 @@ These options only change display of items on public-facing pages, not staff pag
**Default:** ``HELPDESK_SUBMIT_A_TICKET_PUBLIC = True``
Options for public ticket submission form
-----------------------------------------
- **HELPDESK_PUBLIC_TICKET_QUEUE** Sets the queue for tickets submitted through the public form. If defined, the matching form field will be hidden. This cannot be `None` but must be set to a valid queue slug.
**Default:** Not defined
- **HELPDESK_PUBLIC_TICKET_PRIORITY** Sets the priority for tickets submitted through the public form. If defined, the matching form field will be hidden. Must be set to a valid integer priority.
**Default:** Not defined
- **HELPDESK_PUBLIC_TICKET_DUE_DATE** Sets the due date for tickets submitted through the public form. If defined, the matching form field will be hidden. Set to `None` if you want to hide the form field but do not want to define a value.
**Default:** Not defined
Options that change ticket updates
----------------------------------
@ -149,9 +165,9 @@ Staff Ticket Creation Settings
Staff Ticket View Settings
------------------------------
- **HELPDESK_ENABLE_PER_QUEUE_PERMISSION** If ``True``, logged in staff users only see queues and tickets to which they have specifically been granted access - this holds for the dashboard, ticket query, and ticket report views. User assignment is done through the standard ``django.admin.admin`` permissions. *Note*: Staff with access to admin interface will be able to see the full list of tickets, but won't have access to details and could not modify them. This setting does not prevent staff users from creating tickets for all queues. Also, superuser accounts have full access to all queues, regardless of whatever queue memberships they have been granted.
- **HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION** If ``True``, logged in staff users only see queues and tickets to which they have specifically been granted access - this holds for the dashboard, ticket query, and ticket report views. User assignment is done through the standard ``django.admin.admin`` permissions. *Note*: Staff with access to admin interface will be able to see the full list of tickets, but won't have access to details and could not modify them. This setting does not prevent staff users from creating tickets for all queues. Also, superuser accounts have full access to all queues, regardless of whatever queue memberships they have been granted.
**Default:** ``HELPDESK_ENABLE_PER_QUEUE_PERMISSION = False``
**Default:** ``HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = False``

View File

@ -368,6 +368,14 @@ class PublicTicketForm(AbstractTicketForm):
Add any (non-staff) custom fields that are defined to the form
"""
super(PublicTicketForm, self).__init__(*args, **kwargs)
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_QUEUE'):
self.fields['queue'].widget = forms.HiddenInput()
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_PRIORITY'):
self.fields['priority'].widget = forms.HiddenInput()
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_DUE_DATE'):
self.fields['due_date'].widget = forms.HiddenInput()
self._add_form_custom_fields(False)
def save(self):

View File

@ -176,7 +176,14 @@ def process_queue(q, logger):
msgNum = msg.split(" ")[0]
logger.info("Processing message %s" % msgNum)
if six.PY2:
full_message = encoding.force_text("\n".join(server.retr(msgNum)[1]), errors='replace')
else:
raw_content = server.retr(msgNum)[1]
if type(raw_content[0]) is bytes:
full_message = "\n".join([elm.decode('utf-8') for elm in raw_content])
else:
full_message = encoding.force_text("\n".join(raw_content), errors='replace')
ticket = ticket_from_message(message=full_message, queue=q, logger=logger)
if ticket:
@ -229,7 +236,10 @@ def process_queue(q, logger):
logger.info("Processing message %s" % num)
status, data = server.fetch(num, '(RFC822)')
full_message = encoding.force_text(data[0][1], errors='replace')
try:
ticket = ticket_from_message(message=full_message, queue=q, logger=logger)
except TypeError:
ticket = None # hotfix. Need to work out WHY.
if ticket:
server.store(num, '+FLAGS', '\\Deleted')
logger.info("Successfully processed message %s, deleted from IMAP server" % num)
@ -389,8 +399,9 @@ def ticket_from_message(message, queue, logger):
if not body:
mail = BeautifulSoup(part.get_payload(), "lxml")
if ">" in mail.text:
message_body = mail.text.split(">")[1]
body = message_body.encode('ascii', errors='ignore')
body = mail.find('body')
body = body.text
body = body.encode('ascii', errors='ignore')
else:
body = mail.text
@ -459,7 +470,7 @@ def ticket_from_message(message, queue, logger):
for ccemail in new_cc:
tcc = TicketCC.objects.create(
ticket=t,
email=ccemail,
email=ccemail.replace('\n', ' ').replace('\r', ' '),
can_view=True,
can_update=False
)

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-01-19 09:48
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0016_alter_model_options'),
]
operations = [
migrations.AlterField(
model_name='queue',
name='default_owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_owner', to=settings.AUTH_USER_MODEL, verbose_name='Default owner'),
),
]

View File

@ -14,6 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.utils import six
from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import python_2_unicode_compatible
@ -1120,7 +1121,10 @@ class UserSettings(models.Model):
except ImportError:
import cPickle as pickle
from helpdesk.lib import b64encode
if six.PY2:
self.settings_pickled = b64encode(pickle.dumps(data))
else:
self.settings_pickled = b64encode(pickle.dumps(data)).decode()
def _get_settings(self):
# return a python dictionary representing the pickled data.
@ -1130,13 +1134,10 @@ class UserSettings(models.Model):
import cPickle as pickle
from helpdesk.lib import b64decode
try:
if six.PY3:
if type(self.settings_pickled) is bytes:
return pickle.loads(b64decode(str(self.settings_pickled, 'utf8')))
else:
return pickle.loads(b64decode(bytes(self.settings_pickled, 'utf8')))
else:
if six.PY2:
return pickle.loads(b64decode(str(self.settings_pickled)))
else:
return pickle.loads(b64decode(self.settings_pickled.encode('utf-8')))
except pickle.UnpicklingError:
return {}

View File

@ -8,7 +8,6 @@
*
* http://api.jqueryui.com/category/theming/
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6
*/

View File

@ -6,4 +6,4 @@
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'><b>{{ queue.title }}</b>{% if queue.email_address %}<br><a href='mailto:{{ queue.email_address }}'>{{ queue.email_address }}</a>{% endif %}</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 9pt; color: #808080;' color='#808080'>This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error.</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 9pt; color: #808080;'>This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error.</p>

View File

@ -105,6 +105,9 @@ $(document).on('change', ':file', function() {
</li>
{% if forloop.last %}</ul></div>{% endif %}
{% endfor %}
<!--- ugly long test to suppress the following if it will be empty, to save vertical space -->
{% with possible=helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
{% if possible and followup.user and request.user == followup.user and not followup.ticketchange_set.all or possible and user.is_superuser and helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP %}
<hr>
<div class="btn-group">
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
@ -116,6 +119,7 @@ $(document).on('change', ':file', function() {
<a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i>&nbsp;{% trans "Delete" %}</button></a>
{% endif %}
</div>
{% endif %}{% endwith %}
</div>
</div>
</li>

View File

@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core import mail
from django.urls import reverse
from django.test import TestCase
@ -11,6 +12,7 @@ except ImportError: # python 2
from urlparse import urlparse
from helpdesk.templatetags.ticket_to_link import num_to_link
from helpdesk.views.staff import _is_my_ticket
class TicketActionsTestCase(TestCase):
@ -22,7 +24,8 @@ class TicketActionsTestCase(TestCase):
slug='q1',
allow_public_submission=True,
new_ticket_cc='new.public@example.com',
updated_ticket_cc='update.public@example.com')
updated_ticket_cc='update.public@example.com'
)
self.ticket_data = {
'title': 'Test Ticket',
@ -32,6 +35,7 @@ class TicketActionsTestCase(TestCase):
self.client = Client()
def loginUser(self, is_staff=True):
"""Create a staff user and login"""
User = get_user_model()
self.user = User.objects.create(
username='User_1',
@ -124,6 +128,32 @@ class TicketActionsTestCase(TestCase):
response = self.client.post(reverse('helpdesk:update', kwargs={'ticket_id': ticket_id}), post_data, follow=True)
self.assertContains(response, 'Changed Status from Open to Closed')
def test_is_my_ticket(self):
"""Tests whether non-staff but assigned user still counts as owner"""
# make non-staff user
self.loginUser(is_staff=False)
# create second user
User = get_user_model()
self.user2 = User.objects.create(
username='User_2',
is_staff=False,
)
initial_data = {
'title': 'Private ticket test',
'queue': self.queue_public,
'assigned_to': self.user,
'status': Ticket.OPEN_STATUS,
}
# create ticket
ticket = Ticket.objects.create(**initial_data)
self.assertEqual(_is_my_ticket(self.user, ticket), True)
self.assertEqual(_is_my_ticket(self.user2, ticket), False)
def test_num_to_link(self):
"""Test that we are correctly expanding links to tickets from IDs"""

View File

@ -7,11 +7,17 @@ views/public.py - All public facing views, eg non-staff (no authentication
required) views.
"""
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.http import HttpResponseRedirect
try:
# Django 2.0+
from django.urls import reverse
except ImportError:
# Django < 2
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from django.utils.http import urlquote
from django.utils.translation import ugettext as _
from django.conf import settings
from helpdesk import settings as helpdesk_settings
from helpdesk.decorators import protect_view, is_helpdesk_staff
@ -61,6 +67,19 @@ def homepage(request):
except Queue.DoesNotExist:
queue = None
initial_data = {}
# add pre-defined data for public ticket
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_QUEUE'):
# get the requested queue; return an error if queue not found
try:
queue = Queue.objects.get(slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE)
except Queue.DoesNotExist:
return HttpResponse(status=500)
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_PRIORITY'):
initial_data['priority'] = settings.HELPDESK_PUBLIC_TICKET_PRIORITY
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_DUE_DATE'):
initial_data['due_date'] = settings.HELPDESK_PUBLIC_TICKET_DUE_DATE
if queue:
initial_data['queue'] = queue.id
@ -91,6 +110,8 @@ def view_ticket(request):
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
except ObjectDoesNotExist:
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
except ValueError:
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
else:
if is_helpdesk_staff(request.user):
redirect_url = reverse('helpdesk:view', args=[ticket_id])

View File

@ -92,7 +92,7 @@ def _has_access_to_queue(user, queue):
def _is_my_ticket(user, ticket):
"""Check to see if the user has permission to access
a ticket. If not then deny access."""
if user.is_superuser or user.is_staff or user.id == ticket.customer_id:
if user.is_superuser or user.is_staff or user.id == ticket.assigned_to.id:
return True
else:
return False

View File

@ -1,4 +1,4 @@
Django>=1.11,<2
Django>=1.11,<3
django-bootstrap-form>=3.3,<4
email-reply-parser
django-markdown-deux

View File

@ -136,6 +136,7 @@ setup(
"Programming Language :: Python :: 3.6",
"Framework :: Django",
"Framework :: Django :: 1.11",
"Framework :: Django :: 2.0",
"Environment :: Web Environment",
"Operating System :: OS Independent",
"Intended Audience :: Customer Service",