From dfb821336e366eaeec82b6264dc318c78b112c13 Mon Sep 17 00:00:00 2001 From: Ross Poulton Date: Wed, 7 May 2008 09:04:18 +0000 Subject: [PATCH] * Added i18n hooks, eg _() and {% trans %} tags around all helpdesk-generated text to assist with future translation efforts. I've no doubt missed a few. Also we don't have a "Change Language" view in here, unsure if this should be a helpdesk function or a function of the parent project. * Updated svn:ignore to ignore .pyc files * Added new function to replace cursor.dictfetchall() which is available in psycopg1 but not psycopg2. New function should work across other database systems, but is untested. --- api.py | 2 +- feeds.py | 31 ++--- forms.py | 41 +++--- lib.py | 17 +++ models.py | 139 +++++++++++---------- scripts/__init__.py | 0 scripts/escalate_tickets.py | 9 +- scripts/get_email.py | 78 ++++++------ templates/helpdesk/base.html | 26 ++-- templates/helpdesk/create_ticket.html | 14 +-- templates/helpdesk/dashboard.html | 23 ++-- templates/helpdesk/delete_ticket.html | 8 +- templates/helpdesk/public_base.html | 12 +- templates/helpdesk/public_homepage.html | 17 ++- templates/helpdesk/public_view_form.html | 19 +-- templates/helpdesk/public_view_ticket.html | 20 +-- templates/helpdesk/report_index.html | 8 +- templates/helpdesk/report_output.html | 6 +- templates/helpdesk/rss_list.html | 30 ++--- templates/helpdesk/ticket.html | 86 ++++++------- templates/helpdesk/ticket_list.html | 44 +++---- templates/registration/logged_out.html | 9 ++ templates/registration/login.html | 16 +-- templates/registration/logout.html | 9 -- views.py | 35 +++--- 25 files changed, 364 insertions(+), 335 deletions(-) create mode 100644 scripts/__init__.py create mode 100644 templates/registration/logged_out.html delete mode 100644 templates/registration/logout.html diff --git a/api.py b/api.py index 6a28662b..1e5c622f 100644 --- a/api.py +++ b/api.py @@ -11,7 +11,7 @@ The API documentation can be accessed by visiting http://helpdesk/api/help/ through templates/helpdesk/api_help.html. """ from datetime import datetime -import simplejson +from django.utils import simplejson from django.contrib.auth.models import User from django.contrib.auth import authenticate diff --git a/feeds.py b/feeds.py index 639cba36..c2ecc954 100644 --- a/feeds.py +++ b/feeds.py @@ -2,6 +2,7 @@ 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 django.utils.translation import ugettext as _ from models import Ticket, FollowUp, Queue @@ -22,21 +23,21 @@ class OpenTicketsByUser(Feed): def title(self, obj): if obj['queue']: - return "Helpdesk: Open Tickets in queue %s for %s" % (obj['queue'].title, obj['user'].username) + return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {'queue': obj['queue'].title, 'username': obj['user'].username} else: - return "Helpdesk: Open Tickets for %s" % obj['user'].username + return _("Helpdesk: Open Tickets for %(username)s") % {'username': 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) + return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {'queue': obj['queue'].title, 'username': obj['user'].username} else: - return "Open and Reopened Tickets for %s" % obj['user'].username + return _("Open and Reopened Tickets for %(username)s") % {'username': 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) + return u'%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) + return u'%s?assigned_to=%s' % (reverse('helpdesk_list'), obj['user'].id) def items(self, obj): if obj['queue']: @@ -51,15 +52,15 @@ class OpenTicketsByUser(Feed): if item.assigned_to: return item.assigned_to.username else: - return "Unassigned" + 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" + title = _('Helpdesk: Unassigned Tickets') + description = _('Unassigned Open and Reopened tickets') link = ''#%s?assigned_to=' % reverse('helpdesk_list') def items(self, obj): @@ -73,15 +74,15 @@ class UnassignedTickets(Feed): if item.assigned_to: return item.assigned_to.username else: - return "Unassigned" + 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" + title = _('Helpdesk: Recent Followups') + description = _('Recent FollowUps, such as e-mail replies, comments, attachments and resolutions') link = '/tickets/' # reverse('helpdesk_list') def items(self): @@ -98,10 +99,10 @@ class OpenTicketsByQueue(Feed): return Queue.objects.get(slug__exact=bits[0]) def title(self, obj): - return "Helpdesk: Open Tickets in queue %s" % obj.title + return _('Helpdesk: Open Tickets in queue %(queue)s') % {'queue': obj.title} def description(self, obj): - return "Open and Reopened Tickets in queue %s" % obj.title + return _('Open and Reopened Tickets in queue %(queue)s') % {'queue': obj.title} def link(self, obj): return '%s?queue=%s' % (reverse('helpdesk_list'), obj.id) @@ -116,7 +117,7 @@ class OpenTicketsByQueue(Feed): if item.assigned_to: return item.assigned_to.username else: - return "Unassigned" + return _('Unassigned') feed_setup = { diff --git a/forms.py b/forms.py index 27eff547..e18436b8 100644 --- a/forms.py +++ b/forms.py @@ -11,30 +11,31 @@ from django import newforms as forms from helpdesk.models import Ticket, Queue, FollowUp from django.contrib.auth.models import User from datetime import datetime +from django.utils.translation import ugettext as _ class TicketForm(forms.Form): - queue = forms.ChoiceField(label=u'Queue', required=True, choices=()) + queue = forms.ChoiceField(label=_('Queue'), required=True, choices=()) title = forms.CharField(max_length=100, required=True, widget=forms.TextInput(), - label=u'Summary of the problem') + label=_('Summary of the problem')) submitter_email = forms.EmailField(required=False, - label=u'Submitter E-Mail Address', - help_text=u'This e-mail address will receive copies of all public updates to this ticket.') + label=_('Submitter E-Mail Address'), + help_text=_('This e-mail address will receive copies of all public updates to this ticket.')) body = forms.CharField(widget=forms.Textarea(), - label=u'Description of Issue', required=True) + label=_('Description of Issue'), required=True) assigned_to = forms.ChoiceField(choices=(), required=False, - label=u'Case owner', - help_text=u'If you select an owner other than yourself, they\'ll be e-mailed details of this ticket immediately.') + label=_('Case owner'), + help_text=_('If you select an owner other than yourself, they\'ll be e-mailed details of this ticket immediately.')) priority = forms.ChoiceField(choices=Ticket.PRIORITY_CHOICES, required=False, initial='3', - label=u'Priority', - help_text=u'Please select a priority carefully. If unsure, leave it as \'3\'.') + label=_('Priority'), + help_text=_('Please select a priority carefully. If unsure, leave it as \'3\'.')) def save(self, user): """ @@ -61,14 +62,14 @@ class TicketForm(forms.Form): t.save() f = FollowUp( ticket = t, - title = 'Ticket Opened', + title = _('Ticket Opened'), date = datetime.now(), public = True, comment = self.cleaned_data['body'], user = user, ) if self.cleaned_data['assigned_to']: - f.title = 'Ticket Opened & Assigned to %s' % t.get_assigned_to + f.title = _('Ticket Opened & Assigned to %(name)s') % {'name': t.get_assigned_to} f.save() @@ -94,25 +95,25 @@ class TicketForm(forms.Form): return t class PublicTicketForm(forms.Form): - queue = forms.ChoiceField(label=u'Queue', required=True, choices=()) + queue = forms.ChoiceField(label=_('Queue'), required=True, choices=()) title = forms.CharField(max_length=100, required=True, widget=forms.TextInput(), - label=u'Summary of your query') + label=_('Summary of your query')) submitter_email = forms.EmailField(required=True, - label=u'Your E-Mail Address', - help_text=u'We will e-mail you when your ticket is updated.') + label=_('Your E-Mail Address'), + help_text=_('We will e-mail you when your ticket is updated.')) body = forms.CharField(widget=forms.Textarea(), - label=u'Description of your issue', required=True, - help_text=u'Please be as descriptive as possible, including any details we may need to address your query.') + label=_('Description of your issue'), required=True, + help_text=_('Please be as descriptive as possible, including any details we may need to address your query.')) priority = forms.ChoiceField(choices=Ticket.PRIORITY_CHOICES, required=True, initial='3', - label=u'Urgency', - help_text=u'Please select a priority carefully.') + label=_('Urgency'), + help_text=_('Please select a priority carefully.')) def save(self): """ @@ -133,7 +134,7 @@ class PublicTicketForm(forms.Form): t.save() f = FollowUp( ticket = t, - title = 'Ticket Opened Via Web', + title = _('Ticket Opened Via Web'), date = datetime.now(), public = True, comment = self.cleaned_data['body'], diff --git a/lib.py b/lib.py index 196f0ebb..33f7d3ae 100644 --- a/lib.py +++ b/lib.py @@ -184,3 +184,20 @@ def bar_chart(data): chart_url += '&chxl=0:|%s|1:|0|%s' % ('|'.join(column_headings), max) # Axis Label Text return chart_url + +def query_to_dict(results, descriptions): + """ Replacement method for cursor.dictfetchall() as that method no longer + exists in psycopg2, and I'm guessing in other backends too. + + Converts the results of a raw SQL query into a list of dictionaries, suitable + for use in templates etc. """ + output = [] + for data in results: + row = {} + i = 0 + for column in descriptions: + row[column[0]] = data[i] + i += 1 + + output.append(row) + return output diff --git a/models.py b/models.py index 580ad474..88cc280b 100644 --- a/models.py +++ b/models.py @@ -13,6 +13,7 @@ from django.contrib.auth.models import User from django.db import models from django.conf import settings from django.dispatch import dispatcher +from django.utils.translation import ugettext_lazy as _ class Queue(models.Model): """ @@ -25,28 +26,28 @@ class Queue(models.Model): TODO: Add e-mail inboxes (either using piped e-mail or IMAP/POP3) so we can automatically get tickets via e-mail. """ - title = models.CharField(max_length=100) - slug = models.SlugField(help_text='This slug is used when building ticket ID\'s. Once set, try not to change it or e-mailing may get messy.') - email_address = models.EmailField(blank=True, null=True, help_text='All outgoing e-mails for this queue will use this e-mail address. If you use IMAP or POP3, this shoul be the e-mail address for that mailbox.') - escalate_days = models.IntegerField(blank=True, null=True, help_text='For tickets which are not held, how often do you wish to increase their priority? Set to 0 for no escalation.') + title = models.CharField(_('Title'), max_length=100) + slug = models.SlugField(_('Slug'), help_text=_('This slug is used when building ticket ID\'s. Once set, try not to change it or e-mailing may get messy.')) + email_address = models.EmailField(_('E-Mail Address'), blank=True, null=True, help_text=_('All outgoing e-mails for this queue will use this e-mail address. If you use IMAP or POP3, this should be the e-mail address for that mailbox.')) + escalate_days = models.IntegerField(_('Escalation Days'), blank=True, null=True, help_text=_('For tickets which are not held, how often do you wish to increase their priority? Set to 0 for no escalation.')) def _from_address(self): if not self.email_address: - return 'NO QUEUE EMAIL ADDRESS DEFINED <%s>' % settings.DEFAULT_FROM_EMAIL + return u'NO QUEUE EMAIL ADDRESS DEFINED <%s>' % settings.DEFAULT_FROM_EMAIL else: - return '%s <%s>' % (self.title, self.email_address) + return u'%s <%s>' % (self.title, self.email_address) from_address = property(_from_address) - new_ticket_cc = models.EmailField(blank=True, null=True, help_text='If an e-mail address is entered here, then it will receive notification of all new tickets created for this queue') - updated_ticket_cc = models.EmailField(blank=True, null=True, help_text='If an e-mail address is entered here, then it will receive notification of all activity (new tickets, closed tickets, updates, reassignments, etc) for this queue') + new_ticket_cc = models.EmailField(_('New Ticket CC Address'), blank=True, null=True, help_text=_('If an e-mail address is entered here, then it will receive notification of all new tickets created for this queue')) + updated_ticket_cc = models.EmailField(_('Updated Ticket CC Address'), blank=True, null=True, help_text=_('If an e-mail address is entered here, then it will receive notification of all activity (new tickets, closed tickets, updates, reassignments, etc) for this queue')) - email_box_type = models.CharField(max_length=5, choices=(('pop3', 'POP 3'),('imap', 'IMAP')), blank=True, null=True, help_text='E-Mail Server Type - Both POP3 and IMAP are supported. Select your email server type here.') - email_box_host = models.CharField(max_length=200, blank=True, null=True, help_text='Your e-mail server address - either the domain name or IP address. May be "localhost".') - email_box_port = models.IntegerField(blank=True, null=True, help_text='Port number to use for accessing e-mail. Default for POP3 is "110", and for IMAP is "143". This may differ on some servers.') - email_box_user = models.CharField(max_length=200, blank=True, null=True, help_text='Username for accessing this mailbox.') - email_box_pass = models.CharField(max_length=200, blank=True, null=True, help_text='Password for the above username') - email_box_imap_folder = models.CharField(max_length=100, blank=True, null=True, help_text='If using IMAP, what folder do you wish to fetch messages from? This allows you to use one IMAP account for multiple queues, by filtering messages on your IMAP server into separate folders. Default: INBOX.') - email_box_interval = models.IntegerField(help_text='How often do you wish to check this mailbox? (in Minutes)', blank=True, null=True, default='5') + email_box_type = models.CharField(_('E-Mail Box Type'), max_length=5, choices=(('pop3', _('POP 3')),('imap', _('IMAP'))), blank=True, null=True, help_text=_('E-Mail server type for creating tickets automatically from a mailbox - both POP3 and IMAP are supported.')) + email_box_host = models.CharField(_('E-Mail Hostname'), max_length=200, blank=True, null=True, help_text=_('Your e-mail server address - either the domain name or IP address. May be "localhost".')) + email_box_port = models.IntegerField(_('E-Mail Port'), blank=True, null=True, help_text=_('Port number to use for accessing e-mail. Default for POP3 is "110", and for IMAP is "143". This may differ on some servers. Leave it blank to use the defaults.')) + email_box_user = models.CharField(_('E-Mail Username'), max_length=200, blank=True, null=True, help_text=_('Username for accessing this mailbox.')) + email_box_pass = models.CharField(_('E-Mail Password'), max_length=200, blank=True, null=True, help_text=_('Password for the above username')) + email_box_imap_folder = models.CharField(_('IMAP Folder'), max_length=100, blank=True, null=True, help_text=_('If using IMAP, what folder do you wish to fetch messages from? This allows you to use one IMAP account for multiple queues, by filtering messages on your IMAP server into separate folders. Default: INBOX.')) + email_box_interval = models.IntegerField(_('E-Mail Check Interval'), help_text=_('How often do you wish to check this mailbox? (in Minutes)'), blank=True, null=True, default='5') email_box_last_check = models.DateTimeField(blank=True, null=True, editable=False) # Updated by the auto-pop3-and-imap-checker def __unicode__(self): @@ -87,34 +88,34 @@ class Ticket(models.Model): CLOSED_STATUS = 4 STATUS_CHOICES = ( - (OPEN_STATUS, 'Open'), - (REOPENED_STATUS, 'Reopened'), - (RESOLVED_STATUS, 'Resolved'), - (CLOSED_STATUS, 'Closed'), + (OPEN_STATUS, _('Open')), + (REOPENED_STATUS, _('Reopened')), + (RESOLVED_STATUS, _('Resolved')), + (CLOSED_STATUS, _('Closed')), ) PRIORITY_CHOICES = ( - (1, '1. Critical'), - (2, '2. High'), - (3, '3. Normal'), - (4, '4. Low'), - (5, '5. Very Low'), + (1, _('1. Critical')), + (2, _('2. High')), + (3, _('3. Normal')), + (4, _('4. Low')), + (5, _('5. Very Low')), ) - title = models.CharField(max_length=200) + title = models.CharField(_('Title'), max_length=200) queue = models.ForeignKey(Queue) - created = models.DateTimeField(blank=True) - modified = models.DateTimeField(blank=True) - submitter_email = models.EmailField(blank=True, null=True, help_text='The submitter will receive an email for all public follow-ups left for this task.') + created = models.DateTimeField(_('Created'), blank=True) + modified = models.DateTimeField(_('Modified'), blank=True) + submitter_email = models.EmailField(_('Submitter E-Mail'), blank=True, null=True, help_text=_('The submitter will receive an email for all public follow-ups left for this task.')) assigned_to = models.ForeignKey(User, related_name='assigned_to', blank=True, null=True) - status = models.IntegerField(choices=STATUS_CHOICES, default=OPEN_STATUS) + status = models.IntegerField(_('Status'), choices=STATUS_CHOICES, default=OPEN_STATUS) - on_hold = models.BooleanField(blank=True, null=True) + on_hold = models.BooleanField(_('On Hold'), blank=True, null=True) - description = models.TextField(blank=True, null=True) - resolution = models.TextField(blank=True, null=True) + description = models.TextField(_('Description'), blank=True, null=True) + resolution = models.TextField(_('Resolution'), blank=True, null=True) - priority = models.IntegerField(choices=PRIORITY_CHOICES, default=3, blank=3) + priority = models.IntegerField(_('Priority'), choices=PRIORITY_CHOICES, default=3, blank=3) last_escalation = models.DateTimeField(blank=True, null=True, editable=False) @@ -123,7 +124,7 @@ class Ticket(models.Model): ticket has no owner, or the users name if it's assigned. If the user has a full name configured, we use that, otherwise their username. """ if not self.assigned_to: - return 'Unassigned' + return _('Unassigned') else: if self.assigned_to.get_full_name(): return self.assigned_to.get_full_name() @@ -135,36 +136,36 @@ class Ticket(models.Model): """ A user-friendly ticket ID, which is a combination of ticket ID and queue slug. This is generally used in e-mails. """ - return "[%s]" % (self.ticket_for_url) + return u"[%s]" % (self.ticket_for_url) ticket = property(_get_ticket) def _get_ticket_for_url(self): - return "%s-%s" % (self.queue.slug, self.id) + return u"%s-%s" % (self.queue.slug, self.id) ticket_for_url = property(_get_ticket_for_url) def _get_priority_img(self): from django.conf import settings - return "%s/helpdesk/priorities/priority%s.png" % (settings.MEDIA_URL, self.priority) + return u"%s/helpdesk/priorities/priority%s.png" % (settings.MEDIA_URL, self.priority) get_priority_img = property(_get_priority_img) def _get_status(self): held_msg = '' - if self.on_hold: held_msg = ' - On Hold' - return '%s%s' % (self.get_status_display(), held_msg) + if self.on_hold: held_msg = _(' - On Hold') + return u'%s%s' % (self.get_status_display(), held_msg) get_status = property(_get_status) def _get_ticket_url(self): from django.contrib.sites.models import Site from django.core.urlresolvers import reverse site = Site.objects.get_current() - return "http://%s%s?ticket=%s&email=%s" % (site.domain, reverse('helpdesk_public_view'), self.ticket_for_url, self.submitter_email) + return u"http://%s%s?ticket=%s&email=%s" % (site.domain, reverse('helpdesk_public_view'), self.ticket_for_url, self.submitter_email) ticket_url = property(_get_ticket_url) def _get_staff_url(self): from django.contrib.sites.models import Site from django.core.urlresolvers import reverse site = Site.objects.get_current() - return "http://%s%s" % (site.domain, reverse('helpdesk_view', args=[self.id])) + return u"http://%s%s" % (site.domain, reverse('helpdesk_view', args=[self.id])) staff_url = property(_get_staff_url) class Admin: @@ -213,13 +214,13 @@ class FollowUp(models.Model): although all staff can see them. """ ticket = models.ForeignKey(Ticket) - date = models.DateTimeField(auto_now_add=True) - title = models.CharField(max_length=200, blank=True, null=True) - comment = models.TextField(blank=True, null=True) - public = models.BooleanField(blank=True, null=True) + date = models.DateTimeField(_('Date'), auto_now_add=True) + title = models.CharField(_('Title'), max_length=200, blank=True, null=True) + comment = models.TextField(_('Comment'), blank=True, null=True) + public = models.BooleanField(_('Public'), blank=True, null=True) user = models.ForeignKey(User, blank=True, null=True) - new_status = models.IntegerField(choices=Ticket.STATUS_CHOICES, blank=True, null=True) + new_status = models.IntegerField(_('New Status'), choices=Ticket.STATUS_CHOICES, blank=True, null=True) objects = FollowUpManager() @@ -233,7 +234,7 @@ class FollowUp(models.Model): return u'%s' % self.title def get_absolute_url(self): - return "%s#followup%s" % (self.ticket.get_absolute_url(), self.id) + return u"%s#followup%s" % (self.ticket.get_absolute_url(), self.id) def save(self): @@ -247,18 +248,18 @@ class TicketChange(models.Model): etc) are tracked here for display purposes. """ followup = models.ForeignKey(FollowUp, edit_inline=models.TABULAR) - field = models.CharField(max_length=100, core=True) - old_value = models.TextField(blank=True, null=True, core=True) - new_value = models.TextField(blank=True, null=True, core=True) + field = models.CharField(_('Field'), max_length=100, core=True) + old_value = models.TextField(_('Old Value'), blank=True, null=True, core=True) + new_value = models.TextField(_('New Value'), blank=True, null=True, core=True) def __unicode__(self): str = u'%s ' % field if not new_value: - str += 'removed' + str += _('removed') elif not old_value: - str += 'set to %s' % new_value + str += _('set to %s' % new_value) else: - str += 'changed from "%s" to "%s"' % (old_value, new_value) + str += _('changed from "%s" to "%s"' % (old_value, new_value)) return str @@ -290,14 +291,14 @@ class DynamicFileField(models.FileField): class Attachment(models.Model): followup = models.ForeignKey(FollowUp, edit_inline=models.TABULAR) - file = DynamicFileField(upload_to='helpdesk/attachments', core=True) - filename = models.CharField(max_length=100) - mime_type = models.CharField(max_length=30) - size = models.IntegerField(help_text='Size of this file in bytes') + file = DynamicFileField(_('File'), upload_to='helpdesk/attachments', core=True) + filename = models.CharField(_('Filename'), max_length=100) + mime_type = models.CharField(_('MIME Type'), max_length=30) + size = models.IntegerField(_('Size'), help_text=_('Size of this file in bytes')) def get_upload_to(self, field_attname): """ Get upload_to path specific to this item """ - return 'helpdesk/attachments/%s/%s' % (self.followup.ticket.ticket_for_url, self.followup.id) + return u'helpdesk/attachments/%s/%s' % (self.followup.ticket.ticket_for_url, self.followup.id) def __unicode__(self): return u'%s' % self.filename @@ -316,9 +317,9 @@ class PreSetReply(models.Model): When replying to a ticket, the user can select any reply set for the current queue, and the body text is fetched via AJAX.""" - queues = models.ManyToManyField(Queue, blank=True, null=True, help_text='Leave blank to allow this reply to be used for all queues, or select those queues you wish to limit this reply to.') - name = models.CharField(max_length=100, help_text='Only used to assist users with selecting a reply - not shown to the user.') - body = models.TextField(help_text='Context available: {{ ticket }} - ticket object (eg {{ ticket.title }}); {{ queue }} - The queue; and {{ user }} - the current user.') + queues = models.ManyToManyField(Queue, blank=True, null=True, help_text=_('Leave blank to allow this reply to be used for all queues, or select those queues you wish to limit this reply to.')) + name = models.CharField(_('Name'), max_length=100, help_text=_('Only used to assist users with selecting a reply - not shown to the user.')) + body = models.TextField(_('Body'), help_text=_('Context available: {{ ticket }} - ticket object (eg {{ ticket.title }}); {{ queue }} - The queue; and {{ user }} - the current user.')) class Admin: list_display = ('name',) @@ -330,11 +331,11 @@ class PreSetReply(models.Model): return u'%s' % self.name class EscalationExclusion(models.Model): - queues = models.ManyToManyField(Queue, blank=True, null=True, help_text='Leave blank for this exclusion to be applied to all queues, or select those queues you wish to exclude with this entry.') + queues = models.ManyToManyField(Queue, blank=True, null=True, help_text=_('Leave blank for this exclusion to be applied to all queues, or select those queues you wish to exclude with this entry.')) - name = models.CharField(max_length=100) + name = models.CharField(_('Name'), max_length=100) - date = models.DateField(help_text='Date on which escalation should not happen') + date = models.DateField(_('Date'), help_text=_('Date on which escalation should not happen')) class Admin: pass @@ -348,12 +349,12 @@ class EmailTemplate(models.Model): them in the database. """ - template_name = models.CharField(max_length=100, unique=True) + template_name = models.CharField(_('Template Name'), max_length=100, unique=True) - subject = models.CharField(max_length=100, help_text=u'This will be prefixed with "[ticket.ticket] ticket.title". We recommend something simple such as "(Updated") or "(Closed)" - the same context is available as in plain_text, below.') - heading = models.CharField(max_length=100, help_text=u'In HTML e-mails, this will be the heading at the top of the email - the same context is available as in plain_text, below.') - plain_text = models.TextField(help_text=u'The context available to you includes {{ ticket }}, {{ queue }}, and depending on the time of the call: {{ resolution }} or {{ comment }}.') - html = models.TextField(help_text=u'The same context is available here as in plain_text, above.') + subject = models.CharField(_('Subject'), max_length=100, help_text=_('This will be prefixed with "[ticket.ticket] ticket.title". We recommend something simple such as "(Updated") or "(Closed)" - the same context is available as in plain_text, below.')) + heading = models.CharField(_('Heading'), max_length=100, help_text=_('In HTML e-mails, this will be the heading at the top of the email - the same context is available as in plain_text, below.')) + plain_text = models.TextField(_('Plain Text'), help_text=_('The context available to you includes {{ ticket }}, {{ queue }}, and depending on the time of the call: {{ resolution }} or {{ comment }}.')) + html = models.TextField(_('HTML'), help_text=_('The same context is available here as in plain_text, above.')) class Admin: pass diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/escalate_tickets.py b/scripts/escalate_tickets.py index dc96206e..e4970f7f 100644 --- a/scripts/escalate_tickets.py +++ b/scripts/escalate_tickets.py @@ -8,10 +8,13 @@ scripts/escalate_tickets.py - Easy way to escalate tickets based on their age, designed to be run from Cron or similar. """ from datetime import datetime, timedelta, date +import sys, getopt + from django.db.models import Q +from django.utils.translation import ugettext as _ + from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange from helpdesk.lib import send_templated_mail -import sys, getopt def escalate_tickets(queues, verbose): """ Only include queues with escalation configured """ @@ -64,13 +67,13 @@ def escalate_tickets(queues, verbose): title = 'Ticket Escalated', date=datetime.now(), public=True, - comment='Ticket escalated after %s days' % q.escalate_days, + comment=_('Ticket escalated after %s days' % q.escalate_days), ) f.save() tc = TicketChange( followup = f, - field = 'Priority', + field = _('Priority'), old_value = t.priority + 1, new_value = t.priority, ) diff --git a/scripts/get_email.py b/scripts/get_email.py index f8b35af4..05483f4b 100644 --- a/scripts/get_email.py +++ b/scripts/get_email.py @@ -24,53 +24,57 @@ def process_email(): if (q.email_box_last_check + timedelta(minutes=q.email_box_interval)) > datetime.now(): continue - print "Processing: %s" % q - if q.email_box_type == 'pop3': - server = poplib.POP3(q.email_box_host) - server.getwelcome() - server.user(q.email_box_user) - server.pass_(q.email_box_pass) - messagesInfo = server.list()[1] - - for msg in messagesInfo: - msgNum = msg.split(" ")[0] - msgSize = msg.split(" ")[1] - - full_message = "\n".join(server.retr(msgNum)[1]) - ticket_from_message(message=full_message, queue=q) - - server.dele(msgNum) - server.quit() - - elif q.email_box_type == 'imap': - if not q.email_box_port: q.email_box_port = 143 - - server = imaplib.IMAP4(q.email_box_host, q.email_box_port) - server.login(q.email_box_user, q.email_box_pass) - server.select(q.email_box_imap_folder) - status, data = server.search(None, 'ALL') - for num in data[0].split(): - status, data = server.fetch(num, '(RFC822)') - ticket_from_message(message=data[0][1], queue=q) - server.store(num, '+FLAGS', '\\Deleted') - server.expunge() - server.close() - server.logout() + process_queue(q) q.email_box_last_check = datetime.now() q.save() +def process_queue(q): + print "Processing: %s" % q + if q.email_box_type == 'pop3': + server = poplib.POP3(q.email_box_host) + server.getwelcome() + server.user(q.email_box_user) + server.pass_(q.email_box_pass) + + messagesInfo = server.list()[1] + + for msg in messagesInfo: + msgNum = msg.split(" ")[0] + msgSize = msg.split(" ")[1] + + full_message = "\n".join(server.retr(msgNum)[1]) + ticket_from_message(message=full_message, queue=q) + + server.dele(msgNum) + server.quit() + + elif q.email_box_type == 'imap': + if not q.email_box_port: q.email_box_port = 143 + + server = imaplib.IMAP4(q.email_box_host, q.email_box_port) + server.login(q.email_box_user, q.email_box_pass) + server.select(q.email_box_imap_folder) + status, data = server.search(None, 'ALL') + for num in data[0].split(): + status, data = server.fetch(num, '(RFC822)') + ticket_from_message(message=data[0][1], queue=q) + server.store(num, '+FLAGS', '\\Deleted') + server.expunge() + server.close() + server.logout() + def ticket_from_message(message, queue): # 'message' must be an RFC822 formatted message. msg = message message = email.message_from_string(msg) - subject = message.get('subject', 'Created from e-mail') + subject = message.get('subject', _('Created from e-mail')) subject = subject.replace("Re: ", "").replace("Fw: ", "").strip() - sender = message.get('from', 'Unknown Sender') + sender = message.get('from', _('Unknown Sender')) - sender_email = parseaddr(message.get('from', 'Unknown Sender'))[1] + sender_email = parseaddr(sender)[1] if sender_email.startswith('postmaster'): sender_email = '' @@ -147,7 +151,7 @@ def ticket_from_message(message, queue): send_templated_mail('newticket_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True) else: - update = " (Updated)" + update = _(' (Updated)') if t.assigned_to: send_templated_mail('updated_owner', context, recipients=t.assigned_to.email, sender=queue.from_address, fail_silently=True) @@ -157,7 +161,7 @@ def ticket_from_message(message, queue): f = FollowUp( ticket = t, - title = 'E-Mail Received from %s' % sender_email, + title = _('E-Mail Received from %s' % sender_email), date = datetime.now(), public = True, comment = body, diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html index 9c6df15e..634bfed5 100644 --- a/templates/helpdesk/base.html +++ b/templates/helpdesk/base.html @@ -1,32 +1,32 @@ - +{% load i18n %} - {% block helpdesk_title %}Helpdesk{% endblock %} Powered by Jutda Helpdesk + {% block helpdesk_title %}Helpdesk{% endblock %} :: {% trans "Powered by Jutda Helpdesk" %} - - - + + + {% block helpdesk_head %}{% endblock %}
{% block helpdesk_body %}{% endblock %}
{% include "helpdesk/debug.html" %} diff --git a/templates/helpdesk/create_ticket.html b/templates/helpdesk/create_ticket.html index 25b53b7b..d3a61ae1 100644 --- a/templates/helpdesk/create_ticket.html +++ b/templates/helpdesk/create_ticket.html @@ -1,6 +1,6 @@ -{% extends "helpdesk/base.html" %} +{% extends "helpdesk/base.html" %}{% load i18n %} -{% block helpdesk_title %}Helpdesk{% endblock %} +{% block helpdesk_title %}{% trans "Create Ticket" %}{% endblock %} {% block helpdesk_head %} @@ -12,9 +12,9 @@ {% endblock %} {% block helpdesk_body %} -

