From cb34b1933a6530d62e0e9916f69e5cb2d242b072 Mon Sep 17 00:00:00 2001 From: Andreas Kotowicz Date: Sat, 19 Nov 2011 09:34:07 +0100 Subject: [PATCH] make django-helpdesk more customizable + bug fixes: - look at settings.py for all new options regarding customization. - settings can be accessed inside the templates via the new templatetag 'load_helpdesk_settings' - allow editing of personal followups, but only if followup does not contain any ticketchanges - otherwise this information is lost after the editing. - add 'delete' link to attachments - link to list of closed tickets in queue overview - add 'closed & resolved' section to dashboard - hide 'pre-set reply' box if no pre-set replies are found. - use 'SelectDateWidget' for custom DateField - fix how we update followups so that attachments don't get deleted - fix bug where resolution emails contained the solution 'None' - fix stats crashing bug - fix locale bug --- helpdesk/forms.py | 2 + helpdesk/lib.py | 8 +- helpdesk/settings.py | 54 ++++++++++++++ helpdesk/templates/helpdesk/attribution.html | 5 +- helpdesk/templates/helpdesk/base.html | 9 ++- helpdesk/templates/helpdesk/dashboard.html | 35 +++++++-- helpdesk/templates/helpdesk/navigation.html | 10 ++- helpdesk/templates/helpdesk/public_base.html | 8 +- .../templates/helpdesk/public_homepage.html | 9 +++ helpdesk/templates/helpdesk/ticket.html | 18 ++++- .../templates/helpdesk/ticket_desc_table.html | 11 ++- .../templatetags/load_helpdesk_settings.py | 21 ++++++ helpdesk/urls.py | 4 + helpdesk/views/public.py | 5 +- helpdesk/views/staff.py | 74 ++++++++++++++----- 15 files changed, 237 insertions(+), 36 deletions(-) create mode 100644 helpdesk/templatetags/load_helpdesk_settings.py diff --git a/helpdesk/forms.py b/helpdesk/forms.py index 502a90bb..3e45d0c6 100644 --- a/helpdesk/forms.py +++ b/helpdesk/forms.py @@ -11,6 +11,7 @@ from datetime import datetime from StringIO import StringIO from django import forms +from django.forms import extras from django.conf import settings from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -195,6 +196,7 @@ class TicketForm(forms.Form): fieldclass = forms.BooleanField elif field.data_type == 'date': fieldclass = forms.DateField + instanceargs['widget'] = extras.SelectDateWidget elif field.data_type == 'time': fieldclass = forms.TimeField elif field.data_type == 'datetime': diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 05469588..0826b346 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -54,7 +54,13 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b import os context = Context(email_context) - locale = context['queue'].get('locale', 'en') + + if hasattr(context['queue'], 'locale'): + locale = getattr(context['queue'], 'locale', '') + else: + locale = context['queue'].get('locale', 'en') + if not locale: + locale = 'en' t = None try: diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 92e255ef..79a609b5 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -28,5 +28,59 @@ if type(DEFAULT_USER_SETTINGS) != type(dict()): 'tickets_per_page': 25 } +''' generic options - visible on all pages ''' +# redirect to login page instead of the default homepage when users visits "/"? +HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = getattr(settings, 'HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT', False) + # show knowledgebase links? HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True) + +# show extended navigation by default, to all users, irrespective of staff status? +HELPDESK_NAVIGATION_ENABLED = getattr(settings, 'HELPDESK_NAVIGATION_ENABLED', False) + +# show 'stats' link in navigation bar? +HELPDESK_NAVIGATION_STATS_ENABLED = getattr(settings, 'HELPDESK_NAVIGATION_STATS_ENABLED', True) + +# set this to an email address inside your organization and a footer below +# the 'Powered by django-helpdesk' will be shown, telling the user whom to contact +# in case they have technical problems. +HELPDESK_SUPPORT_PERSON = getattr(settings, 'HELPDESK_SUPPORT_PERSON', False) + + +''' options for public pages ''' +# show 'view a ticket' section on public page? +HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC', True) + +# show 'submit a ticket' section on public page? +HELPDESK_SUBMIT_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_SUBMIT_A_TICKET_PUBLIC', True) + + +''' options for update_ticket views ''' +# allow non-staff users to interact with tickets? this will also change how 'staff_member_required' +# in staff.py will be defined. +HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE = getattr(settings, 'HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE', False) + +# show edit buttons in ticket follow ups. +HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP = getattr(settings, 'HELPDESK_HIDE_EDIT_BUTTON_FOLLOW_UP', True) + +# show ticket edit button on top of ticket description. +HELPDESK_SHOW_EDIT_BUTTON_TICKET_TOP = getattr(settings, 'HELPDESK_SHOW_EDIT_BUTTON_TICKET_TOP', True) + +# show ticket delete button on top of ticket description. +HELPDESK_SHOW_DELETE_BUTTON_TICKET_TOP = getattr(settings, 'HELPDESK_SHOW_DELETE_BUTTON_TICKET_TOP', True) + +# show hold / unhold button on top of ticket description. +HELPDESK_SHOW_HOLD_BUTTON_TICKET_TOP = getattr(settings, 'HELPDESK_SHOW_HOLD_BUTTON_TICKET_TOP', True) + +# make all updates public by default? this will hide the 'is this update public' checkbox +HELPDESK_UPDATE_PUBLIC_DEFAULT = getattr(settings, 'HELPDESK_UPDATE_PUBLIC_DEFAULT', True) + + +''' options for dashboard ''' +# show delete button next to unassigned tickets +HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED = getattr(settings, 'HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED', True) + + +''' options for footer ''' +# show 'API' link at bottom of page +HELPDESK_FOOTER_SHOW_API_LINK = getattr(settings, 'HELPDESK_FOOTER_SHOW_API_LINK', True) diff --git a/helpdesk/templates/helpdesk/attribution.html b/helpdesk/templates/helpdesk/attribution.html index 90be0ced..461621e3 100644 --- a/helpdesk/templates/helpdesk/attribution.html +++ b/helpdesk/templates/helpdesk/attribution.html @@ -1,2 +1,5 @@ {% load i18n %} -{% trans "Powered by django-helpdesk." %} \ No newline at end of file +{% trans "Powered by django-helpdesk." %} +{% if helpdesk_settings.HELPDESK_SUPPORT_PERSON %} +

{% trans "For technical support please contact:" %} {{ helpdesk_settings.HELPDESK_SUPPORT_PERSON }}

+{% endif %} diff --git a/helpdesk/templates/helpdesk/base.html b/helpdesk/templates/helpdesk/base.html index aad66a93..252e5232 100644 --- a/helpdesk/templates/helpdesk/base.html +++ b/helpdesk/templates/helpdesk/base.html @@ -1,5 +1,7 @@ {% load i18n %} {% load saved_queries %} +{% load load_helpdesk_settings %} +{% with request|load_helpdesk_settings as helpdesk_settings %} {% with request|saved_queries as user_saved_queries_ %} @@ -51,10 +53,15 @@ {% include "helpdesk/debug.html" %} {% endwith %} +{% endwith %} diff --git a/helpdesk/templates/helpdesk/dashboard.html b/helpdesk/templates/helpdesk/dashboard.html index 4a087073..bb2094fc 100644 --- a/helpdesk/templates/helpdesk/dashboard.html +++ b/helpdesk/templates/helpdesk/dashboard.html @@ -6,13 +6,14 @@ {% block helpdesk_body %} - - + + {% for queue in dash_tickets %} - - + + + {% endfor %}
{% trans "Helpdesk Summary" %}
{% trans "Queue" %}{% trans "Open" %}{% trans "Resolved" %}
{% trans "Helpdesk Summary" %}
{% trans "Queue" %}{% trans "Open" %}{% trans "Resolved" %}{% trans "Closed" %}
{{ queue.name }}{% if queue.open %}{% endif %}{{ queue.open }}{% if queue.open %}{% endif %}{% if queue.resolved %}{% endif %}{{ queue.resolved }}{% if queue.resolved %}{% endif %}{% if queue.open %}{% endif %}{{ queue.open }}{% if queue.open %}{% endif %}{% if queue.resolved %}{% endif %}{{ queue.resolved }}{% if queue.resolved %}{% endif %}{% if queue.closed %}{% endif %}{{ queue.closed }}{% if queue.closed %}{% endif %}
@@ -22,7 +23,7 @@
- + {% for ticket in user_tickets %} @@ -39,6 +40,8 @@ {% endif %}
{% trans "Your Tickets" %}
{% trans "Your Open Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Status" %}{% trans "Last Update" %}
+
+ @@ -49,7 +52,7 @@ - + {% endfor %} {% if not unassigned_tickets %} @@ -57,4 +60,24 @@ {% endif %}
{% trans "Unassigned Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Created" %} 
{{ ticket.title }} {{ ticket.queue }} {{ ticket.created|timesince }} ago{% trans "Take" %} | {% trans "Delete" %}{% trans "Take" %} {% if helpdesk_settings.HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED %}| {% trans "Delete" %}{% endif %}
+ +{% if user_tickets_closed_resolved %} +
+ + + + +{% for ticket in user_tickets_closed_resolved %} + + + + + + + + +{% endfor %} +
{% trans "Your closed & resolved Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Status" %}{% trans "Last Update" %}
{{ ticket.ticket }}{{ ticket.get_priority_span }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.get_status }}{{ ticket.modified|timesince }}
+{% endif %} + {% endblock %} diff --git a/helpdesk/templates/helpdesk/navigation.html b/helpdesk/templates/helpdesk/navigation.html index 15e38852..6fc52b0d 100644 --- a/helpdesk/templates/helpdesk/navigation.html +++ b/helpdesk/templates/helpdesk/navigation.html @@ -1,10 +1,12 @@ {% load i18n %} -{% if user.is_staff %} +{% if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED and user.is_authenticated or user.is_staff %} {% endif %} {% endfor %} {% for attachment in followup.attachment_set.all %}{% if forloop.first %}
{% endif %} {% endfor %} @@ -73,9 +81,11 @@
+ {% if preset_replies %}
(Optional)
{% trans "Selecting a pre-set reply will over-write your comment below. You can then modify the pre-set reply to your liking before saving this update." %}
+ {% endif %}
@@ -109,9 +119,13 @@ {% endifequal %} + {% if helpdesk_settings.HELPDESK_UPDATE_PUBLIC_DEFAULT %} + + {% else %}
(Optional)
-
+
{% trans "If this is public, the submitter will be e-mailed your comment or resolution." %}
+ {% endif %}

{% trans "Change Further Details »" %}

diff --git a/helpdesk/templates/helpdesk/ticket_desc_table.html b/helpdesk/templates/helpdesk/ticket_desc_table.html index 01239590..20b40b75 100644 --- a/helpdesk/templates/helpdesk/ticket_desc_table.html +++ b/helpdesk/templates/helpdesk/ticket_desc_table.html @@ -1,6 +1,15 @@ {% load i18n %} - + diff --git a/helpdesk/templatetags/load_helpdesk_settings.py b/helpdesk/templatetags/load_helpdesk_settings.py new file mode 100644 index 00000000..74f3647f --- /dev/null +++ b/helpdesk/templatetags/load_helpdesk_settings.py @@ -0,0 +1,21 @@ +""" +django-helpdesk - A Django powered ticket tracker for small enterprise. + +templatetags/load_helpdesk_settings.py - returns the settings as defined in + django-helpdesk/helpdesk/settings.py +""" + +from django.template import Library +from helpdesk import settings as helpdesk_settings_config + +def load_helpdesk_settings(request): + try: + return helpdesk_settings_config + except Exception, e: + import sys + print >> sys.stderr, "'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:" + print >> sys.stderr, e + return '' + +register = Library() +register.filter('load_helpdesk_settings', load_helpdesk_settings) diff --git a/helpdesk/urls.py b/helpdesk/urls.py index 4aa4ce7f..0e950d55 100644 --- a/helpdesk/urls.py +++ b/helpdesk/urls.py @@ -80,6 +80,10 @@ urlpatterns = patterns('helpdesk.views.staff', url(r'^tickets/(?P[0-9]+)/dependency/delete/(?P[0-9]+)/$', 'ticket_dependency_del', name='helpdesk_ticket_dependency_del'), + + url(r'^tickets/(?P[0-9]+)/attachment_delete/(?P[0-9]+)/$', + 'attachment_del', + name='helpdesk_attachment_del'), url(r'^raw/(?P\w+)/$', 'raw_details', diff --git a/helpdesk/views/public.py b/helpdesk/views/public.py index b72034b0..e7ee1682 100644 --- a/helpdesk/views/public.py +++ b/helpdesk/views/public.py @@ -22,7 +22,10 @@ from helpdesk.models import Ticket, Queue, UserSettings def homepage(request): - if request.user.is_staff: + if not request.user.is_authenticated() and helpdesk_settings.HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT: + return HttpResponseRedirect(reverse('login')) + + if (request.user.is_staff or (request.user.is_authenticated() and helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE)): try: if getattr(request.user.usersettings.settings, 'login_view_ticketlist', False): return HttpResponseRedirect(reverse('helpdesk_list')) diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index bc80f60a..44e9722c 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -28,14 +28,20 @@ from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTi from helpdesk.lib import send_templated_mail, query_to_dict, apply_query, safe_template_context from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency from helpdesk.settings import HAS_TAG_SUPPORT +from helpdesk import settings as helpdesk_settings if HAS_TAG_SUPPORT: from tagging.models import Tag, TaggedItem -staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_staff) -superuser_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_superuser) +if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: + # treat 'normal' users like 'staff' + staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active) +else: + staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_staff) +superuser_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_superuser) + def dashboard(request): """ @@ -44,12 +50,18 @@ def dashboard(request): with options for them to 'Take' ownership of said tickets. """ + # open & reopened tickets tickets = Ticket.objects.filter( assigned_to=request.user, ).exclude( - status=Ticket.CLOSED_STATUS, + status__in = [Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS], ) + # closed & resolved tickets + tickets_closed_resolved = Ticket.objects.filter( + assigned_to=request.user, + status__in = [Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS]) + unassigned_tickets = Ticket.objects.filter( assigned_to__isnull=True, ).exclude( @@ -67,7 +79,8 @@ def dashboard(request): 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 '3' THEN t.id END) AS resolved, + COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed FROM helpdesk_ticket t, helpdesk_queue q WHERE q.id = t.queue_id @@ -79,6 +92,7 @@ def dashboard(request): return render_to_response('helpdesk/dashboard.html', RequestContext(request, { 'user_tickets': tickets, + 'user_tickets_closed_resolved': tickets_closed_resolved, 'unassigned_tickets': unassigned_tickets, 'dash_tickets': dash_tickets, })) @@ -127,9 +141,18 @@ def followup_edit(request, ticket_id, followup_id, ): new_status = form.cleaned_data['new_status'] #will save previous date old_date = followup.date - followup.delete() new_followup = FollowUp(title=title, date=old_date, ticket=_ticket, comment=comment, public=public, new_status=new_status, ) + # keep old user if one did exist before. + if followup.user: + new_followup.user = followup.user new_followup.save() + # get list of old attachments & link them to new_followup + attachments = Attachment.objects.filter(followup = followup) + for attachment in attachments: + attachment.followup = new_followup + attachment.save() + # delete old followup + followup.delete() return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket.id])) def view_ticket(request, ticket_id): @@ -169,7 +192,7 @@ def view_ticket(request, ticket_id): return render_to_response('helpdesk/ticket.html', RequestContext(request, { 'ticket': ticket, - 'active_users': User.objects.filter(is_active=True).filter(is_staff=True).order_by('username'), + 'active_users': User.objects.filter(is_active=True).order_by('username'), 'priorities': Ticket.PRIORITY_CHOICES, 'preset_replies': PreSetReply.objects.filter(Q(queues=ticket.queue) | Q(queues__isnull=True)), 'tags_enabled': HAS_TAG_SUPPORT, @@ -178,7 +201,7 @@ view_ticket = staff_member_required(view_ticket) def update_ticket(request, ticket_id, public=False): - if not (public or (request.user.is_authenticated() and request.user.is_active and request.user.is_staff)): + if not (public or (request.user.is_authenticated() and request.user.is_active and (request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE))): return HttpResponseForbidden(_('Sorry, you need to login to do that.')) ticket = get_object_or_404(Ticket, id=ticket_id) @@ -195,6 +218,10 @@ def update_ticket(request, ticket_id, public=False): # comment. from django.template import loader, Context context = safe_template_context(ticket) + # this line sometimes creates problems if code is sent as a comment. + # if comment contains some django code, like "why does {% if bla %} crash", + # then the following line will give us a crash, since django expects {% if %} + # to be closed with an {% endif %} tag. comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: @@ -202,7 +229,7 @@ def update_ticket(request, ticket_id, public=False): f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) - if request.user.is_staff: + if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: f.user = request.user f.public = public @@ -217,9 +244,11 @@ def update_ticket(request, ticket_id, public=False): } ticket.assigned_to = new_user reassigned = True - elif owner == 0 and ticket.assigned_to is not None: - f.title = _('Unassigned') - ticket.assigned_to = None + # This makes no sense to me. Why should we ever remove the 'assigned to' + # value? + #elif owner == 0 and ticket.assigned_to is not None: + # f.title = _('Unassigned') + # ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status @@ -289,7 +318,7 @@ def update_ticket(request, ticket_id, public=False): c.save() ticket.tags = tags - if f.new_status == Ticket.RESOLVED_STATUS: + if new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment messages_sent_to = [] @@ -377,7 +406,7 @@ def update_ticket(request, ticket_id, public=False): ticket.save() - if request.user.is_staff: + if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url) @@ -419,11 +448,11 @@ def mass_update(request): f = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS) f.save() # Send email to Submitter, Owner, Queue CC - context = { - 'ticket': t, - 'queue': t.queue, - 'resolution': t.resolution, - } + context = safe_template_context(t) + context.update( + resolution = t.resolution, + queue = t.queue, + ) messages_sent_to = [] @@ -833,7 +862,7 @@ def run_report(request, report): month = 1 if (year > last_year) or (month > last_month and year >= last_year): working = False - periods.append("%s %s" % (months[month], year)) + periods.append("%s %s" % (months[month - 1], year)) if report == 'userpriority': title = _('User by Priority') @@ -1087,3 +1116,10 @@ def ticket_dependency_del(request, ticket_id, dependency_id): })) ticket_dependency_del = staff_member_required(ticket_dependency_del) +def attachment_del(request, ticket_id, attachment_id): + ticket = get_object_or_404(Ticket, id=ticket_id) + attachment = get_object_or_404(Attachment, id=attachment_id) + attachment.delete() + return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id])) +attachment_del = staff_member_required(attachment_del) +
{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] EditDelete{% if ticket.on_hold %}{% trans "Unhold" %}{% else %}{% trans "Hold" %}{% endif %}
{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] + {% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_TICKET_TOP %} + Edit + {% endif %} + {% if helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_TICKET_TOP %} + Delete + {% endif %} + {% if helpdesk_settings.HELPDESK_SHOW_HOLD_BUTTON_TICKET_TOP %} + {% if ticket.on_hold %}{% trans "Unhold" %}{% else %}{% trans "Hold" %}{% endif %} + {% endif %}
{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}