diff --git a/.gitignore b/.gitignore index 663b12da..d5885822 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +.eggs/ /dist/ django_helpdesk.egg-info demo/*.egg-info diff --git a/.travis.yml b/.travis.yml index 84a575a3..8827ddbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ python: - "3.6" env: - - DJANGO=1.11.8 + - DJANGO=1.11.9 install: - pip install -q Django==$DJANGO diff --git a/LICENSE b/LICENSE index d1b97445..5955bc78 100644 --- a/LICENSE +++ b/LICENSE @@ -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, diff --git a/README.rst b/README.rst index 4b1822c8..a4521c28 100644 --- a/README.rst +++ b/README.rst @@ -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`:: diff --git a/demo/setup.py b/demo/setup.py index 82a6ac8c..780e7d83 100644 --- a/demo/setup.py +++ b/demo/setup.py @@ -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 = [ diff --git a/docs/index.rst b/docs/index.rst index 37054149..1757da4d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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``. diff --git a/docs/settings.rst b/docs/settings.rst index 598707de..804e7f3c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -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`` diff --git a/helpdesk/forms.py b/helpdesk/forms.py index 1195e5d0..e9d4ef64 100644 --- a/helpdesk/forms.py +++ b/helpdesk/forms.py @@ -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): diff --git a/helpdesk/management/commands/get_email.py b/helpdesk/management/commands/get_email.py index 520ab3d5..65eea45b 100755 --- a/helpdesk/management/commands/get_email.py +++ b/helpdesk/management/commands/get_email.py @@ -176,7 +176,14 @@ def process_queue(q, logger): msgNum = msg.split(" ")[0] logger.info("Processing message %s" % msgNum) - full_message = encoding.force_text("\n".join(server.retr(msgNum)[1]), errors='replace') + 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') - ticket = ticket_from_message(message=full_message, queue=q, logger=logger) + 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 ) diff --git a/helpdesk/migrations/0017_default_owner_on_delete_null.py b/helpdesk/migrations/0017_default_owner_on_delete_null.py new file mode 100644 index 00000000..54591f64 --- /dev/null +++ b/helpdesk/migrations/0017_default_owner_on_delete_null.py @@ -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'), + ), + ] diff --git a/helpdesk/models.py b/helpdesk/models.py index 6388827b..c9e2c7a3 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -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 - self.settings_pickled = b64encode(pickle.dumps(data)) + 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 {} diff --git a/helpdesk/static/helpdesk/vendor/jquery-ui/jquery-ui.theme.css b/helpdesk/static/helpdesk/vendor/jquery-ui/jquery-ui.theme.css index 6089438c..50ecb9bf 100644 --- a/helpdesk/static/helpdesk/vendor/jquery-ui/jquery-ui.theme.css +++ b/helpdesk/static/helpdesk/vendor/jquery-ui/jquery-ui.theme.css @@ -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 */ diff --git a/helpdesk/templates/helpdesk/en/email_html_base.html b/helpdesk/templates/helpdesk/en/email_html_base.html index d88d39d9..8c7e631a 100644 --- a/helpdesk/templates/helpdesk/en/email_html_base.html +++ b/helpdesk/templates/helpdesk/en/email_html_base.html @@ -6,4 +6,4 @@

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

-

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.

+

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.

diff --git a/helpdesk/templates/helpdesk/ticket.html b/helpdesk/templates/helpdesk/ticket.html index d0f86d16..eb6de0bb 100644 --- a/helpdesk/templates/helpdesk/ticket.html +++ b/helpdesk/templates/helpdesk/ticket.html @@ -105,6 +105,9 @@ $(document).on('change', ':file', function() { {% if forloop.last %}{% endif %} {% endfor %} + +{% 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 %}
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %} @@ -116,6 +119,7 @@ $(document).on('change', ':file', function() { {% endif %}
+{% endif %}{% endwith %} diff --git a/helpdesk/tests/test_ticket_actions.py b/helpdesk/tests/test_ticket_actions.py index a5c82bf3..66f7bbd2 100644 --- a/helpdesk/tests/test_ticket_actions.py +++ b/helpdesk/tests/test_ticket_actions.py @@ -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""" diff --git a/helpdesk/views/public.py b/helpdesk/views/public.py index ddc5b614..78fb113d 100644 --- a/helpdesk/views/public.py +++ b/helpdesk/views/public.py @@ -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]) diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index a9030c42..d0c5b4f2 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -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 diff --git a/requirements.txt b/requirements.txt index a5b92c29..dbd87059 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django>=1.11,<2 +Django>=1.11,<3 django-bootstrap-form>=3.3,<4 email-reply-parser django-markdown-deux diff --git a/setup.py b/setup.py index 76fa2d6d..482f475a 100644 --- a/setup.py +++ b/setup.py @@ -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",