Submit a Ticket

+{% blocktrans %}

Submit a Ticket

-

Unless otherwise stated, all fields are required. Please provide as descriptive a title and description as possible.

+

Unless otherwise stated, all fields are required. Please provide as descriptive a title and description as possible.

{% endblocktrans %}
@@ -27,7 +27,7 @@
{{ form.title }}
{% if form.title.errors %}
{{ form.title.errors }}
{% endif %} -
(Optional)
+
{% trans "(Optional)" %}
{{ form.submitter_email }}
{% if form.submitter_email.errors %}
{{ form.submitter_email.errors }}
{% endif %}
{{ form.submitter_email.help_text }}
@@ -36,7 +36,7 @@
{{ form.body }}
{% if form.body.errors %}
{{ form.body.errors }}
{% endif %} -
(Optional)
+
{% trans "(Optional)" %}
{{ form.assigned_to }}
{% if form.assigned_to.errors %}
{{ form.assigned_to.errors }}
{% endif %}
{{ form.assigned_to.help_text }}
@@ -48,7 +48,7 @@
- +
diff --git a/templates/helpdesk/dashboard.html b/templates/helpdesk/dashboard.html index b6dfd476..42469daf 100644 --- a/templates/helpdesk/dashboard.html +++ b/templates/helpdesk/dashboard.html @@ -1,12 +1,12 @@ -{% extends "helpdesk/base.html" %} -{% block helpdesk_title %}Helpdesk Dashboard{% endblock %} +{% extends "helpdesk/base.html" %}{% load i18n %} +{% block helpdesk_title %}{% trans "Helpdesk Dashboard" %}{% endblock %} {% block helpdesk_head %} {% endblock %} {% block helpdesk_body %} - - + + {% for queue in dash_tickets %} @@ -16,13 +16,14 @@ {% endfor %}
Helpdesk Summary
QueueOpenResolved
{% trans "Helpdesk Summary" %}
{% trans "Queue" %}{% trans "Open" %}{% trans "Resolved" %}
{{ queue.name }}
+ - - + + {% for ticket in user_tickets %} - + @@ -32,16 +33,16 @@
Your Tickets
#PrTitleQueueStatusLast Update
{% trans "Your Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Status" %}{% trans "Last Update" %}
{{ ticket.ticket }}Priority {{ ticket.priority }}{% blocktrans with ticket.priority as priority %}Priority {{ priority }}{% endblocktrans %} {{ ticket.title }} {{ ticket.queue }} {{ ticket.get_status }}
- - + + {% for ticket in unassigned_tickets %} - + - + {% endfor %}
Unassigned Tickets
#PrTitleQueueCreated 
{% trans "Unassigned Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Created" %} 
{{ ticket.ticket }}Priority {{ ticket.priority }}{% blocktrans with ticket.priority as priority %}Priority {{ priority }}{% endblocktrans %} {{ ticket.title }} {{ ticket.queue }} {{ ticket.created|timesince }} agoTake{% trans "Take" %}
diff --git a/templates/helpdesk/delete_ticket.html b/templates/helpdesk/delete_ticket.html index 97f3affc..4dd3d8d7 100644 --- a/templates/helpdesk/delete_ticket.html +++ b/templates/helpdesk/delete_ticket.html @@ -1,8 +1,8 @@ -{% extends "helpdesk/base.html" %} +{% extends "helpdesk/base.html" %}{% load i18n %} -{% block helpdesk_title %}Delete Ticket{% endblock %} +{% block helpdesk_title %}{% trans "Delete Ticket" %}{% endblock %} -{% block helpdesk_body %} +{% block helpdesk_body %}{% blocktrans %}

