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
This commit is contained in:
Andreas Kotowicz 2011-11-19 09:34:07 +01:00
parent 6e33408a7d
commit cb34b1933a
15 changed files with 237 additions and 36 deletions

View File

@ -11,6 +11,7 @@ from datetime import datetime
from StringIO import StringIO from StringIO import StringIO
from django import forms from django import forms
from django.forms import extras
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -195,6 +196,7 @@ class TicketForm(forms.Form):
fieldclass = forms.BooleanField fieldclass = forms.BooleanField
elif field.data_type == 'date': elif field.data_type == 'date':
fieldclass = forms.DateField fieldclass = forms.DateField
instanceargs['widget'] = extras.SelectDateWidget
elif field.data_type == 'time': elif field.data_type == 'time':
fieldclass = forms.TimeField fieldclass = forms.TimeField
elif field.data_type == 'datetime': elif field.data_type == 'datetime':

View File

@ -54,7 +54,13 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
import os import os
context = Context(email_context) context = Context(email_context)
if hasattr(context['queue'], 'locale'):
locale = getattr(context['queue'], 'locale', '')
else:
locale = context['queue'].get('locale', 'en') locale = context['queue'].get('locale', 'en')
if not locale:
locale = 'en'
t = None t = None
try: try:

View File

@ -28,5 +28,59 @@ if type(DEFAULT_USER_SETTINGS) != type(dict()):
'tickets_per_page': 25 '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? # show knowledgebase links?
HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True) 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)

View File

@ -1,2 +1,5 @@
{% load i18n %} {% load i18n %}
{% trans "Powered by <a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>." %} {% trans "Powered by <a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>." %}
{% if helpdesk_settings.HELPDESK_SUPPORT_PERSON %}
<p>{% trans "For technical support please contact:" %} <a href='mailto:{{ helpdesk_settings.HELPDESK_SUPPORT_PERSON }}'>{{ helpdesk_settings.HELPDESK_SUPPORT_PERSON }}</a></p>
{% endif %}

View File

