From 47afa9b45b3908c437e7f18462e5b17e22a9190d Mon Sep 17 00:00:00 2001 From: Ross Poulton Date: Fri, 8 Feb 2008 05:29:51 +0000 Subject: [PATCH] * Added RSS Feed Functionality * RSS Feed Index (/rss/) * Open tasks by User * Open tasks by User / Queue * Open tasks by Queue * All activity (based on FollowUps for now) --- feeds.py | 128 ++++++++++++++++++ htdocs/rss_icon.png | Bin 0 -> 3341 bytes models.py | 3 + templates/helpdesk/base.html | 2 +- .../rss/recent_activity_description.html | 1 + .../helpdesk/rss/recent_activity_title.html | 1 + .../helpdesk/rss/ticket_description.html | 1 + templates/helpdesk/rss/ticket_title.html | 1 + templates/helpdesk/rss_list.html | 31 +++++ urls.py | 16 +++ views.py | 6 + 11 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 feeds.py create mode 100644 htdocs/rss_icon.png create mode 100644 templates/helpdesk/rss/recent_activity_description.html create mode 100644 templates/helpdesk/rss/recent_activity_title.html create mode 100644 templates/helpdesk/rss/ticket_description.html create mode 100644 templates/helpdesk/rss/ticket_title.html create mode 100644 templates/helpdesk/rss_list.html diff --git a/feeds.py b/feeds.py new file mode 100644 index 00000000..639cba36 --- /dev/null +++ b/feeds.py @@ -0,0 +1,128 @@ +from django.contrib.auth.models import User +from django.contrib.syndication.feeds import Feed +from django.core.urlresolvers import reverse +from django.db.models import Q + +from models import Ticket, FollowUp, Queue + + +class OpenTicketsByUser(Feed): + title_template = 'helpdesk/rss/ticket_title.html' + description_template = 'helpdesk/rss/ticket_description.html' + + def get_object(self, bits): + if len(bits) < 1: + raise ObjectDoesNotExist + user = User.objects.get(username__exact=bits[0]) + if len(bits) == 2: + queue = Queue.objects.get(slug__exact=bits[1]) + else: queue = False + + return {'user': user, 'queue': queue} + + def title(self, obj): + if obj['queue']: + return "Helpdesk: Open Tickets in queue %s for %s" % (obj['queue'].title, obj['user'].username) + else: + return "Helpdesk: Open Tickets for %s" % obj['user'].username + + def description(self, obj): + if obj['queue']: + return "Open and Reopened Tickets in queue %s for %s" % (obj['queue'].title, obj['user'].username) + else: + return "Open and Reopened Tickets for %s" % obj['user'].username + + def link(self, obj): + if obj['queue']: + return '%s?assigned_to=%s&queue=%s' % (reverse('helpdesk_list'), obj['user'].id, obj['queue'].id) + else: + return '%s?assigned_to=%s' % (reverse('helpdesk_list'), obj['user'].id) + + def items(self, obj): + if obj['queue']: + return Ticket.objects.filter(assigned_to=obj['user']).filter(queue=obj['queue']).filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)) + else: + return Ticket.objects.filter(assigned_to=obj['user']).filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)) + + def item_pubdate(self, item): + return item.created + + def item_author_name(self, item): + if item.assigned_to: + return item.assigned_to.username + else: + return "Unassigned" + + +class UnassignedTickets(Feed): + title_template = 'helpdesk/rss/ticket_title.html' + description_template = 'helpdesk/rss/ticket_description.html' + + title = "Helpdesk: Unassigned Tickets" + description = "Unassigned Open and Reopened tickets" + link = ''#%s?assigned_to=' % reverse('helpdesk_list') + + def items(self, obj): + return Ticket.objects.filter(assigned_to__isnull=True).filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)) + + def item_pubdate(self, item): + return item.created + + + def item_author_name(self, item): + if item.assigned_to: + return item.assigned_to.username + else: + return "Unassigned" + + +class RecentFollowUps(Feed): + title_template = 'helpdesk/rss/recent_activity_title.html' + description_template = 'helpdesk/rss/recent_activity_description.html' + + title = "Helpdesk: Recent Followups" + description = "Recent FollowUps, such as e-mail replies, comments, attachments and resolutions" + link = '/tickets/' # reverse('helpdesk_list') + + def items(self): + return FollowUp.objects.order_by('-date')[:20] + + +class OpenTicketsByQueue(Feed): + title_template = 'helpdesk/rss/ticket_title.html' + description_template = 'helpdesk/rss/ticket_description.html' + + def get_object(self, bits): + if len(bits) != 1: + raise ObjectDoesNotExist + return Queue.objects.get(slug__exact=bits[0]) + + def title(self, obj): + return "Helpdesk: Open Tickets in queue %s" % obj.title + + def description(self, obj): + return "Open and Reopened Tickets in queue %s" % obj.title + + def link(self, obj): + return '%s?queue=%s' % (reverse('helpdesk_list'), obj.id) + + def items(self, obj): + return Ticket.objects.filter(queue=obj).filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)) + + def item_pubdate(self, item): + return item.created + + def item_author_name(self, item): + if item.assigned_to: + return item.assigned_to.username + else: + return "Unassigned" + + +feed_setup = { + 'user': OpenTicketsByUser, + 'queue': OpenTicketsByQueue, + 'recent_activity': RecentFollowUps, + 'unassigned': UnassignedTickets, +} + diff --git a/htdocs/rss_icon.png b/htdocs/rss_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7b676b345588a734dce002843a442520931170 GIT binary patch literal 3341 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 Xq0QFqM~$u500000NkvXXu0mjf25>N; literal 0 HcmV?d00001 diff --git a/models.py b/models.py index 1eec2697..e986cf03 100644 --- a/models.py +++ b/models.py @@ -232,6 +232,9 @@ class FollowUp(models.Model): def __unicode__(self): return u'%s' % self.title + def get_absolute_url(self): + return "%s#followup%s" % (self.ticket.get_absolute_url(), self.id) + def save(self): t = self.ticket diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html index fc8f93c5..bfb6623d 100644 --- a/templates/helpdesk/base.html +++ b/templates/helpdesk/base.html @@ -21,7 +21,7 @@ {% block helpdesk_body %}{% endblock %} {% include "helpdesk/debug.html" %} diff --git a/templates/helpdesk/rss/recent_activity_description.html b/templates/helpdesk/rss/recent_activity_description.html new file mode 100644 index 00000000..8141388c --- /dev/null +++ b/templates/helpdesk/rss/recent_activity_description.html @@ -0,0 +1 @@ +{{ obj.comment }} diff --git a/templates/helpdesk/rss/recent_activity_title.html b/templates/helpdesk/rss/recent_activity_title.html new file mode 100644 index 00000000..bc66b4e3 --- /dev/null +++ b/templates/helpdesk/rss/recent_activity_title.html @@ -0,0 +1 @@ +{{ obj.title }} by {{ obj.user }} (on {{ obj.ticket.ticket }} {{ obj.ticket.title }}, {{ obj.date }}) diff --git a/templates/helpdesk/rss/ticket_description.html b/templates/helpdesk/rss/ticket_description.html new file mode 100644 index 00000000..dc930eb1 --- /dev/null +++ b/templates/helpdesk/rss/ticket_description.html @@ -0,0 +1 @@ +{{ obj.description }}{% if obj.submitter_email %} (Submitted by {{ obj.submitter_email }}){% endif %} diff --git a/templates/helpdesk/rss/ticket_title.html b/templates/helpdesk/rss/ticket_title.html new file mode 100644 index 00000000..86be79d9 --- /dev/null +++ b/templates/helpdesk/rss/ticket_title.html @@ -0,0 +1 @@ +{{ obj.ticket }} {{ obj.title }} ({{ obj.created }}) diff --git a/templates/helpdesk/rss_list.html b/templates/helpdesk/rss_list.html new file mode 100644 index 00000000..a2d7943c --- /dev/null +++ b/templates/helpdesk/rss_list.html @@ -0,0 +1,31 @@ +{% extends "helpdesk/base.html" %} +{% block helpdesk_title %}RSS Feeds{% endblock %} +{% block helpdesk_body %} +