Delete Ticket

Are you sure you want to delete this ticket ({{ ticket.title }})? All traces of the ticket, including followups, attachments, and updates will be irreversably removed.

@@ -10,4 +10,4 @@

No, Don't Delete It

-{% endblock %} +{% endblocktrans %}{% endblock %} diff --git a/templates/helpdesk/public_base.html b/templates/helpdesk/public_base.html index 9f14de12..f473373d 100644 --- a/templates/helpdesk/public_base.html +++ b/templates/helpdesk/public_base.html @@ -1,6 +1,6 @@ - +{% load i18n %} -{% block helpdesk_title %}Helpdesk{% endblock %} +{% block helpdesk_title %}{% trans "Helpdesk" %}{% endblock %} {% block helpdesk_head %}{% endblock %} @@ -8,17 +8,17 @@
{% block helpdesk_body %}{% endblock %}
{% include "helpdesk/debug.html" %} diff --git a/templates/helpdesk/public_homepage.html b/templates/helpdesk/public_homepage.html index 82d807ec..9db454d4 100644 --- a/templates/helpdesk/public_homepage.html +++ b/templates/helpdesk/public_homepage.html @@ -1,5 +1,4 @@ -{% extends "helpdesk/public_base.html" %} -{% block helpdesk_title %}Helpdesk{% endblock %} +{% extends "helpdesk/public_base.html" %}{% load i18n %} {% block helpdesk_head %} @@ -11,25 +10,25 @@ {% endblock %} {% block helpdesk_body %} -

