* Create new help page for comment template context variables

( see /help/context/; also linked from comment form)
* Refactor API help page to share template with context help
* Allow a limited number of Ticket & Queue model fields to be 
  accessible in comments, as per 'Help' page.
* New function in lib.py to build a dict of 'safe' fields from 
  ticket & queue, to prevent the power of the Django model API 
  from exposing things like passwords (imagine if a user typed
  a comment containing {{ ticket.queue.email_box_password }} !!!!
* When accessing the ticket list with no filter params (eg by 
  clicking on the "Tickets" button in the menu), the default 
  search is for tickets that aren't closed, rather than showing
  all tickets.
* Updated English locale with changed message strings.
This commit is contained in:
Ross Poulton 2008-08-29 09:11:02 +00:00
parent 0068eccbf4
commit a162d77d70
10 changed files with 285 additions and 64 deletions

46
lib.py
View File

@ -279,3 +279,49 @@ def apply_query(queryset, params):
queryset = queryset.order_by(params['sorting']) queryset = queryset.order_by(params['sorting'])
return queryset return queryset
def safe_template_context(ticket):
"""
Return a dictionary that can be used as a template context to render
comments and other details with ticket or queue paramaters. Note that
we don't just provide the Ticket & Queue objects to the template as
they could reveal confidential information. Just imagine these two options:
* {{ ticket.queue.email_box_password }}
* {{ ticket.assigned_to.password }}
Ouch!
The downside to this is that if we make changes to the model, we will also
have to update this code. Perhaps we can find a better way in the future.
"""
context = {
'queue': {},
'ticket': {},
}
queue = ticket.queue
for field in ( 'title', 'slug', 'email_address', 'from_address'):
attr = getattr(queue, field, None)
if callable(attr):
context['queue'][field] = attr()
else:
context['queue'][field] = attr
for field in ( 'title', 'created', 'modified', 'submitter_email',
'status', 'get_status_display', 'on_hold', 'description',
'resolution', 'priority', 'get_priority_display',
'last_escalation', 'ticket', 'ticket_for_url',
'get_status', 'ticket_url', 'staff_url', '_get_assigned_to'
):
attr = getattr(ticket, field, None)
if callable(attr):
context['ticket'][field] = '%s' % attr()
else:
context['ticket'][field] = attr
context['ticket']['queue'] = context['queue']
context['ticket']['assigned_to'] = context['ticket']['_get_assigned_to']
return context

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-08-28 02:50+0000\n" "POT-Creation-Date: 2008-08-29 05:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -54,7 +54,7 @@ msgstr ""
#: forms.py:58 models.py:301 management/commands/escalate_tickets.py:152 #: forms.py:58 models.py:301 management/commands/escalate_tickets.py:152
#: templates/helpdesk/public_view_ticket.html:29 #: templates/helpdesk/public_view_ticket.html:29
#: templates/helpdesk/ticket.html:67 templates/helpdesk/ticket.html.py:159 #: templates/helpdesk/ticket.html:67 templates/helpdesk/ticket.html.py:159
#: templates/helpdesk/ticket_list.html:43 views/staff.py:186 #: templates/helpdesk/ticket_list.html:43 views/staff.py:192
msgid "Priority" msgid "Priority"
msgstr "" msgstr ""
@ -108,7 +108,7 @@ msgstr ""
#: models.py:29 models.py:239 models.py:446 models.py:740 models.py:771 #: models.py:29 models.py:239 models.py:446 models.py:740 models.py:771
#: templates/helpdesk/dashboard.html:26 templates/helpdesk/dashboard.html:41 #: templates/helpdesk/dashboard.html:26 templates/helpdesk/dashboard.html:41
#: templates/helpdesk/ticket.html:153 templates/helpdesk/ticket_list.html:40 #: templates/helpdesk/ticket.html:153 templates/helpdesk/ticket_list.html:40
#: templates/helpdesk/ticket_list.html:115 views/staff.py:176 #: templates/helpdesk/ticket_list.html:115 views/staff.py:182
msgid "Title" msgid "Title"
msgstr "" msgstr ""
@ -378,7 +378,7 @@ msgid ""
msgstr "" msgstr ""
#: models.py:321 templates/helpdesk/ticket.html:58 views/feeds.py:91 #: models.py:321 templates/helpdesk/ticket.html:58 views/feeds.py:91
#: views/feeds.py:117 views/feeds.py:171 views/staff.py:153 #: views/feeds.py:117 views/feeds.py:171 views/staff.py:159
msgid "Unassigned" msgid "Unassigned"
msgstr "" msgstr ""
@ -390,7 +390,7 @@ msgstr ""
msgid "Date" msgid "Date"
msgstr "" msgstr ""
#: models.py:453 views/staff.py:167 #: models.py:453 views/staff.py:173
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
@ -1079,8 +1079,8 @@ msgstr ""
#: templates/helpdesk/ticket.html:119 #: templates/helpdesk/ticket.html:119
msgid "" msgid ""
"You can use the {{ ticket }} and {{ queue }} template variables in your " "You can insert ticket and queue details in your message. For more "
"message." "information, see the <a href='../../help/context/'>context help page</a>."
msgstr "" msgstr ""
#: templates/helpdesk/ticket.html:142 #: templates/helpdesk/ticket.html:142
@ -1289,19 +1289,19 @@ msgstr ""
msgid "Accepted resolution and closed ticket" msgid "Accepted resolution and closed ticket"
msgstr "" msgstr ""
#: views/staff.py:147 #: views/staff.py:153
#, python-format #, python-format
msgid "Assigned to %(username)s" msgid "Assigned to %(username)s"
msgstr "" msgstr ""
#: views/staff.py:169 #: views/staff.py:175
msgid "Updated" msgid "Updated"
msgstr "" msgstr ""
#: views/staff.py:401 #: views/staff.py:422
msgid "Ticket taken off hold" msgid "Ticket taken off hold"
msgstr "" msgstr ""
#: views/staff.py:404 #: views/staff.py:425
msgid "Ticket placed on hold" msgid "Ticket placed on hold"
msgstr "" msgstr ""

View File

@ -1,46 +1,9 @@
<html> {% extends "helpdesk/help_base.html" %}
<head>
<style type='text/css'>
body {
background-color: #fff;
color: #333;
font: 10pt "Trebuchet MS", Arial, sans-serif;
}
h1, h2, h3, h4 { {% block title %}Jutda Helpdesk API Documentation{% endblock %}
font-family: Garamond, Times, serif; {% block heading %}Jutda Helpdesk API Documentation{% endblock %}
}
h1 {
color: #00c;
font-size: 18pt;
border-bottom: solid 1px #00c;
}
h2 {
color: #c00;
font-size: 17pt;
border-bottom: solid 1px #c00;
}
h3 {
font-size: 14pt;
border-bottom: solid 1px #ccc;
}
h4 {
font-size: 13pt;
}
dl {
padding-left: 2em;
}
</style>
<title>Jutda Helpdesk API Documentation</title>
</head>
<body>
<h1>Jutda Helpdesk API Documentation</h1>
{% block content %}
<h2>Contents</h2> <h2>Contents</h2>
<ul> <ul>
@ -305,5 +268,4 @@ echo $result;
<p>This method responds with <strong>plain-text</strong>.</p> <p>This method responds with <strong>plain-text</strong>.</p>
<p>If you receive a 200 OK <a href='#response'>response</a>, then the content of the response will be the users ID.</p> <p>If you receive a 200 OK <a href='#response'>response</a>, then the content of the response will be the users ID.</p>
</body> {% endblock %}
</html>

View File

@ -0,0 +1,46 @@
<html>
<head>
<style type='text/css'>
body {
background-color: #fff;
color: #333;
font: 10pt "Trebuchet MS", Arial, sans-serif;
}
h1, h2, h3, h4 {
font-family: Garamond, Times, serif;
}
h1 {
color: #00c;
font-size: 18pt;
border-bottom: solid 1px #00c;
}
h2 {
color: #c00;
font-size: 17pt;
border-bottom: solid 1px #c00;
}
h3 {
font-size: 14pt;
border-bottom: solid 1px #ccc;
}
h4 {
font-size: 13pt;
}
dl {
padding-left: 2em;
}
</style>
<title>{% block title %}Jutda Helpdesk Help{% endblock %}</title>
</head>
<body>
<h1>{% block heading %}Jutda Helpdesk Help{% endblock %}</h1>
{% block content %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,139 @@
{% extends "helpdesk/help_base.html" %}
{% block title %}Jutda Helpdesk Context Listing{% endblock %}
{% block heading %}Jutda Helpdesk Context Listing{% endblock %}
{% block content %}
<h2>Contents</h2>
<ul>
<li><a href='#introduction'>Introduction</a></li>
<li><a href='#usage'>Inserting Fields</a></li>
<li><a href='#queue'>Queue Fields</a>
<ul>
<li><a href='#queue_title'>queue.title</a></li>
<li><a href='#queue_slug'>queue.slug</a></li>
<li><a href='#queue_email_address'>queue.email_address</a></li>
<li><a href='#queue_from_address'>queue.from_address</a></li>
</ul>
</li>
<li><a href='#ticket'>Ticket Fields</a>
<ul>
<li><a href='#ticket_title'>ticket.title</a></li>
<li><a href='#ticket_created'>ticket.created</a></li>
<li><a href='#ticket_modified'>ticket.modified</a></li>
<li><a href='#ticket_submitter_email'>ticket.submitter_email</a></li>
<li><a href='#ticket_status'>ticket.status</a></li>
<li><a href='#ticket_get_status_display'>ticket.get_status_display</a></li>
<li><a href='#ticket_on_hold'>ticket.on_hold</a></li>
<li><a href='#ticket_description'>ticket.description</a></li>
<li><a href='#ticket_resolution'>ticket.resolution</a></li>
<li><a href='#ticket_priority'>ticket.priority</a></li>
<li><a href='#ticket_get_priority_display'>ticket.get_priority_display</a></li>
<li><a href='#ticket_last_escalation'>ticket.last_escalation</a></li>
<li><a href='#ticket_ticket'>ticket.ticket</a></li>
<li><a href='#ticket_ticket_for_url'>ticket.ticket_for_url</a></li>
<li><a href='#ticket_get_status'>ticket.get_status</a></li>
<li><a href='#ticket_ticket_url'>ticket.ticket_url</a></li>
<li><a href='#ticket_staff_url'>ticket.staff_url</a></li>
<li><a href='#ticket_assigned_to'>ticket.assigned_to</a></li>
<li><a href='#ticket_queue'>ticket.queue</a></li>
</ul>
</li>
</ul>
<h2 id='introduction'>Introduction</h2>
<p>Jutda Helpdesk provides a powerful way for you to embed fields from the current ticket into your comments and ticket resolutions using a template language.</p>
<p>For example, you may want to place the last escalation date into a ticket comment, or reproduce the original description sent to you by the submitter.</p>
<p><strong>Tech Note:</strong> This system uses the <a href='http://docs.djangoproject.com/en/dev/topics/templates/#variables'>Django templating system</a> with a sandboxed template context.</p>
<h2 id='usage'>Inserting Fields</h2>
<p>In your comment, enter the field name surrounded by double curly braces. For example, to get the ticket title, use:</p>
<pre>&#123;&#123; ticket.title &#125;&#125;</pre>
<h2 id='queue'>Queue Fields</h2>
<p>The following fields are available in the template context to display the details of the queue to which this ticket belongs:</p>
<dl>
<dt id='queue_title'>queue.title</dt>
<dd>The display title of the queue</dd>
<dt id='queue_slug'>queue.slug</dt>
<dd>The 'slug' of the queue, a single-word lowercase identifier used in URL's and e-mail subjects.</dd>
<dt id='queue_email_address'>queue.email_address</dt>
<dd>The e-mail address to which users can send an e-mail to open a new ticket automatically.</dd>
<dt id='queue_from_address'>queue.from_address</dt>
<dd>The e-mail address from which the queue will send e-mails to submitters.</dd>
</dl>
<h2 id='ticket'>Ticket Fields</h2>
<dl>
<dt id='ticket_title'>ticket.title</dt>
<dd>Title of the ticket, as provided by the submitter. A single line of text.</dd>
<dt id='ticket_created'>ticket.created</dt>
<dd>The time/date this ticket was first opened</dd>
<dt id='ticket_modified'>ticket.modified</dt>
<dd>The time/date this ticket was most recently changed</dd>
<dt id='ticket_submitter_email'>ticket.submitter_email</dt>
<dd>E-Mail address of the ticket submitter</dd>
<dt id='ticket_status'>ticket.status</dt>
<dd>Status of this ticket (Integer)</dd>
<dt id='ticket_get_status_display'>ticket.get_status_display</dt>
<dd>Status of this ticket (Text-based)</dd>
<dt id='ticket_get_status'>ticket.get_status</dt>
<dd>Status of this ticket (Text-based, includes 'On Hold' if required)</dd>
<dt id='ticket_on_hold'>ticket.on_hold</dt>
<dd>Indicates whether this ticket is on hold (True/False)</dd>
<dt id='ticket_description'>ticket.description</dt>
<dd>The description of the ticket, eg the body of the e-mail sent or the problem entered by the submitter</dd>
<dt id='ticket_resolution'>ticket.resolution</dt>
<dd>The resolution of the ticket provided by staff. This may be blank.</dd>
<dt id='ticket_priority'>ticket.priority</dt>
<dd>The priority of this ticket (1-5, Integer only)</dd>
<dt id='ticket_get_priority_display'>ticket.get_priority_display</dt>
<dd>The priority of this ticket (Text-based)</dd>
<dt id='ticket_last_escalation'>ticket.last_escalation</dt>
<dd>The time/date this ticket was last escalated</dd>
<dt id='ticket_ticket'>ticket.ticket</dt>
<dd>The ticket ID in form '[queue_slug-id]', used in e-mail subjects.</dd>
<dt id='ticket_ticket_for_url'>ticket.ticket_for_url</dt>
<dd>The ticket ID in form 'queue_slug-id', used in URLs</dd>
<dt id='ticket_ticket_url'>ticket.ticket_url</dt>
<dd>Public URL for this ticket</dd>
<dt id='ticket_staff_url'>ticket.staff_url</dt>
<dd>Staff-only URL for this ticket</dd>
<dt id='ticket_assigned_to'>ticket.assigned_to</dt>
<dd>Name of the staff member assigned to this ticket, or 'Unassigned' if it is unassigned.</dd>
<dt id='ticket_queue'>ticket.queue</dt>
<dd>Another dictionary of queue information, identical to <a href='#queue'>queue</a> above. For example, use ticket.queue.title.</dd>
</dl>
{% endblock %}

View File

@ -116,7 +116,7 @@
<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>
<dd class='form_help_text'>{% trans "You can use the {{ ticket }} and {{ queue }} template variables in your message." %}</dd> <dd class='form_help_text'>{% trans "You can insert ticket and queue details in your message. For more information, see the <a href='../../help/context/'>context help page</a>." %}</dd>
<dt><label>{% trans "New Status" %}</label></dt> <dt><label>{% trans "New Status" %}</label></dt>
{% ifequal ticket.status 1 %} {% ifequal ticket.status 1 %}

17
urls.py
View File

@ -91,11 +91,6 @@ urlpatterns += patterns('',
'helpdesk.views.api.api', 'helpdesk.views.api.api',
name='helpdesk_api'), name='helpdesk_api'),
url(r'^api/$',
'django.views.generic.simple.direct_to_template',
{'template': 'helpdesk/api_help.html',},
name='helpdesk_api_help'),
url(r'^login/$', url(r'^login/$',
'django.contrib.auth.views.login', 'django.contrib.auth.views.login',
name='login'), name='login'),
@ -118,3 +113,15 @@ urlpatterns += patterns('helpdesk.views.kb',
url(r'^kb/(?P<item>[0-9]+)/vote/$', url(r'^kb/(?P<item>[0-9]+)/vote/$',
'vote', name='helpdesk_kb_vote'), 'vote', name='helpdesk_kb_vote'),
) )
urlpatterns += patterns('',
url(r'^api/$',
'django.views.generic.simple.direct_to_template',
{'template': 'helpdesk/help_api.html',},
name='helpdesk_api_help'),
url(r'^help/context/$',
'django.views.generic.simple.direct_to_template',
{'template': 'helpdesk/help_context.html',},
name='helpdesk_help_context'),
)

