Issue #13 Add Tags to tickets

Patch courtesy of david@zettazebra.com, adds the ability to add tags to
tickets if django-tagging is installed and in use. If django-tagging isn't
being used, no change is visible to the user.
This commit is contained in:
Ross Poulton 2009-09-09 09:11:05 +00:00
parent f419d8e2d0
commit f418e97efc
8 changed files with 114 additions and 10 deletions

View File

@ -4,3 +4,8 @@
Add rudimentary CC: functionality on tickets, controlled by staff users. CC's
can be e-mail addresses or users, who will receive copies of all emails sent
to the Submitter. This is a work in progress.
2009-09-09 r140 Issue #13 Add Tags to tickets
Patch courtesy of david@zettazebra.com, adds the ability to add tags to
tickets if django-tagging is installed and in use. If django-tagging isn't
being used, no change is visible to the user.

View File

@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _
from helpdesk.lib import send_templated_mail
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC
from helpdesk.settings import HAS_TAG_SUPPORT
class EditTicketForm(forms.ModelForm):
class Meta:
@ -72,6 +73,17 @@ class TicketForm(forms.Form):
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
)
if HAS_TAG_SUPPORT:
tags = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(),
label=_('Tags'),
help_text=_('Words, separated by spaces, or phrases separated by commas. '
'These should communicate significant characteristics of this '
'ticket'),
)
def save(self, user):
"""
Writes and returns a Ticket() object
@ -88,6 +100,9 @@ class TicketForm(forms.Form):
priority = self.cleaned_data['priority'],
)
if HAS_TAG_SUPPORT:
t.tags = self.cleaned_data['tags']
if self.cleaned_data['assigned_to']:
try:
u = User.objects.get(id=self.cleaned_data['assigned_to'])

View File

@ -13,7 +13,10 @@ from django.contrib.auth.models import User
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from helpdesk.settings import HAS_TAG_SUPPORT
if HAS_TAG_SUPPORT:
from tagging.fields import TagField
class Queue(models.Model):
"""
@ -400,6 +403,9 @@ class Ticket(models.Model):
)
staff_url = property(_get_staff_url)
if HAS_TAG_SUPPORT:
tags = TagField(blank=True)
class Meta:
get_latest_by = "created"

14
settings.py Normal file
View File

@ -0,0 +1,14 @@
"""
Default settings for jutda-helpdesk.
"""
from django.conf import settings
# check for django-tagging support
HAS_TAG_SUPPORT = 'tagging' in settings.INSTALLED_APPS
try:
import tagging
except ImportError:
HAS_TAG_SUPPORT = False

View File

@ -22,17 +22,24 @@
<td>{{ ticket.get_priority_display }}</td>
</tr>
{% if tags_enabled %}
<tr class='row_even'>
<th>{% trans "Tags" %}</th>
<td>{{ ticket.tags }}</td>
</tr>
{% endif %}
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<th colspan='2'>{% trans "Description" %}</th>
</tr>
<tr class='row_odd'>
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<td colspan='2'>{{ ticket.description|linebreaksbr }}</td>
</tr>
{% if ticket.resolution %}<tr class='row_even'>
{% if ticket.resolution %}<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='{{ request.get_full_path }}&close'><img src='{{ MEDIA_URL }}/helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th>
</tr>
<tr class='row_odd'>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<td colspan='2'>{{ ticket.resolution }}</td>
</tr>{% endif %}

View File