View a Ticket

+

{% trans "View a Ticket" %}

-
+
-
+
- +
-

Submit a Ticket

+

{% trans "Submit a Ticket" %}

-

All fields are required. Please provide as descriptive a title and description as possible.

+

{% trans "All fields are required. Please provide as descriptive a title and description as possible." %}

@@ -63,7 +62,7 @@
- +
diff --git a/templates/helpdesk/public_view_form.html b/templates/helpdesk/public_view_form.html index 2626102d..b53f1c39 100644 --- a/templates/helpdesk/public_view_form.html +++ b/templates/helpdesk/public_view_form.html @@ -1,23 +1,24 @@ -{% extends "helpdesk/public_base.html" %} -{% block helpdesk_title %}Helpdesk{% endblock %} +{% extends "helpdesk/public_base.html" %}{% load i18n %} {% block helpdesk_body %} -

View a Ticket

+

{% trans "View a Ticket" %}

-{% if error_message %}

Error: {{ error_message }}

{% endif %} +{% if error_message %}

{% trans "Error:" %} {{ error_message }}

{% endif %}
-
-
+
+
-
-
+
+
- +
+ + {% endblock %} diff --git a/templates/helpdesk/public_view_ticket.html b/templates/helpdesk/public_view_ticket.html index f832027f..b5f91457 100644 --- a/templates/helpdesk/public_view_ticket.html +++ b/templates/helpdesk/public_view_ticket.html @@ -1,5 +1,5 @@ -{% extends "helpdesk/public_base.html" %} -{% block helpdesk_title %}Helpdesk{% endblock %} +{% extends "helpdesk/public_base.html" %}{% load i18n %} +{% block helpdesk_title %}{% trans "View a Ticket" %}{% endblock %} {% block helpdesk_head %} @@ -9,58 +9,58 @@ {% load in_list %}
- +
- +
- {% for s in status_choices %} {{ s.1 }}{% endfor %} + {% for s in status_choices %} {{ s.1 }}{% endfor %}
- +
- +
- - + + {% if tickets %}{% for ticket in tickets %} - + @@ -68,7 +68,7 @@ {% endfor %}{% else %} - + {% endif %}
Tickets
#PrTitleQueueStatusCreatedOwner
{% trans "Tickets" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Status" %}{% trans "Created" %}{% trans "Owner" %}
{{ ticket.ticket }}Priority {{ ticket.priority }}{% trans "Priority {{ ticket.priority }}" %} {{ ticket.title }} {{ ticket.queue }} {{ ticket.get_status }}{{ ticket.get_assigned_to }}
No Tickets Match Your Selection
{% trans "No Tickets Match Your Selection" %}
diff --git a/templates/registration/logged_out.html b/templates/registration/logged_out.html new file mode 100644 index 00000000..79d1b8c6 --- /dev/null +++ b/templates/registration/logged_out.html @@ -0,0 +1,9 @@ +{% extends "helpdesk/public_base.html" %}{% load i18n %} +{% block helpdesk_title %}{% trans "Logged Out" %}{% endblock %} + +{% block helpdesk_body %}{% blocktrans %} +

Logged Out

+ +

Thanks for being here. Hopefully you've helped resolve a few tickets and make the world a better place.

+ +{% endblocktrans %}{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index e9bb0bc4..8fae35e3 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -1,20 +1,20 @@ -{% extends "helpdesk/public_base.html" %} -{% block helpdesk_title %}Helpdesk Login{% endblock %} +{% extends "helpdesk/public_base.html" %}{% load i18n %} +{% block helpdesk_title %}{% trans "Helpdesk Login" %}{% endblock %} {% block helpdesk_body %} -

Login

+

{% trans "Login" %}

-

To log in and begin responding to cases, simply enter your username and password below.

+

{% trans "To log in and begin responding to cases, simply enter your username and password below." %}

- {% if form.has_errors %}

Your username and password didn't match. Please try again.

{% endif %} + {% if form.has_errors %}

{% trans "Your username and password didn't match. Please try again." %}

{% endif %}
-
+
{{ form.username }}
-
+
{{ form.password }}
- +
{% endblock %} diff --git a/templates/registration/logout.html b/templates/registration/logout.html deleted file mode 100644 index 28af317d..00000000 --- a/templates/registration/logout.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "helpdesk/public_base.html" %} -{% block helpdesk_title %}Helpdesk{% endblock %} - -{% block helpdesk_body %} -

Logout

- -

Thanks for being here. Hopefully you've helped resolve a few tickets and make the world a better place.

- -{% endblock %} diff --git a/views.py b/views.py index 3a412015..ef972f48 100644 --- a/views.py +++ b/views.py @@ -15,9 +15,10 @@ from django.db.models import Q from django.http import HttpResponseRedirect, Http404, HttpResponse from django.shortcuts import render_to_response, get_object_or_404 from django.template import loader, Context, RequestContext +from django.utils.translation import ugettext as _ from helpdesk.forms import TicketForm, PublicTicketForm -from helpdesk.lib import send_templated_mail, line_chart, bar_chart +from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment def dashboard(request): @@ -43,7 +44,7 @@ def dashboard(request): GROUP BY queue, name ORDER BY q.id; """) - dash_tickets = cursor.dictfetchall() + dash_tickets = query_to_dict(cursor.fetchall(), cursor.description) return render_to_response('helpdesk/dashboard.html', RequestContext(request, { @@ -92,7 +93,7 @@ def view_ticket(request, ticket_id): owner = 0 else: owner = ticket.assigned_to.id - request.POST = {'new_status': Ticket.CLOSED_STATUS, 'public': 1, 'owner': owner, 'title': ticket.title, 'comment': "Accepted resolution and closed ticket"} + request.POST = {'new_status': Ticket.CLOSED_STATUS, 'public': 1, 'owner': owner, 'title': ticket.title, 'comment': _('Accepted resolution and closed ticket')} return update_ticket(request, ticket_id) return render_to_response('helpdesk/ticket.html', @@ -126,11 +127,11 @@ def update_ticket(request, ticket_id): if owner: if owner != 0 and (ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to: new_user = User.objects.get(id=owner) - f.title = 'Assigned to %s' % new_user.username + f.title = _('Assigned to %(username)s') % {'username': new_user.username} ticket.assigned_to = new_user reassigned = True else: - f.title = 'Unassigned' + f.title = _('Unassigned') ticket.assigned_to = None if new_status != ticket.status: @@ -144,19 +145,19 @@ def update_ticket(request, ticket_id): if not f.title: if f.comment: - f.title = 'Comment' + f.title = _('Comment') else: - f.title = 'Updated' + f.title = _('Updated') f.save() if title != ticket.title: - c = TicketChange(followup=f, field='Title', old_value=ticket.title, new_value=title) + c = TicketChange(followup=f, field=_('Title'), old_value=ticket.title, new_value=title) c.save() ticket.title = title if priority != ticket.priority: - c = TicketChange(followup=f, field='Priority', old_value=ticket.priority, new_value=priority) + c = TicketChange(followup=f, field=_('Priority'), old_value=ticket.priority, new_value=priority) c.save() ticket.priority = priority @@ -315,7 +316,7 @@ def public_view(request): RequestContext(request, {'ticket': t,})) except: t = False; - error_message = 'Invalid ticket ID or e-mail address. Please try again.' + error_message = _('Invalid ticket ID or e-mail address. Please try again.') return render_to_response('helpdesk/public_view_form.html', RequestContext(request, { @@ -329,10 +330,10 @@ def hold_ticket(request, ticket_id, unhold=False): if unhold: ticket.on_hold = False - title = 'Ticket taken off hold' + title = _('Ticket taken off hold') else: ticket.on_hold = True - title = 'Ticket placed on hold' + title = _('Ticket placed on hold') f = FollowUp( ticket = ticket, @@ -368,15 +369,15 @@ def run_report(request, report): priority_sql = [] priority_columns = [] for p in Ticket.PRIORITY_CHOICES: - priority_sql.append("COUNT(CASE t.priority WHEN '%s' THEN t.id END) AS \"%s\"" % (p[0], p[1])) - priority_columns.append("%s" % p[1]) + priority_sql.append("COUNT(CASE t.priority WHEN '%s' THEN t.id END) AS \"%s\"" % (p[0], p[1]._proxy____unicode_cast())) + priority_columns.append("%s" % p[1]._proxy____unicode_cast()) priority_sql = ", ".join(priority_sql) status_sql = [] status_columns = [] for s in Ticket.STATUS_CHOICES: - status_sql.append("COUNT(CASE t.status WHEN '%s' THEN t.id END) AS \"%s\"" % (s[0], s[1])) - status_columns.append("%s" % s[1]) + status_sql.append("COUNT(CASE t.status WHEN '%s' THEN t.id END) AS \"%s\"" % (s[0], s[1]._proxy____unicode_cast())) + status_columns.append("%s" % s[1]._proxy____unicode_cast()) status_sql = ", ".join(status_sql) queue_sql = [] @@ -483,7 +484,7 @@ def run_report(request, report): from django.db import connection cursor = connection.cursor() cursor.execute(sql) - report_output = cursor.dictfetchall() + report_output = query_to_dict(cursor.fetchall(), cursor.description) data = []