View File

@ -8,7 +8,7 @@ api.py - Wrapper around API calls, and core functions to provide complete
The API documentation can be accessed by visiting http://helpdesk/api/help/ The API documentation can be accessed by visiting http://helpdesk/api/help/
(obviously, substitute helpdesk for your Jutda Helpdesk URI), or by reading (obviously, substitute helpdesk for your Jutda Helpdesk URI), or by reading
through templates/helpdesk/api_help.html. through templates/helpdesk/help_api.html.
""" """
from datetime import datetime from datetime import datetime
@ -47,7 +47,7 @@ def api(request, method):
""" """
if method == 'help': if method == 'help':
return render_to_response('helpdesk/api_help.html') return render_to_response('helpdesk/help_api.html')
if request.method != 'POST': if request.method != 'POST':
return api_return(STATUS_ERROR_BADMETHOD) return api_return(STATUS_ERROR_BADMETHOD)

View File

@ -21,7 +21,7 @@ from django.template import loader, Context, RequestContext
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from helpdesk.forms import TicketForm from helpdesk.forms import TicketForm
from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query, safe_template_context
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch
@ -133,6 +133,12 @@ def update_ticket(request, ticket_id):
owner = int(request.POST.get('owner', None)) owner = int(request.POST.get('owner', None))
priority = int(request.POST.get('priority', ticket.priority)) priority = int(request.POST.get('priority', ticket.priority))
# We need to allow the 'ticket' and 'queue' contexts to be applied to the
# comment.
from django.template import loader, Context
context = Context(safe_template_context(ticket))
comment = loader.get_template_from_string(comment).render(context)
if not owner and ticket.assigned_to: if not owner and ticket.assigned_to:
owner = ticket.assigned_to.id owner = ticket.assigned_to.id
@ -284,6 +290,8 @@ def ticket_list(request):
'other_filter': None, 'other_filter': None,
} }
from_saved_query = False
if request.GET.get('saved_query', None): if request.GET.get('saved_query', None):
from_saved_query = True from_saved_query = True
try: try:
@ -295,8 +303,21 @@ def ticket_list(request):
import base64, cPickle import base64, cPickle
query_params = cPickle.loads(base64.urlsafe_b64decode(str(saved_query.query))) query_params = cPickle.loads(base64.urlsafe_b64decode(str(saved_query.query)))
elif not ( request.GET.has_key('queue')
or request.GET.has_key('assigned_to')
or request.GET.has_key('status')
or request.GET.has_key('q')
or request.GET.has_key('sort') ):
# Fall-back if no querying is being done, force the list to only
# show open/reopened/resolved (not closed) cases sorted by creation
# date.
query_params = {
'filtering': {'status__in': [1, 2, 3]},
'sorting': 'created',
}
else: else:
from_saved_query = False
queues = request.GET.getlist('queue') queues = request.GET.getlist('queue')
if queues: if queues:
queues = [int(q) for q in queues] queues = [int(q) for q in queues]