@ -1,5 +1,7 @@
{% load i18n %} {% load i18n %}
{% load saved_queries %} {% load saved_queries %}
{% load load_helpdesk_settings %}
{% with request|load_helpdesk_settings as helpdesk_settings %}
{% with request|saved_queries as user_saved_queries_ %} {% with request|saved_queries as user_saved_queries_ %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
@ -51,10 +53,15 @@
</div> </div>
<div id='footer'> <div id='footer'>
<p>{% include "helpdesk/attribution.html" %}<a href='{% url helpdesk_rss_index %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a> <a href='{% url helpdesk_api_help %}'>{% trans "API" %}</a> <a href='{% url helpdesk_user_settings %}'>{% trans "User Settings" %}</a> {% if user.is_superuser %}<a href='{% url helpdesk_system_settings %}'>{% trans "System Settings" %}</a>{% endif %}</p> <p>{% include "helpdesk/attribution.html" %}
<a href='{% url helpdesk_rss_index %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a>
{% if helpdesk_settings.HELPDESK_FOOTER_SHOW_API_LINK %}<a href='{% url helpdesk_api_help %}'>{% trans "API" %}</a>{% endif %}
<a href='{% url helpdesk_user_settings %}'>{% trans "User Settings" %}</a>
{% if user.is_superuser %}<a href='{% url helpdesk_system_settings %}'>{% trans "System Settings" %}</a>{% endif %}</p>
</div> </div>
</div> </div>
{% include "helpdesk/debug.html" %} {% include "helpdesk/debug.html" %}
</body> </body>
</html> </html>
{% endwith %} {% endwith %}
{% endwith %}

View File

@ -6,13 +6,14 @@
{% block helpdesk_body %} {% block helpdesk_body %}
<table width='40%' align='left'> <table width='40%' align='left'>
<tr class='row_tablehead'><td colspan='4'>{% trans "Helpdesk Summary" %}</td></tr> <tr class='row_tablehead'><td colspan='5'>{% trans "Helpdesk Summary" %}</td></tr>
<tr class='row_columnheads'><th>{% trans "Queue" %}</th><th>{% trans "Open" %}</th><th>{% trans "Resolved" %}</th></tr> <tr class='row_columnheads'><th>{% trans "Queue" %}</th><th>{% trans "Open" %}</th><th>{% trans "Resolved" %}</th><th>{% trans "Closed" %}</th></tr>
{% for queue in dash_tickets %} {% for queue in dash_tickets %}
<tr class='row_{% cycle odd,even %} row_hover '> <tr class='row_{% cycle odd,even %} row_hover '>
<th><a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=1&status=2'>{{ queue.name }}</a></th> <th><a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=1&status=2'>{{ queue.name }}</a></th>
<td>{% if queue.open %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td> <td align="center">{% if queue.open %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td>
<td>{% if queue.resolved %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td> <td align="center">{% if queue.resolved %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>
<td align="center">{% if queue.closed %}<a href='{% url helpdesk_list %}?queue={{ queue.queue }}&status=4'>{% endif %}{{ queue.closed }}{% if queue.closed %}</a>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -22,7 +23,7 @@
<br style='clear: both;' /> <br style='clear: both;' />
<table width='100%'> <table width='100%'>
<tr class='row_tablehead'><td colspan='6'>{% trans "Your Tickets" %}</td></tr> <tr class='row_tablehead'><td colspan='6'>{% trans "Your Open Tickets" %}</td></tr>
<tr class='row_columnheads'><th>#</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Status" %}</th><th>{% trans "Last Update" %}</th></tr> <tr class='row_columnheads'><th>#</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Status" %}</th><th>{% trans "Last Update" %}</th></tr>
{% for ticket in user_tickets %} {% for ticket in user_tickets %}
<tr class='row_{% cycle odd,even %} row_hover'> <tr class='row_{% cycle odd,even %} row_hover'>
@ -39,6 +40,8 @@
{% endif %} {% endif %}
</table> </table>
<br style='clear: both;' />
<table width='100%'> <table width='100%'>
<tr class='row_tablehead'><td colspan='6'>{% trans "Unassigned Tickets" %}</td></tr> <tr class='row_tablehead'><td colspan='6'>{% trans "Unassigned Tickets" %}</td></tr>
<tr class='row_columnheads'><th>#</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Created" %}</th><th>&nbsp;</th></tr> <tr class='row_columnheads'><th>#</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Created" %}</th><th>&nbsp;</th></tr>
@ -49,7 +52,7 @@
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th> <th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
<td>{{ ticket.queue }}</td> <td>{{ ticket.queue }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|timesince }} ago</span></td> <td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|timesince }} ago</span></td>
<th><a href='{{ ticket.get_absolute_url }}?take'><span class='button button_take'>{% trans "Take" %}</span></a> | <a href='{% url helpdesk_delete ticket.id %}'><span class='button button_delete'>{% trans "Delete" %}</span></a></th> <th><a href='{{ ticket.get_absolute_url }}?take'><span class='button button_take'>{% trans "Take" %}</span></a> {% if helpdesk_settings.HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED %}| <a href='{% url helpdesk_delete ticket.id %}'><span class='button button_delete'>{% trans "Delete" %}</span></a>{% endif %}</th>
</tr> </tr>
{% endfor %} {% endfor %}
{% if not unassigned_tickets %} {% if not unassigned_tickets %}
@ -57,4 +60,24 @@
{% endif %} {% endif %}
</table> </table>
{% if user_tickets_closed_resolved %}
<br style='clear: both;' />
<table width='100%'>
<tr class='row_tablehead'><td colspan='6'>{% trans "Your closed & resolved Tickets" %}</td></tr>
<tr class='row_columnheads'><th>#</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Status" %}</th><th>{% trans "Last Update" %}</th></tr>
{% for ticket in user_tickets_closed_resolved %}
<tr class='row_{% cycle odd,even %} row_hover'>
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
<td>{{ ticket.get_priority_span }}</td>
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
<td>{{ ticket.queue }}</td>
<td>{{ ticket.get_status }}</td>
<td><span title='{{ ticket.modified|date:"r" }}'>{{ ticket.modified|timesince }}</span></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,10 +1,12 @@
{% load i18n %} {% load i18n %}
{% if user.is_staff %} {% if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED and user.is_authenticated or user.is_staff %}
<ul id="dropdown"> <ul id="dropdown">
<li><a href='{% url helpdesk_dashboard %}'>{% trans "Dashboard" %}</a></li> <li><a href='{% url helpdesk_dashboard %}'>{% trans "Dashboard" %}</a></li>
<li><a href='{% url helpdesk_list %}'>{% trans "Tickets" %}</a></li> <li><a href='{% url helpdesk_list %}'>{% trans "Tickets" %}</a></li>
<li><a href='{% url helpdesk_submit %}'>{% trans "New Ticket" %}</a></li> <li><a href='{% url helpdesk_submit %}'>{% trans "New Ticket" %}</a></li>
{% if helpdesk_settings.HELPDESK_NAVIGATION_STATS_ENABLED %}
<li><a href='{% url helpdesk_report_index %}'>{% trans "Stats" %}</a></li> <li><a href='{% url helpdesk_report_index %}'>{% trans "Stats" %}</a></li>
{% endif %}
{% if user_saved_queries_ %} {% if user_saved_queries_ %}
<li class="headerlink"><a>Load Saved Query</a> <li class="headerlink"><a>Load Saved Query</a>
<ul> <ul>
@ -22,8 +24,12 @@
</ul> </ul>
{% else %} {% else %}
<ul> <ul>
{% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
<li><a href='{% url helpdesk_home %}'>{% trans "Submit A Ticket" %}</a></li> <li><a href='{% url helpdesk_home %}'>{% trans "Submit A Ticket" %}</a></li>
{% endif %}
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}<li><a href='{% url helpdesk_kb_index %}'>{% trans "Knowledgebase" %}</a></li>{% endif %} {% if helpdesk_settings.HELPDESK_KB_ENABLED %}<li><a href='{% url helpdesk_kb_index %}'>{% trans "Knowledgebase" %}</a></li>{% endif %}
<li><a href='{% url login %}?next={% url helpdesk_dashboard %}'>{% trans "Log In" %}</a></li> {% if not helpdesk_settings.HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT or user.is_authenticated %}
<li>{% if user.is_authenticated %}<a href='{% url logout %}'>{% trans "Logout" %}</a>{% else %}<a href='{% url login %}?next={% url helpdesk_dashboard %}'>{% trans "Log In" %}</a>{% endif %}</li>
{% endif %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -1,4 +1,7 @@
{% load i18n %}<html> {% load i18n %}
{% load load_helpdesk_settings %}
{% with request|load_helpdesk_settings as helpdesk_settings %}
<html>
<head> <head>
<title>{% block helpdesk_title %}{% trans "Helpdesk" %}{% endblock %}</title> <title>{% block helpdesk_title %}{% trans "Helpdesk" %}{% endblock %}</title>
<script src='{{ STATIC_URL }}helpdesk/jquery-1.5.min.js' type='text/javascript' language='javascript'></script> <script src='{{ STATIC_URL }}helpdesk/jquery-1.5.min.js' type='text/javascript' language='javascript'></script>
@ -20,3 +23,4 @@
</div>{% include "helpdesk/debug.html" %} </div>{% include "helpdesk/debug.html" %}
</body> </body>
</html> </html>
{% endwith %}

View File

@ -1,6 +1,7 @@
{% extends "helpdesk/public_base.html" %}{% load i18n %} {% extends "helpdesk/public_base.html" %}{% load i18n %}
{% block helpdesk_body %} {% block helpdesk_body %}
{% if helpdesk_settings.HELPDESK_VIEW_A_TICKET_PUBLIC %}
<h2>{% trans "View a Ticket" %}</h2> <h2>{% trans "View a Ticket" %}</h2>
<form method='get' action='{% url helpdesk_public_view %}'> <form method='get' action='{% url helpdesk_public_view %}'>
@ -16,7 +17,9 @@
<input type='submit' value='{% trans "View Ticket" %}' /> <input type='submit' value='{% trans "View Ticket" %}' />
</fieldset> </fieldset>
{% csrf_token %}</form> {% csrf_token %}</form>
{% endif %}
{% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
<h2 name='submit'>{% trans "Submit a Ticket" %}</h2> <h2 name='submit'>{% trans "Submit a Ticket" %}</h2>
<p>{% trans "All fields are required. Please provide as descriptive a title and description as possible." %}</p> <p>{% trans "All fields are required. Please provide as descriptive a title and description as possible." %}</p>
@ -42,4 +45,10 @@
</fieldset> </fieldset>
{% csrf_token %}</form> {% csrf_token %}</form>
{% endif %}
{% if not helpdesk_settings.HELPDESK_VIEW_A_TICKET_PUBLIC and not helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
<h2>{% trans "Please use button at upper right to login first." %}</h2>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -51,7 +51,11 @@
<div class='followup'> <div class='followup'>
<div class='title'> <div class='title'>
{{ followup.title }} <span class='byline'>{% if followup.user %}by {{ followup.user }}{% endif %} <span title='{{ followup.date|date:"r" }}'>{{ followup.date|timesince }} ago</span>{% if not followup.public %} <span class='private'>({% trans "Private" %})</span>{% endif %}</span> {{ followup.title }} <span class='byline'>{% if followup.user %}by {{ followup.user }}{% endif %} <span title='{{ followup.date|date:"r" }}'>{{ followup.date|timesince }} ago</span>{% if not followup.public %} <span class='private'>({% trans "Private" %})</span>{% endif %}</span>
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
{% if followup.user and request.user == followup.user and not followup.ticketchange_set.all %}
<a href="{% url helpdesk_followup_edit ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Edit" alt="Edit" src="{{ STATIC_URL }}helpdesk/buttons/edit.png"></a> <a href="{% url helpdesk_followup_edit ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Edit" alt="Edit" src="{{ STATIC_URL }}helpdesk/buttons/edit.png"></a>
{% endif %}
{% endif %}
</div> </div>
<span class='followup-desc'>{% if followup.comment %}{{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }}{% endif %}</span> <span class='followup-desc'>{% if followup.comment %}{{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }}{% endif %}</span>
{% for change in followup.ticketchange_set.all %} {% for change in followup.ticketchange_set.all %}
@ -60,7 +64,11 @@
{% if forloop.last %}</div></ul>{% endif %} {% if forloop.last %}</div></ul>{% endif %}
{% endfor %} {% endfor %}
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<div class='attachments'><ul>{% endif %} {% for attachment in followup.attachment_set.all %}{% if forloop.first %}<div class='attachments'><ul>{% endif %}
<li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})</li> <li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
{% if followup.user and request.user == followup.user %}
<a href='{% url helpdesk_attachment_del ticket.id attachment.id %}'>delete</a>
{% endif %}
</li>
{% if forloop.last %}</ul></div>{% endif %} {% if forloop.last %}</ul></div>{% endif %}
{% endfor %} {% endfor %}
</div> </div>
@ -73,9 +81,11 @@
<fieldset> <fieldset>
<dl> <dl>
{% if preset_replies %}
<dt><label for='id_preset'>{% trans "Use a Pre-set Reply" %}</label> <span class='form_optional'>(Optional)</span></dt> <dt><label for='id_preset'>{% trans "Use a Pre-set Reply" %}</label> <span class='form_optional'>(Optional)</span></dt>
<dd><select name='preset' id='id_preset'><option value=''>------</option>{% for preset in preset_replies %}<option value='{{ preset.id }}'>{{ preset.name }}</option>{% endfor %}</select></dd> <dd><select name='preset' id='id_preset'><option value=''>------</option>{% for preset in preset_replies %}<option value='{{ preset.id }}'>{{ preset.name }}</option>{% endfor %}</select></dd>
<dd class='form_help_text'>{% 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." %}</dd> <dd class='form_help_text'>{% 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." %}</dd>
{% endif %}
<dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt> <dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt>
<dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd> <dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd>
@ -109,9 +119,13 @@
<input type='radio' name='new_status' value='5' id='st_duplicate' checked='checked'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd> <input type='radio' name='new_status' value='5' id='st_duplicate' checked='checked'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd>
{% endifequal %} {% endifequal %}
{% if helpdesk_settings.HELPDESK_UPDATE_PUBLIC_DEFAULT %}
<input type='hidden' name='public' value='1'>
{% else %}
<dt><label for='id_public'>{% trans "Is this update public?" %}</label> <span class='form_optional'>(Optional)</span></dt> <dt><label for='id_public'>{% trans "Is this update public?" %}</label> <span class='form_optional'>(Optional)</span></dt>
<dd><input type='checkbox' name='public' value='1' checked='checked' /></dD> <dd><input type='checkbox' name='public' value='1' checked='checked' /></dd>
<dd class='form_help_text'>{% trans "If this is public, the submitter will be e-mailed your comment or resolution." %}</dd> <dd class='form_help_text'>{% trans "If this is public, the submitter will be e-mailed your comment or resolution." %}</dd>
{% endif %}
</dl> </dl>
<p id='ShowFurtherOptPara'><a href='#' id='ShowFurtherEditOptions'>{% trans "Change Further Details &raquo;" %}</a></p> <p id='ShowFurtherOptPara'><a href='#' id='ShowFurtherEditOptions'>{% trans "Change Further Details &raquo;" %}</a></p>

View File

@ -1,6 +1,15 @@
{% load i18n %} {% load i18n %}
<table width='100%'> <table width='100%'>
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] <span class='ticket_toolbar'><a href='{% url helpdesk_edit ticket.id %}'><img src='{{ STATIC_URL }}helpdesk/buttons/edit.png' alt='Edit' title='Edit' width='60' height='15' /></a><a href='{% url helpdesk_delete ticket.id %}'><img src='{{ STATIC_URL }}helpdesk/buttons/delete.png' alt='Delete' title='Delete' width='60' height='15' /></a>{% if ticket.on_hold %}<a href='unhold/'>{% trans "Unhold" %}</a>{% else %}<a href='hold/'>{% trans "Hold" %}</a>{% endif %}</span></td></tr> <tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] <span class='ticket_toolbar'>
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_TICKET_TOP %}
<a href='{% url helpdesk_edit ticket.id %}'><img src='{{ STATIC_URL }}helpdesk/buttons/edit.png' alt='Edit' title='Edit' width='60' height='15' /></a>
{% endif %}
{% if helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_TICKET_TOP %}
<a href='{% url helpdesk_delete ticket.id %}'><img src='{{ STATIC_URL }}helpdesk/buttons/delete.png' alt='Delete' title='Delete' width='60' height='15' /></a>
{% endif %}
{% if helpdesk_settings.HELPDESK_SHOW_HOLD_BUTTON_TICKET_TOP %}
{% if ticket.on_hold %}<a href='unhold/'>{% trans "Unhold" %}</a>{% else %}<a href='hold/'>{% trans "Hold" %}</a>{% endif %}
{% endif %}</span></td></tr>
<tr class='row_columnheads'><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr> <tr class='row_columnheads'><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
<tr class='{% cycle 'row_odd' 'row_even' as rowcolors %}'> <tr class='{% cycle 'row_odd' 'row_even' as rowcolors %}'>