@ -71,17 +71,25 @@
<td>{% for ticketcc in ticket.ticketcc_set.all %}{{ ticketcc.display }}{% if not forloop.last %}, {% endif %}{% endfor %} <strong><a href='{% url helpdesk_ticket_cc ticket.id %}'>{% trans "Manage" %}</a></strong></td>
</tr>
{% if tags_enabled %}
<tr class='row_even'>
<th>{% trans "Tags" %}</th>
<td>{{ ticket.tags }}</td>
</tr>
{% endif %}
<tr class='row_even'>
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<th colspan='2'>{% trans "Description" %}</th>
</tr>
<tr class='row_odd'>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<td colspan='2'>{{ ticket.description|force_escape|linebreaksbr }}</td>
</tr>
{% if ticket.resolution %}<tr class='row_even'>
{% if ticket.resolution %}<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><img src='{{ MEDIA_URL }}helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th>
</tr>
<tr class='row_odd'>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<td colspan='2'>{{ ticket.resolution|force_escape }}</td>
</tr>{% endif %}
@ -161,6 +169,10 @@
<dt><label for='id_priority'>{% trans "Priority" %}</label></dt>
<dd><select id='id_priority' name='priority'>{% for p in priorities %}<option value='{{ p.0 }}'{% ifequal p.0 ticket.priority %} selected='selected'{% endifequal %}>{{ p.1 }}</option>{% endfor %}</select></dd>
{% if tags_enabled %}
<dt><label for='id_tags'>{% trans "Tags" %}</label></dt>
<dd><input type='text' id='id_tags' name='tags' value='{{ ticket.tags }}'/></dd>
{% endif %}
</dl>

View File