RSS Feeds

+ +

The following RSS feeds are available for you to monitor using your preferred RSS software. With the exception of the 'Latest Activity' feed, all feeds provide information only on Open and Reopened cases. This ensures your RSS reader isn't full of information about closed or historical tasks.

+ +
+
RSS IconMy Open Tickets
+
A summary of your open tickets - useful for getting alerted to new tickets opened for you
+ +
RSS IconLatest Activity
+
A summary of all helpdesk activity - including comments, emails, attachments, and more
+ +
RSS IconUnassigned Tickets
+
All unassigned tickets - useful for being alerted to new tickets opened by the public via the web or via e-mail
+
+ +

These RSS feeds allow you to view a summary of either your own tickets, or all tickets, for each of the queues in your helpdesk. For example, if you manage the staff who utilise a particular queue, this may be used to view new tickets coming into that queue.

+ + + + +{% for queue in queues %} + + + + +{% endfor %} +
Per-Queue Feeds
QueueAll Open TicketsMy Open Tickets
{{ queue.title }}RSS IconRSS Icon
+{% endblock %} diff --git a/urls.py b/urls.py index df92c245..8905c016 100644 --- a/urls.py +++ b/urls.py @@ -9,6 +9,12 @@ urls.py - Mapping of URL's to our various views. Note we always used NAMED from django.conf.urls.defaults import * +from django.contrib.auth.decorators import login_required + +from feeds import feed_setup + +from django.contrib.syndication.views import feed as django_feed + urlpatterns = patterns('helpdesk.views', url(r'^$', 'dashboard', @@ -49,8 +55,18 @@ urlpatterns = patterns('helpdesk.views', url(r'^view/$', 'public_view', name='helpdesk_public_view'), + + url(r'^rss/$', + 'rss_list', + name='helpdesk_rss_index'), ) +urlpatterns += patterns('', + url(r'^rss/(?P.*)/$', + login_required(django_feed), + {'feed_dict': feed_setup}, + name='helpdesk_rss'), +) urlpatterns += patterns('', url(r'^api/(?P[a-z_-]+)/$', 'helpdesk.api.api', diff --git a/views.py b/views.py index ac480579..f4abe051 100644 --- a/views.py +++ b/views.py @@ -357,3 +357,9 @@ def unhold_ticket(request, ticket_id): return hold_ticket(request, ticket_id, unhold=True) unhold_ticket = login_required(unhold_ticket) +def rss_list(request): + return render_to_response('helpdesk/rss_list.html', + RequestContext(request, { + 'queues': Queue.objects.all(), + })) +rss_list = login_required(rss_list)