View File

@ -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)

View File

@ -81,6 +81,10 @@ urlpatterns = patterns('helpdesk.views.staff',
'ticket_dependency_del', 'ticket_dependency_del',
name='helpdesk_ticket_dependency_del'), name='helpdesk_ticket_dependency_del'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/attachment_delete/(?P<attachment_id>[0-9]+)/$',
'attachment_del',
name='helpdesk_attachment_del'),
url(r'^raw/(?P<type>\w+)/$', url(r'^raw/(?P<type>\w+)/$',
'raw_details', 'raw_details',
name='helpdesk_raw'), name='helpdesk_raw'),

View File

@ -22,7 +22,10 @@ from helpdesk.models import Ticket, Queue, UserSettings
def homepage(request): 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: try:
if getattr(request.user.usersettings.settings, 'login_view_ticketlist', False): if getattr(request.user.usersettings.settings, 'login_view_ticketlist', False):
return HttpResponseRedirect(reverse('helpdesk_list')) return HttpResponseRedirect(reverse('helpdesk_list'))

View File

@ -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.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.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency
from helpdesk.settings import HAS_TAG_SUPPORT from helpdesk.settings import HAS_TAG_SUPPORT
from helpdesk import settings as helpdesk_settings
if HAS_TAG_SUPPORT: if HAS_TAG_SUPPORT:
from tagging.models import Tag, TaggedItem 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) if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
superuser_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_superuser) # 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): def dashboard(request):
""" """
@ -44,12 +50,18 @@ def dashboard(request):
with options for them to 'Take' ownership of said tickets. with options for them to 'Take' ownership of said tickets.
""" """
# open & reopened tickets
tickets = Ticket.objects.filter( tickets = Ticket.objects.filter(
assigned_to=request.user, assigned_to=request.user,
).exclude( ).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( unassigned_tickets = Ticket.objects.filter(
assigned_to__isnull=True, assigned_to__isnull=True,
).exclude( ).exclude(
@ -67,7 +79,8 @@ def dashboard(request):
SELECT q.id as queue, SELECT q.id as queue,
q.title AS name, 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 '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, FROM helpdesk_ticket t,
helpdesk_queue q helpdesk_queue q
WHERE q.id = t.queue_id WHERE q.id = t.queue_id
@ -79,6 +92,7 @@ def dashboard(request):
return render_to_response('helpdesk/dashboard.html', return render_to_response('helpdesk/dashboard.html',
RequestContext(request, { RequestContext(request, {
'user_tickets': tickets, 'user_tickets': tickets,
'user_tickets_closed_resolved': tickets_closed_resolved,
'unassigned_tickets': unassigned_tickets, 'unassigned_tickets': unassigned_tickets,
'dash_tickets': dash_tickets, 'dash_tickets': dash_tickets,
})) }))
@ -127,9 +141,18 @@ def followup_edit(request, ticket_id, followup_id, ):
new_status = form.cleaned_data['new_status'] new_status = form.cleaned_data['new_status']
#will save previous date #will save previous date
old_date = followup.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, ) 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() 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])) return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket.id]))
def view_ticket(request, 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', return render_to_response('helpdesk/ticket.html',
RequestContext(request, { RequestContext(request, {
'ticket': ticket, '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, 'priorities': Ticket.PRIORITY_CHOICES,
'preset_replies': PreSetReply.objects.filter(Q(queues=ticket.queue) | Q(queues__isnull=True)), 'preset_replies': PreSetReply.objects.filter(Q(queues=ticket.queue) | Q(queues__isnull=True)),
'tags_enabled': HAS_TAG_SUPPORT, 'tags_enabled': HAS_TAG_SUPPORT,
@ -178,7 +201,7 @@ view_ticket = staff_member_required(view_ticket)
def update_ticket(request, ticket_id, public=False): 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.')) return HttpResponseForbidden(_('Sorry, you need to login to do that.'))
ticket = get_object_or_404(Ticket, id=ticket_id) ticket = get_object_or_404(Ticket, id=ticket_id)
@ -195,6 +218,10 @@ def update_ticket(request, ticket_id, public=False):
# comment. # comment.
from django.template import loader, Context from django.template import loader, Context
context = safe_template_context(ticket) 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)) comment = loader.get_template_from_string(comment).render(Context(context))
if owner is None and ticket.assigned_to: 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) 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.user = request.user
f.public = public f.public = public
@ -217,9 +244,11 @@ def update_ticket(request, ticket_id, public=False):
} }
ticket.assigned_to = new_user ticket.assigned_to = new_user
reassigned = True reassigned = True
elif owner == 0 and ticket.assigned_to is not None: # This makes no sense to me. Why should we ever remove the 'assigned to'
f.title = _('Unassigned') # value?
ticket.assigned_to = None #elif owner == 0 and ticket.assigned_to is not None:
# f.title = _('Unassigned')
# ticket.assigned_to = None
if new_status != ticket.status: if new_status != ticket.status:
ticket.status = new_status ticket.status = new_status
@ -289,7 +318,7 @@ def update_ticket(request, ticket_id, public=False):
c.save() c.save()
ticket.tags = tags ticket.tags = tags
if f.new_status == Ticket.RESOLVED_STATUS: if new_status == Ticket.RESOLVED_STATUS:
ticket.resolution = comment ticket.resolution = comment
messages_sent_to = [] messages_sent_to = []
@ -377,7 +406,7 @@ def update_ticket(request, ticket_id, public=False):
ticket.save() 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()) return HttpResponseRedirect(ticket.get_absolute_url())
else: else:
return HttpResponseRedirect(ticket.ticket_url) 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 = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS)
f.save() f.save()
# Send email to Submitter, Owner, Queue CC # Send email to Submitter, Owner, Queue CC
context = { context = safe_template_context(t)
'ticket': t, context.update(
'queue': t.queue, resolution = t.resolution,
'resolution': t.resolution, queue = t.queue,
} )
messages_sent_to = [] messages_sent_to = []
@ -833,7 +862,7 @@ def run_report(request, report):
month = 1 month = 1
if (year > last_year) or (month > last_month and year >= last_year): if (year > last_year) or (month > last_month and year >= last_year):
working = False working = False
periods.append("%s %s" % (months[month], year)) periods.append("%s %s" % (months[month - 1], year))
if report == 'userpriority': if report == 'userpriority':
title = _('User by Priority') 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) 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)