@ -54,6 +54,9 @@ $(document).ready(function() {
<option value='Queue'>{% trans "Queue" %}</option>
<option value='Status'>{% trans "Status" %}</option>
<option value='Keywords'>{% trans "Keywords" %}</option>
{% if tags_enabled %}
<option value='Tags'>{% trans "Tags" %}</option>
{% endif %}
</select>
<input type='button' id='filterBuilderButton' value='+' />
</form>
@ -112,6 +115,13 @@ $(document).ready(function() {
<input type='button' class='filterBuilderRemove' value='-' />
</div>
{% if tags_enabled %}
<div class='filterBox{% if query_params.tags %} filterBoxShow{% endif %}' id='filterBoxTags'>
<label for='id_tags'>{% trans "Tag(s)" %}</label><select id='id_tags' name='tags' multiple='selected' size='5'>{% for t in tag_choices %}<option value='{{t.name}}'{% if t.name|in_list:query_params.tags %} selected='selected'{% endif %}>{{ t.name }}</option>{% endfor %}</select>
<p class='filterHelp'>Ctrl-click to select multiple options</p>
<input type='button' class='filterBuilderRemove' value='-' />
</div>
{% endif %}
<div class='filterBox{% if query %} filterBoxShow{% endif %}' id='filterBoxKeywords'>
<label for='id_query'>{% trans "Keywords" %}</label><input type='text' name='q' value='{{ query }}' id='id_query' />
@ -169,8 +179,8 @@ $(document).ready(function() {
{{ search_message|safe }}
<form method='post' action='{% url helpdesk_mass_update %}'>
<table width='100%'>
<tr class='row_tablehead'><td colspan='8'>{% trans "Tickets" %}</td></tr>
<tr class='row_columnheads'><th>#</th><th>&nbsp;</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Status" %}</th><th>{% trans "Created" %}</th><th>{% trans "Owner" %}</th></tr>
<tr class='row_tablehead'><td colspan='9'>{% trans "Tickets" %}</td></tr>
<tr class='row_columnheads'><th>#</th><th>&nbsp;</th><th>{% trans "Pr" %}</th><th>{% trans "Title" %}</th><th>{% trans "Queue" %}</th><th>{% trans "Status" %}</th><th>{% trans "Created" %}</th><th>{% trans "Owner" %}</th>{% if tags_enabled %}<th>{% trans "Tags" %}</th>{% endif %}</tr>
{% if tickets %}{% for ticket in tickets.object_list %}
<tr class='row_{% cycle odd,even %} row_hover'>
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
@ -181,6 +191,7 @@ $(document).ready(function() {
<td>{{ ticket.get_status }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|timesince }} ago</span></td>
<td>{{ ticket.get_assigned_to }}</td>
{% if tags_enabled %}<td>{{ ticket.tags }}</td>{% endif %}
</tr>
{% endfor %}{% else %}
<tr class='row_odd'><td colspan='5'>{% trans "No Tickets Match Your Selection" %}</td></tr>

View File

@ -25,7 +25,10 @@ from django.utils.translation import ugettext as _
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm
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, IgnoreEmail, TicketCC
from helpdesk.settings import HAS_TAG_SUPPORT
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)
@ -126,6 +129,7 @@ def view_ticket(request, ticket_id):
'active_users': User.objects.filter(is_active=True).filter(is_staff=True),
'priorities': Ticket.PRIORITY_CHOICES,
'preset_replies': PreSetReply.objects.filter(Q(queues=ticket.queue) | Q(queues__isnull=True)),
'tags_enabled': HAS_TAG_SUPPORT
}))
view_ticket = staff_member_required(view_ticket)
@ -142,6 +146,7 @@ def update_ticket(request, ticket_id, public=False):
public = request.POST.get('public', False)
owner = int(request.POST.get('owner', None))
priority = int(request.POST.get('priority', ticket.priority))
tags = request.POST.get('tags', '')
# We need to allow the 'ticket' and 'queue' contexts to be applied to the
# comment.
@ -230,6 +235,17 @@ def update_ticket(request, ticket_id, public=False):
c.save()
ticket.priority = priority
if HAS_TAG_SUPPORT:
if tags != ticket.tags:
c = TicketChange(
followup=f,
field=_('Tags'),
old_value=ticket.tags,
new_value=tags,
)
c.save()
ticket.tags = tags
if f.new_status == Ticket.RESOLVED_STATUS:
ticket.resolution = comment
@ -477,7 +493,8 @@ def ticket_list(request):
or request.GET.has_key('status')
or request.GET.has_key('q')
or request.GET.has_key('sort')
or request.GET.has_key('sortreverse') ):
or request.GET.has_key('sortreverse')
or request.GET.has_key('tags') ):
# Fall-back if no querying is being done, force the list to only
# show open/reopened/resolved (not closed) cases sorted by creation
@ -527,6 +544,14 @@ def ticket_list(request):
query_params['sortreverse'] = sortreverse
ticket_qs = apply_query(Ticket.objects.select_related(), query_params)
## TAG MATCHING
if HAS_TAG_SUPPORT:
tags = request.GET.getlist('tags')
if tags:
ticket_qs = TaggedItem.objects.get_by_model(ticket_qs, tags)
query_params['tags'] = tags
ticket_paginator = paginator.Paginator(ticket_qs, request.user.usersettings.settings.get('tickets_per_page') or 20)
try:
page = int(request.GET.get('page', '1'))
@ -554,6 +579,11 @@ def ticket_list(request):
if get_key != "page":
query_string.append("%s=%s" % (get_key, get_value))
tag_choices = []
if HAS_TAG_SUPPORT:
# FIXME: restrict this to tags that are actually in use
tag_choices = Tag.objects.all()
return render_to_response('helpdesk/ticket_list.html',
RequestContext(request, dict(
context,
@ -562,11 +592,13 @@ def ticket_list(request):
user_choices=User.objects.filter(is_active=True),
queue_choices=Queue.objects.all(),
status_choices=Ticket.STATUS_CHOICES,
tag_choices=tag_choices,
urlsafe_query=urlsafe_query,
user_saved_queries=user_saved_queries,
query_params=query_params,
from_saved_query=from_saved_query,
search_message=search_message,
tags_enabled=HAS_TAG_SUPPORT
)))
ticket_list = staff_member_required(ticket_list)
@ -584,6 +616,7 @@ def edit_ticket(request, ticket_id):
return render_to_response('helpdesk/edit_ticket.html',
RequestContext(request, {
'form': form,
'tags_enabled': HAS_TAG_SUPPORT,
}))
edit_ticket = staff_member_required(edit_ticket)
@ -607,6 +640,7 @@ def create_ticket(request):
return render_to_response('helpdesk/create_ticket.html',
RequestContext(request, {
'form': form,
'tags_enabled': HAS_TAG_SUPPORT,
}))
create_ticket = staff_member_required(create_ticket)