From 3453de52d5d3081f7547d0203c8c12ad4b0eff5d Mon Sep 17 00:00:00 2001 From: demo Date: Tue, 8 Nov 2011 16:14:04 +0100 Subject: [PATCH 01/40] corrected doc on migrate to fit actual needs --- docs/install.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 82a55c78..da306c6c 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -56,11 +56,7 @@ Adding To Your Django Project ./manage.py syncdb - If you're wise enough to use South, the first migration will need to be a fake:: - - ./manage.py migrate helpdesk 0001 --fake - - After the initial migration, all others are done in the usual way:: + Then migrate using South ./manage.py migrate helpdesk From 23463c902eeff1c0102cef81293de54ee32298bd Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Tue, 8 Nov 2011 17:31:05 +0100 Subject: [PATCH 02/40] Using safe_template_context to build templated email context forms.py Without this send_templated_mail does not work (Exception on context['queue'].get('locale', 'en') --- helpdesk/forms.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/helpdesk/forms.py b/helpdesk/forms.py index 502a90bb..bfc61bcd 100644 --- a/helpdesk/forms.py +++ b/helpdesk/forms.py @@ -15,7 +15,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.utils.translation import ugettext as _ -from helpdesk.lib import send_templated_mail +from helpdesk.lib import send_templated_mail, safe_template_context from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC, CustomField, TicketCustomFieldValue, TicketDependency from helpdesk.settings import HAS_TAG_SUPPORT @@ -280,11 +280,8 @@ class TicketForm(forms.Form): # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) - context = { - 'ticket': t, - 'queue': q, - 'comment': f.comment, - } + context = safe_template_context(t) + context['comment'] = f.comment messages_sent_to = [] @@ -482,10 +479,7 @@ class PublicTicketForm(forms.Form): # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) - context = { - 'ticket': t, - 'queue': q, - } + context = safe_template_context(t) messages_sent_to = [] From 08efeb1fc900823befa0b1038d0a2603735b485a Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Tue, 8 Nov 2011 17:57:26 +0100 Subject: [PATCH 03/40] fixing month index error in reports month has to span from 0 to 11 not 1 to 12 --- helpdesk/views/staff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index bc80f60a..418e0f91 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -833,7 +833,7 @@ def run_report(request, report): month = 1 if (year > last_year) or (month > last_month and year >= last_year): working = False - periods.append("%s %s" % (months[month], year)) + periods.append("%s %s" % (months[month - 1], year)) if report == 'userpriority': title = _('User by Priority') From ab84017dd59475e0a9a37f098daa07f3135ca586 Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Wed, 9 Nov 2011 16:37:37 +0100 Subject: [PATCH 04/40] more fixes on templated mail and safe context --- helpdesk/management/commands/escalate_tickets.py | 9 +++------ helpdesk/management/commands/get_email.py | 7 ++----- helpdesk/views/api.py | 16 +++++----------- helpdesk/views/staff.py | 9 ++++----- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/helpdesk/management/commands/escalate_tickets.py b/helpdesk/management/commands/escalate_tickets.py index 13f6b712..b6050061 100644 --- a/helpdesk/management/commands/escalate_tickets.py +++ b/helpdesk/management/commands/escalate_tickets.py @@ -13,12 +13,12 @@ import getopt from optparse import make_option import sys -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError 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 +from helpdesk.lib import send_templated_mail, safe_template_context class Command(BaseCommand): @@ -99,10 +99,7 @@ def escalate_tickets(queues, verbose): t.priority -= 1 t.save() - context = { - 'ticket': t, - 'queue': q, - } + context = safe_template_context(t) if t.submitter_email: send_templated_mail( diff --git a/helpdesk/management/commands/get_email.py b/helpdesk/management/commands/get_email.py index 795fc2e5..c3b9e33f 100644 --- a/helpdesk/management/commands/get_email.py +++ b/helpdesk/management/commands/get_email.py @@ -26,7 +26,7 @@ from django.core.management.base import BaseCommand from django.db.models import Q from django.utils.translation import ugettext as _ -from helpdesk.lib import send_templated_mail +from helpdesk.lib import send_templated_mail, safe_template_context from helpdesk.models import Queue, Ticket, FollowUp, Attachment, IgnoreEmail @@ -246,10 +246,7 @@ def ticket_from_message(message, queue, quiet): t.status = Ticket.REOPENED_STATUS t.save() - context = { - 'ticket': t, - 'queue': queue, - } + context = safe_template_context(t) if new: diff --git a/helpdesk/views/api.py b/helpdesk/views/api.py index 236f4842..0c394b86 100644 --- a/helpdesk/views/api.py +++ b/helpdesk/views/api.py @@ -22,7 +22,7 @@ from django.template import loader, Context from django.utils import simplejson from helpdesk.forms import TicketForm -from helpdesk.lib import send_templated_mail +from helpdesk.lib import send_templated_mail, safe_template_context from helpdesk.models import Ticket, Queue, FollowUp STATUS_OK = 200 @@ -191,11 +191,8 @@ class API: f.save() - context = { - 'ticket': ticket, - 'queue': ticket.queue, - 'comment': f.comment, - } + context = safe_template_context(ticket) + context['comment'] = f.comment messages_sent_to = [] @@ -266,11 +263,8 @@ class API: ) f.save() - context = { - 'ticket': ticket, - 'queue': ticket.queue, - 'resolution': f.comment, - } + context = safe_template_context(ticket) + context['resolution'] = f.comment subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title) diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 418e0f91..6e5f38af 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -419,11 +419,10 @@ 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.save() # Send email to Submitter, Owner, Queue CC - context = { - 'ticket': t, - 'queue': t.queue, - 'resolution': t.resolution, - } + context = safe_template_context(t) + context.update( + resolution=t.resolution, + ) messages_sent_to = [] From d78ea39c693cd40449e448afb1e60954d2eb4e9b Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Wed, 9 Nov 2011 18:29:57 +0100 Subject: [PATCH 05/40] Document the need of 'django.core.context_processors.request' in docs/settings.rst --- docs/settings.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index 6e9d250a..c1e89089 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,6 +1,14 @@ Settings ======== +First, django-helpdesk needs ``django.core.context_processors.request`` activated, so in your ``settings.py`` add:: + + from django.conf import global_settings + TEMPLATE_CONTEXT_PROCESSORS = ( + global_settings.TEMPLATE_CONTEXT_PROCESSORS + + ('django.core.context_processors.request',) + ) + The following settings can be changed in your ``settings.py`` file to help change the way django-helpdesk operates. HELPDESK_DEFAULT_SETTINGS From 89cc11cb8eb406c60e575d7ff85d8fa8fc369948 Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Thu, 10 Nov 2011 12:18:16 +0100 Subject: [PATCH 06/40] French translations for notifications and headers / footers --- helpdesk/fixtures/initial_data.json | 192 ++++++++++++++++++ .../helpdesk/fr/email_html_base.html | 9 + .../helpdesk/fr/email_text_footer.txt | 6 + 3 files changed, 207 insertions(+) create mode 100644 helpdesk/templates/helpdesk/fr/email_html_base.html create mode 100644 helpdesk/templates/helpdesk/fr/email_text_footer.txt diff --git a/helpdesk/fixtures/initial_data.json b/helpdesk/fixtures/initial_data.json index 1ecb3262..8f6a5ed9 100644 --- a/helpdesk/fixtures/initial_data.json +++ b/helpdesk/fixtures/initial_data.json @@ -574,5 +574,197 @@ "subject": "(Aktualisiert)", "locale": "de" } + }, + { + "pk": 49, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket Assign\u00e9", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} ({{ ticket.title }}) par {{ ticket.submitter_email }} {% if ticket.assigned_to %}a \u00e9t\u00e9 assign\u00e9 \u00e0 {{ ticket.assigned_to }}{% else %} n'est plus assign\u00e9 \u00e0 personne{% endif %}.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }} {% if ticket.assigned_to %} a \u00e9t\u00e9 assign\u00e9 \u00e0 {{ ticket.assigned_to }}{% else %} n'est plus assign\u00e9 \u00e0 personne{% endif %}.\r\n\r\nIdentifiant\u00a0: {{ ticket.ticket }}\r\nFile d'attente\u00a0: {{ queue.title }}\r\nTitre\u00a0: {{ ticket.title }}\r\nOuvert le\u00a0: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par\u00a0: {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9\u00a0: {{ ticket.get_priority_display }}\r\nStatut\u00a0: {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0\u00a0: {{ ticket.get_assigned_to }}\r\nAdresse\u00a0: {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait\u00a0:\r\n\r\n{{ ticket.description }}\r\n\r\n", + "subject": "(Assign\u00e9)", + "template_name": "assigned_cc" + } + }, + { + "pk": 50, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Le ticket vous est assign\u00e9", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} ({{ ticket.title }}) pour {{ ticket.submitter_email }} vous a \u00e9t\u00e9 assign\u00e9.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") pour {{ ticket.submitter_email }} vous a \u00e9t\u00e9 assign\u00e9.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}", + "subject": "(Pour vous)", + "template_name": "assigned_owner" + } + }, + { + "pk": 51, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket Ferm\u00e9", + "html": "

Bonjour,

\r\n\r\n

Le ticket {{ ticket.title }} ('{{ ticket.title }}'){% if ticket.assigned_to %}, assign\u00e9 \u00e0 {{ ticket.get_assigned_to }}{% endif %} a \u00e9t\u00e9 ferm\u00e9.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n

La motivation de r\u00e9solution est:

\r\n\r\n
{{ resolution }}
", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nLe ticket {{ ticket.title }} (\"{{ ticket.title }}\"){% if ticket.assigned_to %}, assign\u00e9 \u00e0 {{ ticket.assigned_to }}{% endif %} a \u00e9t\u00e9 ferm\u00e9.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n\r\nLa motivation de r\u00e9solution est:\r\n\r\n{{ resolution }}\r\n\r\n", + "subject": "(Ferm\u00e9)", + "template_name": "closed_cc" + } + }, + { + "pk": 52, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket Ferm\u00e9", + "html": "

Bonjour,

\r\n\r\n

\r\nLe ticket suivant qui vous est actuellement assign\u00e9 a \u00e9t\u00e9 ferm\u00e9.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n

La motivation de r\u00e9solution est:

\r\n\r\n
{{ resolution }}
\r\n", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nLe ticket suivant qui vous est actuellement assign\u00e9 a \u00e9t\u00e9 ferm\u00e9.\r\n\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }} (authentification obligatoire)\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n\r\nLa motivation de r\u00e9solution est:\r\n\r\n{{ resolution }}", + "subject": "(Ferm\u00e9 - \u00e0 vous)", + "template_name": "closed_owner" + } + }, + { + "pk": 53, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket Ferm\u00e9", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }}. Ce courriel vous confirme que ce ticket a \u00e9t\u00e9 ferm\u00e9.

\r\n\r\n

\r\n\r\n

{{ ticket.resolution }}
\r\n\r\n

Vous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Si vous pensez que nous devons encore travailler sur ce probl\u00e8me, faites le nous savoir en r\u00e9pondant \u00e0 ce courriel en conservant le sujet tel-quel..

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\". Ce courriel vous confirme que ce ticket a \u00e9t\u00e9 ferm\u00e9.\r\n\r\nSi vous pensez que nous devons encore travailler sur ce probl\u00e8me, faites le nous savoir en r\u00e9pondant \u00e0 ce courriel en conservant le sujet tel-quel.\r\n\r\nVous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\nLa r\u00e9solution a \u00e9t\u00e9 motiv\u00e9e ainsi\u00a0:\r\n\r\n{{ ticket.resolution }}\r\n\r\n", + "subject": "(Ferm\u00e9)", + "template_name": "closed_submitter" + } + }, + { + "pk": 54, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Priorit\u00e9 du ticket augment\u00e9e", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} ('{{ ticket.title }}') a vu sa priorit\u00e9 augment\u00e9 de mani\u00e8re automatique.

\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") a vu sa priorit\u00e9 augment\u00e9 de mani\u00e8re automatique.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n", + "subject": "(Priorit\u00e9 augment\u00e9e)", + "template_name": "escalated_cc" + } + }, + { + "pk": 55, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Votre ticket a vu sa priorit\u00e9 augment\u00e9e", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }} . Ce courriel vous informe que ce ticket a vu sa priorit\u00e9 augment\u00e9 de mani\u00e8re automatique, vu son d\u00e9lai de r\u00e9solution plus long que pr\u00e9vu.

\r\n\r\n

Nous allons reprendre rapidement ce ticket afin d'essayer de le r\u00e9soudre le plus vite possible.

\r\n\r\n

Vous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\". Ce courriel vous informe que ce ticket a vu sa priorit\u00e9 augment\u00e9 de mani\u00e8re automatique, vu son d\u00e9lai de r\u00e9solution plus long que pr\u00e9vu.\r\n\r\nNous allons reprendre rapidement ce ticket afin d'essayer de le r\u00e9soudre le plus vite possible.\r\n\r\nVous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\n", + "subject": "(Priorit\u00e9 augment\u00e9e)", + "template_name": "escalated_submitter" + } + }, + { + "pk": 56, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Priorit\u00e9 de votre ticket augment\u00e9e", + "html": "

Bonjour,

\r\n\r\n

Un ticket qui vous est assign\u00e9 a vu sa priorit\u00e9 augment\u00e9 vu son d\u00e9lai de r\u00e9solution plus long que pr\u00e9vu.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n\r\n

Merci de reprendre ce ticket afin d'essayer de le r\u00e9soudre le plus vite possible..

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nUn ticket qui vous est assign\u00e9 a vu sa priorit\u00e9 augment\u00e9 vu son d\u00e9lai de r\u00e9solution plus long que pr\u00e9vu.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n\r\nMerci de reprendre ce ticket afin d'essayer de le r\u00e9soudre le plus vite possible.\r\n", + "subject": "(Priorit\u00e9 augment\u00e9e - \u00e0 vous)", + "template_name": "escalated_owner" + } + }, + { + "pk": 57, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Nouveau ticket ouvert", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir qu'un nouveau ticket a \u00e9t\u00e9 ouvert.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Description :

\r\n\r\n
{{ ticket.description }}
", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir qu'un nouveau ticket a \u00e9t\u00e9 ouvert.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nDescription\u00a0:\r\n{{ ticket.description }}\r\n\r\n", + "subject": "(Ouvert)", + "template_name": "newticket_cc" + } + }, + { + "pk": 58, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Votre ticket est d\u00e9sormais ouvert", + "html": "

Bonjour,

\r\n\r\n

Ce courriel permet de vous informer que nous avons re\u00e7u votre demande de support dont le sujet est {{ ticket.title }}.

\r\n\r\n

{{ ticket.ticket }} et sera trait\u00e9 rapidement.

\r\n\r\n

Si vous voulez nous donner plus de d\u00e9tails ou si vous avez une question concernant ce ticket, merci d'inclure la r\u00e9f\u00e9rence {{ ticket.ticket }} dans le sujet du message. Le plus simple \u00e9tant d'utiliser la fonction 'r\u00e9pondre' de votre logiciel de messagerie.

\r\n\r\n

Vous pouvez visualiser ce ticket en ligne et y ajouter des informations ou des pi\u00e8ces jointes ainsi que voir les derni\u00e8res mies \u00e0 jour en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Nous allons traiter votre demande afin, si possible, de la r\u00e9soudre au plus vite. Vous recevrez des mise \u00e0 jour ou la r\u00e9ponse au ticket \u00e0 cette adresse mail.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel permet de vous informer que nous avons re\u00e7u votre demande de support dont le sujet est \"{{ ticket.title }}\".\r\n\r\nVous n'avez rien de plus \u00e0 faire pour le moment. Votre ticket porte l'identifiant {{ ticket.ticket }} et sera trait\u00e9 rapidement.\r\n\r\nSi vous voulez nous donner plus de d\u00e9tails ou si vous avez une question concernant ce ticket, merci d'inclure la r\u00e9f\u00e9rence '{{ ticket.ticket }}' dans le sujet du message. Le plus simple \u00e9tant d'utiliser la fonction 'r\u00e9pondre' de votre logiciel de messagerie.\r\n\r\nVous pouvez visualiser ce ticket en ligne et y ajouter des informations ou des pi\u00e8ces jointes ainsi que voir les derni\u00e8res mies \u00e0 jour en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\nNous allons traiter votre demande afin, si possible, de la r\u00e9soudre au plus vite. Vous recevrez des mise \u00e0 jour ou la r\u00e9ponse au ticket \u00e0 cette adresse mail.", + "subject": "(Ouvert)", + "template_name": "newticket_submitter" + } + }, + { + "pk": 59, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket r\u00e9solu", + "html": "

Bonjour,

\r\n\r\n

Le ticket suivant a \u00e9t\u00e9 r\u00e9solu.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n

La motivation de r\u00e9solution est:

\r\n\r\n
{{ resolution }}
\r\n\r\n

\r\nCette information a \u00e9t\u00e9 envoy\u00e9 au cr\u00e9ateur de ce ticket, qui la confirmera avant que vous puissiez fermer ce ticket.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nLe ticket suivant a \u00e9t\u00e9 r\u00e9solu.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n\r\nLa motivation de r\u00e9solution est:\r\n\r\n{{ resolution }}\r\n\r\nCette information a \u00e9t\u00e9 envoy\u00e9 au cr\u00e9ateur de ce ticket, qui la confirmera avant que vous puissiez fermer ce ticket.\r\n\r\n", + "subject": "(R\u00e9solu)", + "template_name": "resolved_cc" + } + }, + { + "pk": 60, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket r\u00e9solu", + "html": "

Bonjour,

\r\n\r\n

Un ticket qui vous est assign\u00e9 a \u00e9t\u00e9 r\u00e9solu.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n

La motivation de r\u00e9solution est:

\r\n\r\n
{{ resolution }}
\r\n\r\n

\r\nCette information a \u00e9t\u00e9 envoy\u00e9 au cr\u00e9ateur de ce ticket, qui la confirmera avant que vous puissiez fermer ce ticket.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nUn ticket qui vous est assign\u00e9 a \u00e9t\u00e9 r\u00e9solu.\r\n\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nLa description originelle \u00e9tait :\r\n\r\n{{ ticket.description }}\r\n\r\nLa motivation de r\u00e9solution est:\r\n\r\n{{ resolution }}\r\n\r\nCette information a \u00e9t\u00e9 envoy\u00e9 au cr\u00e9ateur de ce ticket, qui la confirmera avant que vous puissiez fermer ce ticket.\r\n\r\n", + "subject": "(R\u00e9solu - \u00e0 vous)", + "template_name": "resolved_owner" + } + }, + { + "pk": 61, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Votre ticket a \u00e9t\u00e9 r\u00e9solu", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }}. Ce message vous informe d'une r\u00e9solution de la demande.

\r\n\r\n

La solution suivante a \u00e9t\u00e9 donn\u00e9e au ticket {{ ticket.ticket }}:

\r\n\r\n
{{ resolution }}
\r\n\r\n

Merci de confirmer que cette solution vous convient afin que nous puissions clore le ticket. Si vous avez d'autre demandes, o\u00f9 si vous pensez que cette solution n'est pas adapt\u00e9e, merci de r\u00e9pondre \u00e0 ce mail en conservant le sujet tel-quel.

\r\n\r\n

Vous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\" . Ce message vous informe d'une r\u00e9solution de la demande.\r\n\r\nLa solution suivante a \u00e9t\u00e9 donn\u00e9e au ticket {{ ticket.ticket }}:\r\n\r\n{{ resolution }}\r\n\r\nMerci de confirmer que cette solution vous convient afin que nous puissions clore le ticket. Si vous avez d'autre demandes, o\u00f9 si vous pensez que cette solution n'est pas adapt\u00e9e, merci de r\u00e9pondre \u00e0 ce mail en conservant le sujet tel-quel.\r\n\r\nVous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\n", + "subject": "(R\u00e9solu)", + "template_name": "resolved_submitter" + } + }, + { + "pk": 62, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket mis \u00e0 jour", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }} a \u00e9t\u00e9 mis \u00e0 jour.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n\r\n

Le commentaire suivant a \u00e9t\u00e9 ajout\u00e9 :

\r\n\r\n
{{ comment }}
\r\n\r\n

Cette information {% if private %} n' a pas {% else %} a {% endif %} \u00e9t\u00e9 envoy\u00e9 par mail \u00e0 l'\u00e9metteur.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }} a \u00e9t\u00e9 mis \u00e0 jour.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nDescription originelle :\r\n\r\n{{ ticket.description }}\r\n\r\nLe commentaire suivant a \u00e9t\u00e9 ajout\u00e9\u00a0:\r\n\r\n{{ comment }}\r\n\r\nCette information {% if private %} n' a pas {% else %} a {% endif %} \u00e9t\u00e9 envoy\u00e9 par mail \u00e0 l'\u00e9metteur.\r\n\r\n", + "subject": "(Mis \u00e0 jour)", + "template_name": "updated_cc" + } + }, + { + "pk": 63, + "model": "helpdesk.emailtemplate", + "fields": { + "heading": "Ticket mis \u00e0 jour", + "html": "

Bonjour,

\r\n\r\n

Ce courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assign\u00e9, a \u00e9t\u00e9 mis \u00e0 jour.

\r\n\r\n

\r\nFile d'attente : {{ ticket.ticket }}
\r\nQueue : {{ queue.title }}
\r\nTitre : {{ ticket.title }}
\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}
\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}
\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}
\r\nStatut : {{ ticket.get_status }}
\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}
\r\nVoir le ticket en ligne pour le mettre \u00e0 jour (apr\u00e8s authentification)

\r\n\r\n

Pour m\u00e9moire, la description originelle \u00e9tait :

\r\n\r\n
{{ ticket.description }}
\r\n\r\n

Le commentaire suivant a \u00e9t\u00e9 ajout\u00e9 :

\r\n\r\n
{{ comment }}
\r\n\r\n

Cette information {% if private %} n' a pas {% else %} a {% endif %} \u00e9t\u00e9 envoy\u00e9 par mail \u00e0 l'\u00e9metteur.

", + "locale": "fr", + "plain_text": "Hello,\r\n\r\nCe courriel indicatif permet de vous pr\u00e9venir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assign\u00e9, a \u00e9t\u00e9 mis \u00e0 jour.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Unknown\" }}\r\nPriorit\u00e9 : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssign\u00e9 \u00e0 : {{ ticket.get_assigned_to }}\r\nAdresse : {{ ticket.staff_url }}\r\n\r\nDescription originelle :\r\n\r\n{{ ticket.description }}\r\n\r\nLe commentaire suivant a \u00e9t\u00e9 ajout\u00e9 :\r\n\r\n{{ comment }}\r\n\r\nCette information {% if private %} n' a pas {% else %} a {% endif %} \u00e9t\u00e9 envoy\u00e9 par mail \u00e0 l'\u00e9metteur.\r\n\r\n", + "subject": "(Mis \u00e0 jour - \u00e0 vous)", + "template_name": "updated_owner" + } + }, + { + "model": "helpdesk.emailtemplate", + "pk": 64, + "fields": { + "heading": "Votre ticket a \u00e9t\u00e9 mis \u00e0 jour", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }} . Ce message vous informe d'une mise \u00e0 jour du ticket.

\r\n\r\n

Le commentaire suivant a \u00e9t\u00e9 ajout\u00e9 au ticket {{ ticket.ticket }}:

\r\n\r\n
{{ comment }}
\r\n\r\n

{{ ticket.ticket_url }}.

", + "locale": "fr", + "plain_text": "Bonjour,\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\". Ce message vous informe d'une mise \u00e0 jour du ticket.\r\n\r\nLe commentaire suivant a \u00e9t\u00e9 ajout\u00e9 au ticket {{ ticket.ticket }}\u00a0:\r\n\r\n{{ comment }}\r\n\r\nSi vous voulez nous fournir d'autres informations, merci de r\u00e9pondre \u00e0 ce mail en conservant le sujet tel-quel. Vous pouvez \u00e9galement voir et mettre \u00e0 jour ce ticket en ligne \u00e0 l'adresse {{ ticket.ticket_url }}", + "subject": "(Mis \u00e0 jour)", + "template_name": "updated_submitter" + } } ] diff --git a/helpdesk/templates/helpdesk/fr/email_html_base.html b/helpdesk/templates/helpdesk/fr/email_html_base.html new file mode 100644 index 00000000..02415ba1 --- /dev/null +++ b/helpdesk/templates/helpdesk/fr/email_html_base.html @@ -0,0 +1,9 @@ +

{% block header %}Helpdesk{% endblock %}

+ +{% block content %}{% endblock %} + +

Cordialement,

+ +

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

+ +

Ce courriel vous a été envoyé en tant qu'utilisateur de notre service de support, en accord avec notre politique de confidentialité. Merci de nous informer si vous pensez que ce message ne vous était pas destiné.

diff --git a/helpdesk/templates/helpdesk/fr/email_text_footer.txt b/helpdesk/templates/helpdesk/fr/email_text_footer.txt new file mode 100644 index 00000000..b1ceeaa3 --- /dev/null +++ b/helpdesk/templates/helpdesk/fr/email_text_footer.txt @@ -0,0 +1,6 @@ +Cordialement, + +{{ queue.title }}{% if queue.email_address %} +{{ queue.email_address }}{% endif %} + +Ce courriel vous a été envoyé en tant qu'utilisateur de notre service de support, en accord avec notre politique de confidentialité. Merci de nous informer si vous pensez que ce message ne vous était pas destiné. From 7b594cfdc0049a1e5fc7ae8930c955a84f06bf45 Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Thu, 10 Nov 2011 12:19:57 +0100 Subject: [PATCH 07/40] Added a warning if no message notification template found in database --- helpdesk/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 05469588..e1f83e70 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -17,6 +17,9 @@ try: except ImportError: from base64 import decodestring as b64decode +import logging +logger = logging.getLogger('helpdesk') + from django.utils.encoding import smart_str def send_templated_mail(template_name, email_context, recipients, sender=None, bcc=None, fail_silently=False, files=None): @@ -66,6 +69,8 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b try: t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True) except EmailTemplate.DoesNotExist: + logger.warning('template "%s" does not exist, no mail sent' % + template_name) return # just ignore if template doesn't exist if not sender: From 5000413bf055d0adb2bd66f9a2a5196c5b13f381 Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Thu, 10 Nov 2011 17:36:58 +0100 Subject: [PATCH 08/40] fixes in french mail templates (passed all through tidy) --- helpdesk/fixtures/initial_data.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpdesk/fixtures/initial_data.json b/helpdesk/fixtures/initial_data.json index 8f6a5ed9..a8bbffe5 100644 --- a/helpdesk/fixtures/initial_data.json +++ b/helpdesk/fixtures/initial_data.json @@ -628,7 +628,7 @@ "model": "helpdesk.emailtemplate", "fields": { "heading": "Ticket Ferm\u00e9", - "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }}. Ce courriel vous confirme que ce ticket a \u00e9t\u00e9 ferm\u00e9.

\r\n\r\n

\r\n\r\n

{{ ticket.resolution }}
\r\n\r\n

Vous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Si vous pensez que nous devons encore travailler sur ce probl\u00e8me, faites le nous savoir en r\u00e9pondant \u00e0 ce courriel en conservant le sujet tel-quel..

", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }}. Ce courriel vous confirme que ce ticket a \u00e9t\u00e9 ferm\u00e9.

\r\n\r\n

\"La r\u00e9solution a \u00e9t\u00e9 motiv\u00e9e ainsi :

\r\n\r\n
{{ ticket.resolution }}
\r\n\r\n

Vous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Si vous pensez que nous devons encore travailler sur ce probl\u00e8me, faites le nous savoir en r\u00e9pondant \u00e0 ce courriel en conservant le sujet tel-quel..

", "locale": "fr", "plain_text": "Bonjour,\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\". Ce courriel vous confirme que ce ticket a \u00e9t\u00e9 ferm\u00e9.\r\n\r\nSi vous pensez que nous devons encore travailler sur ce probl\u00e8me, faites le nous savoir en r\u00e9pondant \u00e0 ce courriel en conservant le sujet tel-quel.\r\n\r\nVous pouvez visualiser ce ticket en ligne, en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\nLa r\u00e9solution a \u00e9t\u00e9 motiv\u00e9e ainsi\u00a0:\r\n\r\n{{ ticket.resolution }}\r\n\r\n", "subject": "(Ferm\u00e9)", @@ -688,7 +688,7 @@ "model": "helpdesk.emailtemplate", "fields": { "heading": "Votre ticket est d\u00e9sormais ouvert", - "html": "

Bonjour,

\r\n\r\n

Ce courriel permet de vous informer que nous avons re\u00e7u votre demande de support dont le sujet est {{ ticket.title }}.

\r\n\r\n

{{ ticket.ticket }} et sera trait\u00e9 rapidement.

\r\n\r\n

Si vous voulez nous donner plus de d\u00e9tails ou si vous avez une question concernant ce ticket, merci d'inclure la r\u00e9f\u00e9rence {{ ticket.ticket }} dans le sujet du message. Le plus simple \u00e9tant d'utiliser la fonction 'r\u00e9pondre' de votre logiciel de messagerie.

\r\n\r\n

Vous pouvez visualiser ce ticket en ligne et y ajouter des informations ou des pi\u00e8ces jointes ainsi que voir les derni\u00e8res mies \u00e0 jour en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Nous allons traiter votre demande afin, si possible, de la r\u00e9soudre au plus vite. Vous recevrez des mise \u00e0 jour ou la r\u00e9ponse au ticket \u00e0 cette adresse mail.

", + "html": "

Bonjour,

\r\n\r\n

Ce courriel permet de vous informer que nous avons re\u00e7u votre demande de support dont le sujet est {{ ticket.title }}.

\r\n\r\n

\"Vous n'avez rien de plus \u00e0 faire pour le moment. Votre ticket porte l'identifiant {{ ticket.ticket }} et sera trait\u00e9 rapidement.

\r\n\r\n

Si vous voulez nous donner plus de d\u00e9tails ou si vous avez une question concernant ce ticket, merci d'inclure la r\u00e9f\u00e9rence {{ ticket.ticket }} dans le sujet du message. Le plus simple \u00e9tant d'utiliser la fonction 'r\u00e9pondre' de votre logiciel de messagerie.

\r\n\r\n

Vous pouvez visualiser ce ticket en ligne et y ajouter des informations ou des pi\u00e8ces jointes ainsi que voir les derni\u00e8res mies \u00e0 jour en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.

\r\n\r\n

Nous allons traiter votre demande afin, si possible, de la r\u00e9soudre au plus vite. Vous recevrez des mise \u00e0 jour ou la r\u00e9ponse au ticket \u00e0 cette adresse mail.

", "locale": "fr", "plain_text": "Bonjour,\r\n\r\nCe courriel permet de vous informer que nous avons re\u00e7u votre demande de support dont le sujet est \"{{ ticket.title }}\".\r\n\r\nVous n'avez rien de plus \u00e0 faire pour le moment. Votre ticket porte l'identifiant {{ ticket.ticket }} et sera trait\u00e9 rapidement.\r\n\r\nSi vous voulez nous donner plus de d\u00e9tails ou si vous avez une question concernant ce ticket, merci d'inclure la r\u00e9f\u00e9rence '{{ ticket.ticket }}' dans le sujet du message. Le plus simple \u00e9tant d'utiliser la fonction 'r\u00e9pondre' de votre logiciel de messagerie.\r\n\r\nVous pouvez visualiser ce ticket en ligne et y ajouter des informations ou des pi\u00e8ces jointes ainsi que voir les derni\u00e8res mies \u00e0 jour en vous rendant \u00e0 l'adresse {{ ticket.ticket_url }}.\r\n\r\nNous allons traiter votre demande afin, si possible, de la r\u00e9soudre au plus vite. Vous recevrez des mise \u00e0 jour ou la r\u00e9ponse au ticket \u00e0 cette adresse mail.", "subject": "(Ouvert)", @@ -760,7 +760,7 @@ "pk": 64, "fields": { "heading": "Votre ticket a \u00e9t\u00e9 mis \u00e0 jour", - "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }} . Ce message vous informe d'une mise \u00e0 jour du ticket.

\r\n\r\n

Le commentaire suivant a \u00e9t\u00e9 ajout\u00e9 au ticket {{ ticket.ticket }}:

\r\n\r\n
{{ comment }}
\r\n\r\n

{{ ticket.ticket_url }}.

", + "html": "

Bonjour,

\r\n\r\n

Vous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est {{ ticket.title }} . Ce message vous informe d'une mise \u00e0 jour du ticket.

\r\n\r\n

Le commentaire suivant a \u00e9t\u00e9 ajout\u00e9 au ticket {{ ticket.ticket }}:

\r\n\r\n
{{ comment }}
\r\n\r\n

\"Si vous voulez nous fournir d'autres informations, merci de r\u00e9pondre \u00e0 ce mail en conservant le sujet tel-quel. Vous pouvez \u00e9galement voir et mettre \u00e0 jour ce ticket en ligne \u00e0 l'adresse {{ ticket.ticket_url }}.

", "locale": "fr", "plain_text": "Bonjour,\r\n\r\nVous avez r\u00e9cemment ouvert chez nous un ticket dont le sujet est \"{{ ticket.title }}\". Ce message vous informe d'une mise \u00e0 jour du ticket.\r\n\r\nLe commentaire suivant a \u00e9t\u00e9 ajout\u00e9 au ticket {{ ticket.ticket }}\u00a0:\r\n\r\n{{ comment }}\r\n\r\nSi vous voulez nous fournir d'autres informations, merci de r\u00e9pondre \u00e0 ce mail en conservant le sujet tel-quel. Vous pouvez \u00e9galement voir et mettre \u00e0 jour ce ticket en ligne \u00e0 l'adresse {{ ticket.ticket_url }}", "subject": "(Mis \u00e0 jour)", From a6f7bd8a25cfc0c4421decfe65859a2218b9e10d Mon Sep 17 00:00:00 2001 From: Alex Garel Date: Thu, 10 Nov 2011 18:10:17 +0100 Subject: [PATCH 09/40] added css class on ticket actions in ticket_desc_table --- helpdesk/templates/helpdesk/ticket_desc_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/templates/helpdesk/ticket_desc_table.html b/helpdesk/templates/helpdesk/ticket_desc_table.html index 01239590..b135a6b1 100644 --- a/helpdesk/templates/helpdesk/ticket_desc_table.html +++ b/helpdesk/templates/helpdesk/ticket_desc_table.html @@ -1,6 +1,6 @@ {% load i18n %} - + From b74f6914b28833d27e10c4add5766a3d63f4a19d Mon Sep 17 00:00:00 2001 From: demo Date: Mon, 28 Nov 2011 18:13:07 +0100 Subject: [PATCH 10/40] handle the case when recipients is not str but unicode --- helpdesk/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index e1f83e70..287906e8 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -61,6 +61,7 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b t = None try: + import pdb;pdb.set_trace() t = EmailTemplate.objects.get(template_name__iexact=template_name, locale=locale) except EmailTemplate.DoesNotExist: pass @@ -101,9 +102,8 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b "{{ ticket.ticket }} {{ ticket.title|safe }} %s" % t.subject ).render(context) - if type(recipients) == str: - if recipients.find(','): - recipients = recipients.split(',') + if isinstance(recipients,(str,unicode)): + recipients = recipients.split(',') elif type(recipients) != list: recipients = [recipients,] From 049b75182b02988731d394a49e580eef96f2c9d5 Mon Sep 17 00:00:00 2001 From: demo Date: Mon, 28 Nov 2011 18:53:01 +0100 Subject: [PATCH 11/40] removed forgotten pdb --- helpdesk/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 287906e8..5a3960f5 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -61,7 +61,6 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b t = None try: - import pdb;pdb.set_trace() t = EmailTemplate.objects.get(template_name__iexact=template_name, locale=locale) except EmailTemplate.DoesNotExist: pass From 1e6fa7a92d058a86d1aa3e036edf2fdb9e61b18d Mon Sep 17 00:00:00 2001 From: Andreas Kotowicz Date: Fri, 2 Dec 2011 12:54:06 +0100 Subject: [PATCH 12/40] add option to 'hide empty queues' in dashboard overview. code to show empty queue is from https://github.com/gjedeer/django-helpdesk/commit/b7df9b949533a80cf931d8b51d0bdea5ca626566 --- helpdesk/settings.py | 3 +++ helpdesk/views/staff.py | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 455a62af..36f3f649 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -109,6 +109,9 @@ HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = getattr(settings, 'HELPDESK_CREATE_TIC # show delete button next to unassigned tickets HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED = getattr(settings, 'HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED', True) +# hide empty queues in dashboard overview? +HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES = getattr(settings, 'HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES', True) + ''' options for footer ''' diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index b4fef2d7..f9c9269e 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -85,18 +85,34 @@ def dashboard(request): # Queue 2 4 12 cursor = connection.cursor() - cursor.execute(""" - SELECT q.id as queue, - 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 '3' THEN t.id END) AS resolved, - COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed - FROM helpdesk_ticket t, - helpdesk_queue q - WHERE q.id = t.queue_id - GROUP BY queue, name - ORDER BY q.id; - """) + if helpdesk_settings.HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES: + cursor.execute(""" + SELECT q.id as queue, + 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 '3' THEN t.id END) AS resolved, + COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed + FROM helpdesk_ticket t, + helpdesk_queue q + WHERE q.id = t.queue_id + GROUP BY queue, name + ORDER BY q.id; + """) + else: + cursor.execute(""" + SELECT q.id as queue, + 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 '3' THEN t.id END) AS resolved, + COUNT(CASE t.status WHEN '4' THEN t.id END) AS closed + FROM helpdesk_queue q + LEFT OUTER JOIN helpdesk_ticket t + ON q.id = t.queue_id + GROUP BY queue, name + ORDER BY q.id; + """) + + dash_tickets = query_to_dict(cursor.fetchall(), cursor.description) return render_to_response('helpdesk/dashboard.html', From 50406665c923968a3ef45ee7351919590935d618 Mon Sep 17 00:00:00 2001 From: Andreas Kotowicz Date: Fri, 2 Dec 2011 17:47:30 +0100 Subject: [PATCH 13/40] jquery translate temporary work arounds: - google translate api v1 is deprecated, for v2 you need to pay. - the google translate widget is still available for free. I left the jquery translate code intact, because maybe we can get jquery-translate to work with microsoft's translation service. --- ...adapter.ui.progress.native.parallel.min.js | 43 - .../helpdesk/jquery.translate-debug-all.js | 1546 +++++++++++++++++ helpdesk/templates/helpdesk/base.html | 22 +- helpdesk/templates/helpdesk/ticket.html | 12 + 4 files changed, 1574 insertions(+), 49 deletions(-) delete mode 100644 helpdesk/static/helpdesk/jquery.translate-1.4.7.NCT.core.NCT-adapter.ui.progress.native.parallel.min.js create mode 100644 helpdesk/static/helpdesk/jquery.translate-debug-all.js diff --git a/helpdesk/static/helpdesk/jquery.translate-1.4.7.NCT.core.NCT-adapter.ui.progress.native.parallel.min.js b/helpdesk/static/helpdesk/jquery.translate-1.4.7.NCT.core.NCT-adapter.ui.progress.native.parallel.min.js deleted file mode 100644 index 24870f93..00000000 --- a/helpdesk/static/helpdesk/jquery.translate-1.4.7.NCT.core.NCT-adapter.ui.progress.native.parallel.min.js +++ /dev/null @@ -1,43 +0,0 @@ -//jQuery Translate plugin and related components - - /* - * jQuery nodesContainingText plugin - * Version: 1.1.2 - * http://code.google.com/p/jquery-translate/ - * Copyright (c) 2009 Balazs Endresz (balazs.endresz@gmail.com) - * Dual licensed under the MIT and GPL licenses. - */ -(function(b){function a(){}a.prototype={init:function(e,d){this.textArray=[];this.elements=[];this.options=d;this.jquery=e;this.n=-1;if(d.async===true){d.async=2}if(d.not){e=e.not(d.not);e=e.add(e.find("*").not(d.not)).not(b(d.not).find("*"))}else{e=e.add(e.find("*"))}this.jq=e;this.jql=this.jq.length;return this.process()},process:function(){this.n++;var i=this,d=this.options,p="",h=false,g=false,f=this.jq[this.n],k,m,j;if(this.n===this.jql){j=this.jquery.pushStack(this.elements,"nodesContainingText");d.complete.call(j,j,this.textArray);if(d.returnAll===false&&d.walk===false){return this.jquery}return j}if(!f){return this.process()}k=b(f);var n=f.nodeName.toUpperCase(),l=n==="INPUT"&&b.attr(f,"type").toLowerCase();if(({SCRIPT:1,NOSCRIPT:1,STYLE:1,OBJECT:1,IFRAME:1})[n]){return this.process() -}if(typeof d.subject==="string"){p=k.attr(d.subject)}else{if(d.altAndVal&&(n==="IMG"||l==="image")){p=k.attr("alt")}else{if(d.altAndVal&&({text:1,button:1,submit:1})[l]){p=k.val()}else{if(n==="TEXTAREA"){p=k.val()}else{m=f.firstChild;if(d.walk!==true){g=true}else{while(m){if(m.nodeType==1){g=true;break}m=m.nextSibling}}if(!g){p=k.text()}else{if(d.walk!==true){h=true}m=f.firstChild;while(m){if(m.nodeType==3&&m.nodeValue.match(/\S/)!==null){if(m.nodeValue.match(//)!==null){if(m.nodeValue.match(/(\S+(?=.*<))|(>(?=.*\S+))/)!==null){h=true;break}}else{h=true;break}}m=m.nextSibling}if(h){p=k.html();p=d.stripScripts?p.replace(/]*>([\s\S]*?)<\/script>/gi,""):p;this.jq=this.jq.not(k.find("*"))}}}}}}if(!p){return this.process()}this.elements.push(f);this.textArray.push(p);d.each.call(f,this.elements.length-1,f,p);if(d.async){setTimeout(function(){i.process()},d.async);return this.jquery}else{return this.process()}}};var c={not:"",async:false,each:function(){},complete:function(){},comments:false,returnAll:true,walk:true,altAndVal:false,subject:true,stripScripts:true}; -b.fn.nodesContainingText=function(d){d=b.extend({},c,b.fn.nodesContainingText.defaults,d);return new a().init(this,d)};b.fn.nodesContainingText.defaults=c})(jQuery); -/* - * jQuery Translate plugin - * Version: 1.4.7 - * http://code.google.com/p/jquery-translate/ - * Copyright (c) 2009 Balazs Endresz (balazs.endresz@gmail.com) - * Dual licensed under the MIT and GPL licenses. - * This plugin uses the 'Google AJAX Language API' (http://code.google.com/apis/ajaxlanguage/) - * You can read the terms of use at http://code.google.com/apis/ajaxlanguage/terms.html - */ -(function(c){function p(){}var d=true,g=false,e,u="".replace,v=String,k=Function,t=Object,n,l,f,q={},b,j=[],h={from:"",to:"",start:p,error:p,each:p,complete:p,onTimeout:p,timeout:0,stripComments:d,stripWhitespace:d,stripScripts:d,separators:/\.\?\!;:/,limit:1750,walk:d,returnAll:g,replace:d,rebind:d,data:d,setLangAttr:g,subject:d,not:"",altAndVal:d,async:g,toggle:g,fromOriginal:d,parallel:false,trim:true,alwaysReplace:false};function s(){c.translate.GL=n=google.language;c.translate.GLL=l=n.Languages;f=c.translate.toLanguageCode;c.each(l,function(y,z){q[z.toUpperCase()]=y}); -c.translate.isReady=d;var x;while((x=j.shift())){x()}}function i(z,y){var x={};c.each(z,function(A,B){if(y(B,A)===d){x[A]=B}});return x}function w(y,z,x){return function(){return y.apply(z===d?arguments[0]:z,x||arguments)}}function r(x){return x!==e}function o(y,B,A){var x,C={},z=c.grep(y,r);c.each(B,function(D,E){var F=c.grep(E[0],function(H,G){return r(z[G])&&z[G].constructor===H}).length;if(F===z.length&&F===E[0].length&&(x=d)){c.each(E[1],function(G,H){C[H]=z[G]});return g}});if(!x){throw A}return C}function m(A,z){var x=o(A,c.translate.overload,"jQuery.translate: Invalid arguments"),y=x.options||{};delete x.options;y=c.extend({},h,z,c.extend(y,x));if(y.fromOriginal){y.toggle=d}if(y.toggle){y.data=d}if(y.async===d){y.async=2}return y}function a(){this.extend(c.translate);delete this.defaults;delete this.fn}a.prototype={version:"1.4.7",_init:function(z,C){var B=C.separators.source||C.separators,y=this.isString=typeof z==="string",x=0,A;c.each(["stripComments","stripScripts","stripWhitespace"],function(E,D){var F=c.translate[D]; -if(C[D]){z=y?F(z):c.map(z,F)}});this.rawSource="
"+(y?z:z.join("
"))+"
";this._m3=new RegExp("["+B+"](?![^"+B+"]*["+B+"])");this.options=C;this.from=C.from=f(C.from)||"";this.to=C.to=f(C.to)||"";this.source=z;this.rawTranslation="";this.translation=[];this.i=0;this.stopped=g;this.elements=C.nodes;this._i=-1;this.rawSources=[];while(d){A=this.truncate(this.rawSource.substr(x),C.limit);if(!A){break}this.rawSources.push(A);x+=A.length}this.queue=new Array(this.rawSources.length);this.done=0;C.start.call(this,z,C.from,C.to,C);if(C.timeout){this.timeout=setTimeout(w(C.onTimeout,this,[z,C.from,C.to,C]),C.timeout)}(C.toggle&&C.nodes)?(C.textNodes?this._toggleTextNodes():this._toggle()):this._process()},_process:function(){if(this.stopped){return}var x=this.options,E=this.rawTranslation.length,I,J,G,F;var H=this;while((I=this.rawTranslation.lastIndexOf("",E))>-1){E=I-1;J=this.rawTranslation.substr(0,E+1);G=J.match(/ ]/gi);F=J.match(/<\/div>/gi);G=G?G.length:0;F=F?F.length:0; -if(G!==F+1){continue}var A=c(this.rawTranslation.substr(0,E+7)),C=A.length,B=this.i;if(B===C){break}A.slice(B,C).each(w(function(M,P){if(this.stopped){return g}var L=c(P).html(),O=x.trim?c.trim(L):L,N=B+M,Q=this.source,R=!this.from&&this.detectedSourceLanguage||this.from;this.translation[N]=O;this.isString?this.translation=O:Q=this.source[N];x.each.call(this,N,O,Q,R,this.to,x);this.i++},this));break}if(this.rawSources.length-1==this._i){this._complete()}var z=w(this._translate,this);if(x.parallel){if(this._i<0){if(!x.parallel){c.each(this.rawSources,z)}else{var D=0,y=this.rawSources.length;function K(){z();if(Dthis.done)||(x===this.queue.length-1)){for(var y=0;y<=x;y++){this.rawTranslation+=this.queue[y]}this._process()}this.done=x},_complete:function(){clearTimeout(this.timeout);this.options.complete.call(this,this.translation,this.source,!this.from&&this.detectedSourceLanguage||this.from,this.to,this.options)},stop:function(){if(this.stopped){return this}this.stopped=d;this.options.error.call(this,{message:"stopped"});return this}};c.translate=function(z,x){if(z==e){return new a()}if(c.isFunction(z)){return c.translate.ready(z,x)}var A=new a();var y=[].slice.call(arguments,0);y.shift();return c.translate.ready(w(A._init,A,[z,m(y,c.translate.defaults)]),g,A)};c.translate.fn=c.translate.prototype=a.prototype;c.translate.fn.extend=c.translate.extend=c.extend;c.translate.extend({_bind:w,_filter:i,_validate:o,_getOpt:m,_defaults:h,defaults:c.extend({},h),capitalize:function(x){return x.charAt(0).toUpperCase()+x.substr(1).toLowerCase() -},truncate:function(D,y){var z,G,E,C,B,F,x=encodeURIComponent(D);for(z=0;z<10;z++){try{F=decodeURIComponent(x.substr(0,y-z))}catch(A){continue}if(F){break}}return(!(G=/<(?![^<]*>)/.exec(F)))?((!(E=/>\s*$/.exec(F)))?((C=this._m3.exec(F))?((B=/>(?![^>]*<)/.exec(F))?(C.index>B.index?F.substring(0,C.index+1):F.substring(0,B.index+1)):F.substring(0,C.index+1)):F):F):F.substring(0,G.index)},getLanguages:function(E,D){if(E==e||(D==e&&!E)){return l}var B={},A=typeof E,z=D?c.translate.getLanguages(E):l,F=(A==="object"||A==="function")?E:D;if(F){if(F.call){B=i(z,F)}else{for(var C=0,y=F.length,x;C]*>([\s\S]*?)<\/script>/gi,""]),stripWhitespace:w(u,d,[/\s\s+/g," "]),stripComments:w(u,d,[//g,""])}) -})(jQuery); - -(function(g){var f=true,a={text:f,button:f,submit:f},b={SCRIPT:f,NOSCRIPT:f,STYLE:f,OBJECT:f,IFRAME:f},e=g([]);e.length=1;function d(i){while(i&&i.nodeType!=9){i=i.parentNode}return i}function c(j,i){var k=j.css("text-align");j.css("direction",i);if(k==="right"){j.css("text-align","left")}if(k==="left"){j.css("text-align","right")}}function h(j,k){var l=j.nodeName.toUpperCase(),i=l==="INPUT"&&g.attr(j,"type").toLowerCase();k=k||{altAndVal:f,subject:f};return typeof k.subject==="string"?k.subject:k.altAndVal&&(l==="IMG"||i==="image")?"alt":k.altAndVal&&a[i]?"$val":l==="TEXTAREA"?"$val":"$html"}g.translate.fn._toggle=function(){var j=this.options,k=j.to,i;this.elements.each(g.translate._bind(function(l,m){this.i=l;var o=g(m),n=g.translate.getData(o,k,j);if(!n){return !(i=f)}this.translation.push(n);j.each.call(this,l,m,n,this.source[l],this.from,k,j) -},this));!i?this._complete():this._process()};g.translate.extend({_getType:h,each:function(k,m,j,l,q,p,n){e[0]=m;g.translate.setData(e,p,j,q,l,n);g.translate.replace(e,j,p,n);g.translate.setLangAttr(e,p,n)},getData:function(k,m,l){var i=k[0]||k,j=g.data(i,"translation");return j&&j[m]&&j[m][h(i,l)]},setData:function(l,n,q,p,r,i){if(i&&!i.data){return}var j=l[0]||l,m=h(j,i),k=g.data(j,"translation");k=k||g.data(j,"translation",{});(k[p]=k[p]||{})[m]=r;(k[n]=k[n]||{})[m]=q},replace:function(m,u,s,k){if(k&&!k.replace){return}if(k&&typeof k.subject==="string"){return m.attr(k.subject,u)}var l=m[0]||m,q=l.nodeName.toUpperCase(),p=q==="INPUT"&&g.attr(l,"type").toLowerCase(),n=g.translate.isRtl,j=g.data(l,"lang");if(!k.alwaysReplace){if(j===s){return}}if(n[s]!==n[j||k&&k.from]){if(n[s]){c(m,"rtl")}else{if(m.css("direction")==="rtl"){c(m,"ltr")}}}if((!k||k.altAndVal)&&(q==="IMG"||p==="image")){m.attr("alt",u)}else{if(q==="TEXTAREA"||(!k||k.altAndVal)&&a[p]){m.val(u)}else{if(!k||k.rebind){this.doc=this.doc||d(l); -var i=m.find("*").not("script"),r=g(this.doc.createElement("div")).html(u);g.translate.copyEvents(i,r.find("*"));m.html(r.contents())}else{m.html(u)}}}g.data(l,"lang",s)},setLangAttr:function(i,k,j){if(!j||j.setLangAttr){i.attr((!j||j.setLangAttr===f)?"lang":j.setLangAttr,k)}},copyEvents:function(j,i){i.each(function(l,o){var p=j[l];if(!o||!p){return false}if(b[p.nodeName.toUpperCase()]){return f}var k=g.data(p,"events");if(!k){return f}for(var n in k){for(var m in k[n]){g.event.add(o,n,k[n][m],k[n][m].data)}}})}});g.fn.translate=function(j,i,m){var k=g.translate._getOpt(arguments,g.fn.translate.defaults),l=g.extend({},g.translate._defaults,g.fn.translate.defaults,k,{complete:function(o,n){g.translate(function(){var r=g.translate.toLanguageCode(k.from);if(k.fromOriginal){o.each(function(s,t){e[0]=t;var u=g.translate.getData(e,r,k);if(!u){return true}n[s]=u})}var q=k.each;function p(s){return function(){[].unshift.call(arguments,this.elements);s.apply(this,arguments)}}k.nodes=o;k.start=p(k.start); -k.onTimeout=p(k.onTimeout);k.complete=p(k.complete);k.each=function(t){var s=arguments;if(arguments.length!==7){[].splice.call(s,1,0,this.elements[t])}this.each.apply(this,s);q.apply(this,s)};g.translate(n,k)})},each:function(){}});if(this.nodesContainingText){return this.nodesContainingText(l)}k.nodes=this;g.translate(g.map(this,function(n){return g(n).html()||g(n).val()}),k);return this};g.fn.translate.defaults=g.extend({},g.translate._defaults)})(jQuery); - -(function(a){var b={tags:["select","option"],filter:a.translate.isTranslatable,label:a.translate.toNativeLanguage||function(d,c){return a.translate.capitalize(c)},includeUnknown:false};a.translate.ui=function(){var g={},f="",d="",c="";if(typeof arguments[0]==="string"){g.tags=a.makeArray(arguments)}else{g=arguments[0]}g=a.extend({},b,a.translate.ui.defaults,g);if(g.tags[2]){d="<"+g.tags[2]+">";c=""}var e=a.translate.getLanguages(g.filter);if(!g.includeUnknown){delete e.UNKNOWN}a.each(e,function(h,i){f+=("<"+g.tags[1]+" value="+i+">"+d+g.label(i,h)+c+"")});return a("<"+g.tags[0]+' class="jq-translate-ui">'+f+"")};a.translate.ui.defaults=a.extend({},b)})(jQuery); - -jQuery.translate.fn.progress=function(a,c){if(!this.i){this._pr=0}this._pr+=this.source[this.i].length;var b=100*this._pr/(this.rawSource.length-(11*(this.i+1)));if(a){var d=jQuery(a);if(!this.i&&!d.hasClass("ui-progressbar")){d.progressbar(c)}d.progressbar("option","value",b)}return b}; - -(function(a){a.translate.extend({toNativeLanguage:function(b){return a.translate.nativeLanguages[b]||a.translate.nativeLanguages[a.translate.toLanguageCode(b)]},nativeLanguages:{af:"Afrikaans",be:"Беларуская",is:"Íslenska",ga:"Gaeilge",mk:"Македонски",ms:"Bahasa Melayu",sw:"Kiswahili",cy:"Cymraeg",yi:"ייִדיש",sq:"Shqipe",ar:"العربية",bg:"Български",ca:"Català",zh:"中文","zh-CN":"简体中文","zh-TW":"繁體中文",hr:"Hrvatski",cs:"Čeština",da:"Dansk",nl:"Nederlands",en:"English",et:"Eesti",tl:"Tagalog",fi:"Suomi",fr:"Français",gl:"Galego",de:"Deutsch",el:"Ελληνικά",iw:"עברית",hi:"हिन्दी",hu:"Magyar",id:"Bahasa Indonesia",it:"Italiano",ja:"日本語",ko:"한국어",lv:"Latviešu",lt:"Lietuvių",mt:"Malti",no:"Norsk",fa:"فارسی",pl:"Polski","pt-PT":"Português",ro:"Român",ru:"Русский",sr:"Српски",sk:"Slovenský",sl:"Slovenski",es:"Español",sv:"Svenska",th:"ไทย",tr:"Türkçe",uk:"Українська",vi:"Tiếng Việt"}}) -})(jQuery); - -(function(a){a.translate.extend({defer:function(){return a.translate._bind(a.translate,null,arguments)},run:function(d,c){var b=d.length;a.each(d,function(){var f=this(),e=f.options.complete;f.options.complete=function(){e.apply(this,arguments);if(!--b){c()}}})}})})(jQuery); \ No newline at end of file diff --git a/helpdesk/static/helpdesk/jquery.translate-debug-all.js b/helpdesk/static/helpdesk/jquery.translate-debug-all.js new file mode 100644 index 00000000..db922995 --- /dev/null +++ b/helpdesk/static/helpdesk/jquery.translate-debug-all.js @@ -0,0 +1,1546 @@ +/*! + * jQuery nodesContainingText plugin + * + * Version: 1.1.2 + * + * http://code.google.com/p/jquery-translate/ + * + * Copyright (c) 2009 Balazs Endresz (balazs.endresz@gmail.com) + * Dual licensed under the MIT and GPL licenses. + * + */ + +;(function($){ + +function Nct(){} + +Nct.prototype = { + init: function(jq, o){ + this.textArray = []; + this.elements = []; + this.options = o; + this.jquery = jq; + this.n = -1; + if(o.async === true) + o.async = 2; + + if(o.not){ + jq = jq.not(o.not); + jq = jq.add( jq.find("*").not(o.not) ).not( $(o.not).find("*") ); + }else + jq = jq.add( jq.find("*") ); + + this.jq = jq; + this.jql = this.jq.length; + return this.process(); + + }, + + process: function(){ + this.n++; + var that = this, o = this.options, text = "", hasTextNode = false, + hasChildNode = false, el = this.jq[this.n], e, c, ret; + + if(this.n === this.jql){ + ret = this.jquery.pushStack(this.elements, "nodesContainingText"); + o.complete.call(ret, ret, this.textArray); + + if(o.returnAll === false && o.walk === false) + return this.jquery; + return ret; + } + + if(!el) + return this.process(); + e=$(el); + + var nodeName = el.nodeName.toUpperCase(), + type = nodeName === "INPUT" && $.attr(el, "type").toLowerCase(); + + if( ({SCRIPT:1, NOSCRIPT:1, STYLE:1, OBJECT:1, IFRAME:1})[ nodeName ] ) + return this.process(); + + if(typeof o.subject === "string"){ + text=e.attr(o.subject); + }else{ + if(o.altAndVal && (nodeName === "IMG" || type === "image" ) ) + text = e.attr("alt"); + else if( o.altAndVal && ({text:1, button:1, submit:1})[ type ] ) + text = e.val(); + else if(nodeName === "TEXTAREA") + text = e.val(); + else{ + //check childNodes: + c = el.firstChild; + if(o.walk !== true) + hasChildNode = true; + else{ + while(c){ + if(c.nodeType == 1){ + hasChildNode = true; + break; + } + c=c.nextSibling; + } + } + + if(!hasChildNode) + text = e.text(); + else{//check textNodes: + if(o.walk !== true) + hasTextNode = true; + + c=el.firstChild; + while(c){ + if(c.nodeType == 3 && c.nodeValue.match(/\S/) !== null){//textnodes with text + /*jslint skipLines*/ + if(c.nodeValue.match(//) !== null){ + if(c.nodeValue.match(/(\S+(?=.*<))|(>(?=.*\S+))/) !== null){ + hasTextNode = true; + break; + } + }else{ + hasTextNode = true; + break; + } + /*jslint skipLinesEnd*/ + } + c = c.nextSibling; + } + + if(hasTextNode){//remove child nodes from jq + //remove scripts: + text = e.html(); + /*jslint skipLines*/ + text = o.stripScripts ? text.replace(/]*>([\s\S]*?)<\/script>/gi, "") : text; + /*jslint skipLinesEnd*/ + this.jq = this.jq.not( e.find("*") ); + } + } + } + } + + if(!text) + return this.process(); + this.elements.push(el); + this.textArray.push(text); + + o.each.call(el, this.elements.length - 1, el, text); + + if(o.async){ + setTimeout(function(){that.process();}, o.async); + return this.jquery; + }else + return this.process(); + + } +}; + +var defaults = { + not: "", + async: false, + each: function(){}, + complete: function(){}, + comments: false, + returnAll: true, + walk: true, + altAndVal: false, + subject: true, + stripScripts: true +}; + +$.fn.nodesContainingText = function(o){ + o = $.extend({}, defaults, $.fn.nodesContainingText.defaults, o); + return new Nct().init(this, o); +}; + +$.fn.nodesContainingText.defaults = defaults; + +})(jQuery);/*! + * Textnode Translator + * Ariel Flesler - http://flesler.blogspot.com/2008/05/textnode-translator-for-javascript.html + */ +//This is now only a placeholder, the original script has been modified +//and the Translator class is no longer exposed +/*! + * jQuery Translate plugin + * + * Version: ${version} + * + * http://code.google.com/p/jquery-translate/ + * + * Copyright (c) 2009 Balazs Endresz (balazs.endresz@gmail.com) + * Dual licensed under the MIT and GPL licenses. + * + * This plugin uses the 'Google AJAX Language API' (http://code.google.com/apis/ajaxlanguage/) + * You can read the terms of use at http://code.google.com/apis/ajaxlanguage/terms.html + * + */ +;(function($){ + +function $function(){} + +var True = true, False = false, undefined, replace = "".replace, + Str = String, Fn = Function, Obj = Object, + GL, GLL, toLangCode, inverseLanguages = {}, + loading, readyList = [], key, + defaults = { + from: "", + to: "", + start: $function, + error: $function, + each: $function, + complete: $function, + onTimeout: $function, + timeout: 0, + + stripComments: True, + stripWhitespace: True, + stripScripts: True, + separators: /\.\?\!;:/, + limit: 1750, + + + walk: True, + returnAll: False, + replace: True, + rebind: True, + data: True, + setLangAttr: False, + subject: True, + not: "", + altAndVal:True, + async: False, + toggle: False, + fromOriginal: True, + + parallel: false, + trim: true, + alwaysReplace: false + //,response: $function + + }; + + + +function ms_loaded(languageCodes, languageNames){ + GLL = {}; + for(var i = 0; i < languageCodes.length; i++){ + GLL[languageNames[i].toUpperCase()] = languageCodes[i]; + } + + //$.translate.GL = GL = google.language; + $.translate.GLL = GLL; // = GL.Languages; + + loaded(); +} + +function loaded(){ + toLangCode = $.translate.toLanguageCode; + + $.each(GLL, function(l, lc){ + inverseLanguages[ lc.toUpperCase() ] = l; + }); + + $.translate.isReady = True; + var fn; + while((fn = readyList.shift())) fn(); +} + +function filter(obj, fn){ + var newObj = {}; + $.each(obj, function(lang, langCode){ + if( fn(langCode, lang) === True) newObj[ lang ] = langCode; + }); + return newObj; +} + +function bind(fn, thisObj, args){ + return function(){ + return fn.apply(thisObj === True ? arguments[0] : thisObj, args || arguments); + }; +} + +function isSet(e){ + return e !== undefined; +} + +function validate(_args, overload, error){ + var matched, obj = {}, args = $.grep(_args, isSet); + + $.each(overload, function(_, el){ + var matches = $.grep(el[0], function(e, i){ + return isSet(args[i]) && args[i].constructor === e; + }).length; + if(matches === args.length && matches === el[0].length && (matched = True)){ + $.each(el[1], function(i, prop){ + obj[prop] = args[i]; + }); + return False; + } + }); + //TODO + if(!matched) throw error; + return obj; +} + + +function getOpt(args0, _defaults){ + //args0=[].slice.call(args0, 0) + var args = validate(args0 , $.translate.overload, "jQuery.translate: Invalid arguments" ), + o = args.options || {}; + delete args.options; + o = $.extend({}, defaults, _defaults, $.extend(o, args)); + + if(o.fromOriginal) o.toggle = True; + if(o.toggle) o.data = True; + if(o.async === True) o.async = 2; + if(o.alwaysReplace === true){ //see issue #58 + o.toggle = false; + o.fromOriginal = false; + } + + return o; +} + + +function T(){ + //copy over static methods during each instantiation + //for backward compatibility and access inside callback functions + this.extend($.translate); + delete this.defaults; + delete this.fn; +} + +T.prototype = { + version: "${version}", + + _init: function(t, o){ + var separator = o.separators.source || o.separators, + isString = this.isString = typeof t === "string", + lastpos = 0, substr; + + $.each(["stripComments", "stripScripts", "stripWhitespace"], function(i, name){ + var fn = $.translate[name]; + if( o[name] ) + t = isString ? fn(t) : $.map(t, fn); + }); + + this.rawSource = "
" + (isString ? t : t.join("
")) + "
"; + this._m3 = new RegExp("[" + separator + "](?![^" + separator + "]*[" + separator + "])"); + this.options = o; + this.from = o.from = toLangCode(o.from) || ""; + this.to = o.to = toLangCode(o.to) || ""; + this.source = t; + this.rawTranslation = ""; + this.translation = []; + this.i = 0; + this.stopped = False; + this.elements = o.nodes; + + //this._nres = 0; + //this._progress = 0; + this._i = -1; //TODO: rename + this.rawSources = []; + + while(True){ + substr = this.truncate( this.rawSource.substr(lastpos), o.limit); + if(!substr) break; + this.rawSources.push(substr); + lastpos += substr.length; + } + this.queue = new Array(this.rawSources.length); + this.done = 0; + + o.start.call(this, t , o.from, o.to, o); + + if(o.timeout) + this.timeout = setTimeout(bind(o.onTimeout, this, [t, o.from, o.to, o]), o.timeout); + + (o.toggle && o.nodes) ? + (o.textNodes ? this._toggleTextNodes() : this._toggle()) : + this._process(); + }, + + _process: function(){ + if(this.stopped) + return; + var o = this.options, + i = this.rawTranslation.length, + lastpos, subst, divst, divcl; + var that = this; + + while( (lastpos = this.rawTranslation.lastIndexOf("", i)) > -1){ + + i = lastpos - 1; + subst = this.rawTranslation.substr(0, i + 1); + /*jslint skipLines*/ + divst = subst.match(/ ]/gi); + divcl = subst.match(/<\/div>/gi); + /*jslint skipLinesEnd*/ + + divst = divst ? divst.length : 0; + divcl = divcl ? divcl.length : 0; + + if(divst !== divcl + 1) continue; //if there are some unclosed divs + + var divscompl = $( this.rawTranslation.substr(0, i + 7) ), + divlen = divscompl.length, + l = this.i; + + if(l === divlen) break; //if no new elements have been completely translated + + divscompl.slice(l, divlen).each( bind(function(j, e){ + if(this.stopped) + return False; + var e_html = $(e).html(), tr = o.trim ? $.trim(e_html) : e_html, + i = l + j, src = this.source, + from = !this.from && this.detectedSourceLanguage || this.from; + this.translation[i] = tr;//create an array for complete callback + this.isString ? this.translation = tr : src = this.source[i]; + + o.each.call(this, i, tr, src, from, this.to, o); + + this.i++; + }, this)); + + break; + } + + if(this.rawSources.length - 1 == this._i) + this._complete(); + + var _translate = bind(this._translate, this); + + if(o.parallel){ + if(this._i < 0){ + if(!o.parallel){ + $.each(this.rawSources, _translate); + }else{ + var j = 0, n = this.rawSources.length; + function seq(){ + _translate(); + if(j++ < n) + setTimeout( seq, o.parallel ); + } + seq(); + } + } + }else + _translate(); + + }, + + _translate: function(){ + this._i++; + var _this = this, i = this._i, src = this.rawSourceSub = this.rawSources[i]; + if(!src) return; + + if(key.length < 40){ + $.ajax({ + url: "https://www.googleapis.com/language/translate/v2", + dataType: "jsonp", + jsonp: "callback", + crossDomain: true, + //context: this, //doesn't work with older versions of jQuery + data: $.extend({"key": key, target: this.to, q: src}, this.from ? {source: this.from} : {}), + success: function(response){ + if(response.error){ + return _this.options.error.call(_this, response.error, _this.rawSourceSub, _this.from, _this.to, _this.options); + } + var tr = response.data.translations[0].translatedText; + _this.queue[i] = tr || _this.rawSourceSub; + _this.detectedSourceLanguage = response.data.translations[0].detectedSourceLanguage; + _this._check(); + } + }); + + /* + GL.translate(src, this.from, this.to, bind(function(result){ + //this._progress = 100 * (++this._nres) / this.rawSources.length; + //this.options.response.call(this, this._progress, result); + if(result.error) + return this.options.error.call(this, result.error, this.rawSourceSub, this.from, this.to, this.options); + + this.queue[i] = result.translation || this.rawSourceSub; + this.detectedSourceLanguage = result.detectedSourceLanguage; + this._check(); + }, this)); + */ + }else{ + $.ajax({ + url: "http://api.microsofttranslator.com/V2/Ajax.svc/Translate", + dataType: "jsonp", + jsonp: "oncomplete", + crossDomain: true, + //context: this, + data: {appId: key, from: _this.from, to: _this.to, contentType: "text/plain", text: src}, + success: function(data, status){ + //console.log(data); + _this.queue[i] = data || _this.rawSourceSub; + //this.detectedSourceLanguage = result.detectedSourceLanguage; + _this._check(); + } + }); + } + }, + + _check: function(){ + if(!this.options.parallel){ + this.rawTranslation += this.queue[this._i]; + this._process(); + return; + } + + var done = 0; + jQuery.each(this.queue, function(i, n) { + if (n != undefined) done = i; + else return false; + }); + + if ((done > this.done) || (done === this.queue.length - 1)) { + for(var i = 0; i <= done; i++) + this.rawTranslation += this.queue[i]; + this._process(); + } + this.done = done; + + }, + + _complete: function(){ + clearTimeout(this.timeout); + + this.options.complete.call(this, this.translation, this.source, + !this.from && this.detectedSourceLanguage || this.from, this.to, this.options); + }, + + stop: function(){ + if(this.stopped) + return this; + this.stopped = True; + this.options.error.call(this, {message:"stopped"}); + return this; + } +}; + + + +$.translate = function(t, a){ + if(t == undefined) + return new T(); + if( $.isFunction(t) ) + return $.translate.ready(t, a); + var that = new T(); + + var args = [].slice.call(arguments, 0); + args.shift(); + return $.translate.ready( bind(that._init, that, [t, getOpt(args, $.translate.defaults)] ), False, that ); +}; + + +$.translate.fn = $.translate.prototype = T.prototype; + +$.translate.fn.extend = $.translate.extend = $.extend; + + +$.translate.extend({ + + _bind: bind, + + _filter: filter, + + _validate: validate, + + _getOpt: getOpt, + + _defaults: defaults, //base defaults used by other components as well //TODO + + defaults: $.extend({}, defaults), + + capitalize: function(t){ return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase(); }, + + truncate: function(text, limit){ + var i, m1, m2, m3, m4, t, encoded = encodeURIComponent( text ); + + for(i = 0; i < 10; i++){ + try { + t = decodeURIComponent( encoded.substr(0, limit - i) ); + } catch(e){ continue; } + if(t) break; + } + + return ( !( m1 = /<(?![^<]*>)/.exec(t) ) ) ? ( //if no broken tag present + ( !( m2 = />\s*$/.exec(t) ) ) ? ( //if doesn't end with '>' + ( m3 = this._m3.exec(t) ) ? ( //if broken sentence present + ( m4 = />(?![^>]*<)/.exec(t) ) ? ( + m3.index > m4.index ? t.substring(0, m3.index+1) : t.substring(0, m4.index+1) + ) : t.substring(0, m3.index+1) ) : t ) : t ) : t.substring(0, m1.index); + }, + + getLanguages: function(a, b){ + if(a == undefined || (b == undefined && !a)) + return GLL; + + var newObj = {}, typeof_a = typeof a, + languages = b ? $.translate.getLanguages(a) : GLL, + filterArg = ( typeof_a === "object" || typeof_a === "function" ) ? a : b; + + if(filterArg) + if(filterArg.call) //if it's a filter function + newObj = filter(languages, filterArg); + else //if it's an array of languages + for(var i = 0, length = filterArg.length, lang; i < length; i++){ + lang = $.translate.toLanguage(filterArg[i]); + if(languages[lang] != undefined) + newObj[lang] = languages[lang]; + } + else //if the first argument is true -> only translatable languages + newObj = filter(GLL, $.translate.isTranslatable); + + return newObj; + }, + + + toLanguage: function(a, format){ + var u = a.toUpperCase(); + var l = inverseLanguages[u] || + (GLL[u] ? u : undefined) || + inverseLanguages[($.translate.languageCodeMap[a.toLowerCase()]||"").toUpperCase()]; + return l == undefined ? undefined : + format === "lowercase" ? l.toLowerCase() : format === "capitalize" ? $.translate.capitalize(l) : l; + }, + + toLanguageCode: function(a){ + return GLL[a] || + GLL[ $.translate.toLanguage(a) ] || + $.translate.languageCodeMap[a.toLowerCase()]; + }, + + same: function(a, b){ + return a === b || toLangCode(a) === toLangCode(b); + }, + + isTranslatable: function(l){ + return !!toLangCode(l); + }, + + //keys must be lower case, and values must equal to a + //language code specified in the Language API + languageCodeMap: { + "pt": "pt-PT", + "pt-br": "pt-PT", + "he": "iw", + "zlm": "ms", + "zh-hans": "zh-CN", + "zh-hant": "zh-TW" + //,"zh-sg":"zh-CN" + //,"zh-hk":"zh-TW" + //,"zh-mo":"zh-TW" + }, + + //use only language codes specified in the Language API + isRtl: { + "ar": True, + "iw": True, + "fa": True, + "ur": True, + "yi": True + }, + + getBranding: function(){ + if(typeof console != "undefined") + console.log("$.translate.getBranding() IS DEPRECATED! PLEASE REMOVE IT FROM YOUR CODE!"); + return $(); + }, + + load: function(_key, version){ + loading = True; + key = _key; + + if(key.length < 40){ //Google API + /* + function _load(){ + google.load("language", version || "1", {"callback" : google_loaded}); + } + + if(typeof google !== "undefined" && google.load) + _load(); + else + $.getScript(((document.location.protocol == "https:") ? "https://" : "http://") + + "www.google.com/jsapi" + (key ? "?key=" + key : ""), _load); + */ + + /* + $.ajax({ + url: "https://www.googleapis.com/language/translate/v2/languages", + dataType: "jsonp", + jsonp: "oncomplete", + crossDomain: true, + context: this, + data: {key: key, target: "en"}, + success: function(response, status){ + var languageCodes = [], languageNames = []; + $.each(response.data.languages, function(i, e){ + languageCodes.push(e.language); + languageNames.push(e.name); + }); + ms_loaded(languageCodes, languageNames); + } + }); + */ + + var response = {"data": { + "languages": [ + { + "language": "af", + "name": "Afrikaans" + }, + { + "language": "sq", + "name": "Albanian" + }, + { + "language": "ar", + "name": "Arabic" + }, + { + "language": "be", + "name": "Belarusian" + }, + { + "language": "bg", + "name": "Bulgarian" + }, + { + "language": "ca", + "name": "Catalan" + }, + { + "language": "zh", + "name": "Chinese (Simplified)" + }, + { + "language": "zh-TW", + "name": "Chinese (Traditional)" + }, + { + "language": "hr", + "name": "Croatian" + }, + { + "language": "cs", + "name": "Czech" + }, + { + "language": "da", + "name": "Danish" + }, + { + "language": "nl", + "name": "Dutch" + }, + { + "language": "en", + "name": "English" + }, + { + "language": "et", + "name": "Estonian" + }, + { + "language": "tl", + "name": "Filipino" + }, + { + "language": "fi", + "name": "Finnish" + }, + { + "language": "fr", + "name": "French" + }, + { + "language": "gl", + "name": "Galician" + }, + { + "language": "de", + "name": "German" + }, + { + "language": "el", + "name": "Greek" + }, + { + "language": "ht", + "name": "Haitian Creole" + }, + { + "language": "iw", + "name": "Hebrew" + }, + { + "language": "hi", + "name": "Hindi" + }, + { + "language": "hu", + "name": "Hungarian" + }, + { + "language": "is", + "name": "Icelandic" + }, + { + "language": "id", + "name": "Indonesian" + }, + { + "language": "ga", + "name": "Irish" + }, + { + "language": "it", + "name": "Italian" + }, + { + "language": "ja", + "name": "Japanese" + }, + { + "language": "ko", + "name": "Korean" + }, + { + "language": "lv", + "name": "Latvian" + }, + { + "language": "lt", + "name": "Lithuanian" + }, + { + "language": "mk", + "name": "Macedonian" + }, + { + "language": "ms", + "name": "Malay" + }, + { + "language": "mt", + "name": "Maltese" + }, + { + "language": "no", + "name": "Norwegian" + }, + { + "language": "fa", + "name": "Persian" + }, + { + "language": "pl", + "name": "Polish" + }, + { + "language": "pt", + "name": "Portuguese" + }, + { + "language": "ro", + "name": "Romanian" + }, + { + "language": "ru", + "name": "Russian" + }, + { + "language": "sr", + "name": "Serbian" + }, + { + "language": "sk", + "name": "Slovak" + }, + { + "language": "sl", + "name": "Slovenian" + }, + { + "language": "es", + "name": "Spanish" + }, + { + "language": "sw", + "name": "Swahili" + }, + { + "language": "sv", + "name": "Swedish" + }, + { + "language": "th", + "name": "Thai" + }, + { + "language": "tr", + "name": "Turkish" + }, + { + "language": "uk", + "name": "Ukrainian" + }, + { + "language": "vi", + "name": "Vietnamese" + }, + { + "language": "cy", + "name": "Welsh" + }, + { + "language": "yi", + "name": "Yiddish" + } + ] + } +}; + + var languageCodes = [], languageNames = []; + $.each(response.data.languages, function(i, e){ + languageCodes.push(e.language); + languageNames.push(e.name); + }); + ms_loaded(languageCodes, languageNames); + + }else{ //Microsoft API + $.ajax({ + url: "http://api.microsofttranslator.com/V2/Ajax.svc/GetLanguagesForTranslate", + dataType: "jsonp", + jsonp: "oncomplete", + crossDomain: true, + context: this, + data: {appId: key}, + success: function(languageCodes, status){ + $.ajax({ + url: "http://api.microsofttranslator.com/V2/Ajax.svc/GetLanguageNames", + dataType: "jsonp", + jsonp: "oncomplete", + crossDomain: true, + context: this, + data: {appId: key, locale: "en", languageCodes: '["'+languageCodes.join('", "')+'"]'}, + success: function(languageNames, status){ + ms_loaded(languageCodes, languageNames); + } + }); + } + }); + + } + + return $.translate; + }, + + ready: function(fn, preventAutoload, that){ + $.translate.isReady ? fn() : readyList.push(fn); + if(!loading && !preventAutoload) + $.translate.load(); + return that || $.translate; + }, + + isReady: False, + + overload: [ + [[],[]], + [[Str, Str, Obj], ["from", "to", "options"] ], + [[Str, Obj], ["to", "options"] ], + [[Obj], ["options"] ], + [[Str, Str], ["from", "to"] ], + [[Str], ["to"] ], + [[Str, Str, Fn], ["from", "to", "complete"] ], + [[Str, Fn], ["to", "complete"] ] + //TODO + //,[[Str, Str, Fn, Fn], ["from", "to", "each", "complete"]] + ] + /*jslint skipLines*/ + , + //jslint doesn't seem to be able to parse some regexes correctly if used on the server, + //however it works fine if it's run on the command line: java -jar rhino.jar jslint.js file.js + stripScripts: bind(replace, True, [/]*>([\s\S]*?)<\/script>/gi, ""]), + + stripWhitespace: bind(replace, True, [/\s\s+/g, " "]), + + stripComments: bind(replace, True, [//g, ""]) + /*jslint skipLinesEnd*/ +}); + + +})(jQuery);/*!- + * jQuery.fn.nodesContainingText adapter for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ +;(function($){ + +var True = true, + isInput = {text:True, button:True, submit:True}, + dontCopyEvents = {SCRIPT:True, NOSCRIPT:True, STYLE:True, OBJECT:True, IFRAME:True}, + $fly = $([]); + +$fly.length = 1; + +function getDoc(node){ + while (node && node.nodeType != 9) + node = node.parentNode; + return node; +} + +function toggleDir(e, dir){ + var align = e.css("text-align"); + e.css("direction", dir); + if(align === "right") e.css("text-align", "left"); + if(align === "left") e.css("text-align", "right"); +} + +function getType(el, o){ + var nodeName = el.nodeName.toUpperCase(), + type = nodeName === 'INPUT' && $.attr(el, 'type').toLowerCase(); + o = o || {altAndVal:True, subject:True}; + return typeof o.subject === "string" ? o.subject : + o.altAndVal && (nodeName === 'IMG' || type === "image" ) ? "alt" : + o.altAndVal && isInput[ type ] ? "$val" : + nodeName === "TEXTAREA" ? "$val" : "$html"; +} + +$.translate.fn._toggle = function(){ + var o = this.options, to = o.to, stop; + + this.elements.each($.translate._bind(function(i, el){ + this.i = i; + var e = $(el), tr = $.translate.getData(e, to, o); + + if(!tr) return !(stop = True); + + this.translation.push(tr); + + o.each.call(this, i, el, tr, this.source[i], this.from, to, o); + //'from' will be undefined if it wasn't set + }, this)); + + !stop ? this._complete() : this._process(); + //o.complete.call(this, o.nodes, this.translation, this.source, this.from, this.to, o) +}; + + + +$.translate.extend({ + _getType: getType, + + each: function(i, el, t, s, from, to, o){ + $fly[0] = el; + $.translate.setData($fly, to, t, from, s, o); + $.translate.replace($fly, t, to, o); + $.translate.setLangAttr($fly, to, o); + }, + + getData: function(e, lang, o){ + var el = e[0] || e, data = $.data(el, "translation"); + return data && data[lang] && data[lang][ getType(el, o) ]; + }, + + setData: function(e, to, t, from, s, o){ + if(o && !o.data) return; + + var el = e[0] || e, + type = getType(el, o), + data = $.data(el, "translation"); + + data = data || $.data(el, "translation", {}); + (data[from] = data[from] || {})[type] = s; + (data[to] = data[to] || {})[type] = t; + }, + + + replace: function(e, t, to, o){ + + if(o && !o.replace) return; + + if(o && typeof o.subject === "string") + return e.attr(o.subject, t); + + var el = e[0] || e, + nodeName = el.nodeName.toUpperCase(), + type = nodeName === 'INPUT' && $.attr(el, 'type').toLowerCase(), + isRtl = $.translate.isRtl, + lang = $.data(el, "lang"); + + //http://code.google.com/p/jquery-translate/issues/detail?id=38 + if(!o.alwaysReplace) + if( lang === to ) + return; + + if( isRtl[ to ] !== isRtl[ lang || o && o.from ] ){ + if( isRtl[ to ] ) + toggleDir(e, "rtl"); + else if( e.css("direction") === "rtl" ) + toggleDir(e, "ltr"); + } + + if( (!o || o.altAndVal) && (nodeName === 'IMG' || type === "image" ) ) + e.attr("alt", t); + else if( nodeName === "TEXTAREA" || (!o || o.altAndVal) && isInput[ type ] ) + e.val(t); + else{ + if(!o || o.rebind){ + this.doc = this.doc || getDoc(el); + var origContents = e.find("*").not("script"), + newElem = $(this.doc.createElement("div")).html(t); + $.translate.copyEvents( origContents, newElem.find("*") ); + e.html( newElem.contents() ); + }else + e.html(t); + } + + //used for determining if the text-align property should be changed, + //it's much faster than setting the "lang" attribute, see bug #13 + $.data(el, "lang", to); + }, + + setLangAttr: function(e, to, o){ + if(!o || o.setLangAttr) + e.attr((!o || o.setLangAttr === True) ? "lang" : o.setLangAttr, to); + }, + + copyEvents: function(from, to){ + to.each(function(i, to_i){ + var from_i = from[i]; + if( !to_i || !from_i ) //in some rare cases the translated html structure can be slightly different + return false; + if( dontCopyEvents[ from_i.nodeName.toUpperCase() ]) + return True; + var events = $.data(from_i, "events"); + if(!events) + return True; + for(var type in events) + for(var handler in events[type]) + $.event.add(to_i, type, events[type][handler], events[type][handler].data); + }); + } + +}); + + +$.fn.translate = function(a, b, c){ + var o = $.translate._getOpt(arguments, $.fn.translate.defaults), + ncto = $.extend( {}, $.translate._defaults, $.fn.translate.defaults, o, + { complete:function(e,t){$.translate(function(){ + + var from = $.translate.toLanguageCode(o.from); + + if(o.fromOriginal) + e.each(function(i, el){ + $fly[0] = el; + var data = $.translate.getData($fly, from, o); + if( !data ) return true; + t[i] = data; + }); + + + var each = o.each; + + function unshiftArgs(method){ + return function(){ + [].unshift.call(arguments, this.elements); + method.apply(this, arguments); + }; + } + + //TODO: set as instance property + o.nodes = e; + o.start = unshiftArgs(o.start); + o.onTimeout = unshiftArgs(o.onTimeout); + o.complete = unshiftArgs(o.complete); + + o.each = function(i){ + var args = arguments; + if(arguments.length !== 7) //if isn't called from _toggle + [].splice.call(args, 1, 0, this.elements[i]); + this.each.apply(this, args); + each.apply(this, args); + }; + + $.translate(t, o); + + });}, + + each: function(){} + }); + + if(this.nodesContainingText) + return this.nodesContainingText(ncto); + + //fallback if nodesContainingText method is not present: + o.nodes = this; + $.translate($.map(this, function(e){ return $(e).html() || $(e).val(); }), o); + return this; +}; + +$.fn.translate.defaults = $.extend({}, $.translate._defaults); + +})(jQuery);/*!- + * TextNode Translator for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ + +;(function($){ + + +function getTextNodes( root, _filter ){ + + var nodes = [], + skip = {SCRIPT:1, NOSCRIPT:1, STYLE:1, IFRAME:1}, + notType = typeof _filter, + filter = notType === "string" ? function(node){ return !$(node).is(_filter); } : + notType === "function" ? _filter : //e.g. function(node){ return node.nodeName != 'A'; } + null; + + function recurse(_, root){ + var i = 0, children = root.childNodes, l = children.length, node; + for(; i < l; i++){ + node = children[i]; + + if(node.nodeType == 3 && /\S/.test(node.nodeValue)) + nodes.push(node); + else if( node.nodeType == 1 && + !skip[ node.nodeName.toUpperCase() ] && + (!filter || filter(node))) + recurse(null, node); + } + } + + $.each((root.length && !root.nodeName) ? root : [root], recurse); + + return nodes; +} + +function toggleDir(e, dir){ + var align = e.css("text-align"); + e.css("direction", dir); + if(align === "right") e.css("text-align", "left"); + if(align === "left") e.css("text-align", "right"); +} + +function setLangAttr(e, to, o){ + if(!o || o.setLangAttr) + $(e).attr((!o || o.setLangAttr === true) ? "lang" : o.setLangAttr, to); +} + +function replace(parent, node, text, to, o){ + if(!o.replace) return; + var isRtl = $.translate.isRtl, + lang = $.data(parent, "lang"); + + if( isRtl[ to ] !== isRtl[ lang || o && o.from ] ){ + var $parent = $(parent); + if( isRtl[ to ] ) + toggleDir($parent, "rtl"); + else if( $parent.css("direction") === "rtl" ) + toggleDir($parent, "ltr"); + } + + $.data(parent, "lang", to); + + if(text != node.nodeValue){ + var newTextNode = document.createTextNode(text); + parent.replaceChild(newTextNode, node); + return newTextNode; + } + + return node; +} + +function setData(parent, o, src, trnsl){ + if(o.data){ + var TR = "translation"; + if(!$.data(parent, TR)) + $.data(parent, TR, {}); + + if(!$.data(parent, TR)[o.from]) + $.data(parent, TR)[o.from] = []; + [].push.call($.data(parent, TR)[o.from], src); + + if(!$.data(parent, TR)[o.to]) + $.data(parent, TR)[o.to] = []; + [].push.call($.data(parent, TR)[o.to], trnsl); + } +} + +function getData(parent, lang, that){ + that._childIndex = that._prevParent === parent ? that._childIndex + 1 : 0; + var tr = $.data(parent, "translation"); + that._prevParent = parent; + return tr && tr[lang] && tr[lang][that._childIndex]; + +} + +function _each(i, textNode, t, s, from, to, o){ + t = t.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'|'/g, "'"); + + var parent = textNode.parentNode; + setData(parent, o, s, t); + var newTextNode = replace(parent, textNode, t, to, o); + setLangAttr(parent, o.to, o); + + return newTextNode; +} + +$.translateTextNodes = function(root){ + var args = [].slice.call(arguments,0); + args.shift(); + +$.translate(function(){ + var o = $.translate._getOpt(args, $.translateTextNodes.defaults), + each = o.each, + nodes = getTextNodes(root, o.not), + contents = $.map(nodes, function(n){ return n.nodeValue; }), + from = $.translate.toLanguageCode(o.from), + obj = {}; + + o.nodes = nodes; + o.textNodes = true; + o.trim = false; + + if(o.fromOriginal) + $.each(nodes, function(i, textNode){ + var data = getData(textNode.parentNode, from, obj); + if( !data ) return true; + contents[i] = data; + }); + + function unshiftArgs(method){ + return function(){ + [].unshift.call(arguments, this.elements); + method.apply(this, arguments); + }; + } + + o.start = unshiftArgs(o.start); + o.onTimeout = unshiftArgs(o.onTimeout); + o.complete = unshiftArgs(o.complete); + + o.each = function(i){ + var args = arguments; + if(arguments.length !== 7) //if isn't called from _toggle + [].splice.call(args, 1, 0, this.elements[i]); + this.elements[i] = args[1] = _each.apply(this, args); + + each.apply(this, args); + }; + + $.translate(contents, o); + +}); +}; + +$.translate.fn._toggleTextNodes = function(){ + var o = this.options, to = o.to, stop; + + $.each(this.elements, $.translate._bind(function(i, textNode){ + this.i = i; + var parent = textNode.parentNode, + tr = getData(parent, to, this); + + if(!tr) return !(stop = true); + + this.translation.push(tr); + + o.each.call(this, i, textNode, tr, this.source[i], this.from, to, o); + //'from' will be undefined if it wasn't set + }, this)); + + !stop ? this._complete() : this._process(); + //o.complete.call(this, this.elements, this.translation, this.source, this.from, this.to, o); +}; + +$.fn.translateTextNodes = function(a, b, c){ + [].unshift.call(arguments, this); + $.translateTextNodes.apply(null, arguments); + return this; +}; + +$.translateTextNodes.defaults = $.fn.translateTextNodes.defaults = $.extend({}, $.translate._defaults); + + +})(jQuery); +/*!- + * Simple user interface extension for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ +;(function($){ + +var defaults = { + tags: ["select", "option"], + filter: $.translate.isTranslatable, + label: $.translate.toNativeLanguage || + function(langCode, lang){ + return $.translate.capitalize(lang); + }, + includeUnknown: false +}; + +$.translate.ui = function(){ + var o = {}, str='', cs='', cl=''; + + if(typeof arguments[0] === "string") + o.tags = $.makeArray(arguments); + else o = arguments[0]; + + o = $.extend({}, defaults, $.translate.ui.defaults, o); + + if(o.tags[2]){ + cs = '<' + o.tags[2] + '>'; + cl = ''; + } + + var languages = $.translate.getLanguages(o.filter); + if(!o.includeUnknown) delete languages.UNKNOWN; + + $.each( languages, function(l, lc){ + str += ('<' + o.tags[1] + " value=" + lc + '>' + cs + + //$.translate.capitalize(l) + " - " + + o.label(lc, l) + + cl + ''); + }); + + return $('<' + o.tags[0] + ' class="jq-translate-ui">' + str + ''); + +}; + +$.translate.ui.defaults = $.extend({}, defaults); + + +})(jQuery); +/*!- + * Progress indicator extension for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ + +;jQuery.translate.fn.progress = function(selector, options){ + if(!this.i) this._pr = 0; + this._pr += this.source[this.i].length; + var progress = 100 * this._pr / ( this.rawSource.length - ( 11 * (this.i + 1) ) ); + + if(selector){ + var e = jQuery(selector); + if( !this.i && !e.hasClass("ui-progressbar") ) + e.progressbar(options); + e.progressbar( "option", "value", progress ); + } + + return progress; +};/*!- + * Native language names extension for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ +;(function($){ +$.translate.extend({ + + toNativeLanguage: function(lang){ + return $.translate.nativeLanguages[ lang ] || + $.translate.nativeLanguages[ $.translate.toLanguageCode(lang) ]; + }, + + nativeLanguages: { + "af":"Afrikaans", + "be":"Беларуская", + "is":"Íslenska", + "ga":"Gaeilge", + "mk":"Македонски", + "ms":"Bahasa Melayu", + "sw":"Kiswahili", + "cy":"Cymraeg", + "yi":"ייִדיש", + + "sq":"Shqipe", + "ar":"العربية", + "bg":"Български", + "ca":"Català", + "zh":"中文", + "zh-CN":"简体中文", + "zh-TW":"繁體中文", + "hr":"Hrvatski", + "cs":"Čeština", + "da":"Dansk", + "nl":"Nederlands", + "en":"English", + "et":"Eesti", + "tl":"Tagalog", + "fi":"Suomi", + "fr":"Français", + "gl":"Galego", + "de":"Deutsch", + "el":"Ελληνικά", + "iw":"עברית", + "hi":"हिन्दी", + "hu":"Magyar", + "id":"Bahasa Indonesia", + "it":"Italiano", + "ja":"日本語", + "ko":"한국어", + "lv":"Latviešu", + "lt":"Lietuvių", + "mt":"Malti", + "no":"Norsk", + "fa":"فارسی", + "pl":"Polski", + "pt-PT":"Português", + "ro":"Român", + "ru":"Русский", + "sr":"Српски", + "sk":"Slovenský", + "sl":"Slovenski", + "es":"Español", + "sv":"Svenska", + "th":"ไทย", + "tr":"Türkçe", + "uk":"Українська", + "vi":"Tiếng Việt" + } + +}); + +})(jQuery);/*!- + * Paralell extension for the jQuery Translate plugin + * Version: ${version} + * http://code.google.com/p/jquery-translate/ + */ + +;(function($){ +$.translate.extend({ + defer: function(){ + return $.translate._bind($.translate, null, arguments); + }, + + run: function(array, finished){ + var count = array.length; + $.each(array, function(){ + var inst = this(), + complete = inst.options.complete; + inst.options.complete = function(){ + complete.apply(this, arguments); + if(!--count) finished(); + }; + }); + } +}); + +})(jQuery); diff --git a/helpdesk/templates/helpdesk/base.html b/helpdesk/templates/helpdesk/base.html index e437de2d..34815234 100644 --- a/helpdesk/templates/helpdesk/base.html +++ b/helpdesk/templates/helpdesk/base.html @@ -9,13 +9,16 @@ {% block helpdesk_title %}Helpdesk{% endblock %} :: {% trans "Powered by django-helpdesk" %} - + {% comment %} + + {% endcomment %} + {% comment %} + {% endcomment %} + +
{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] EditDelete{% if ticket.on_hold %}{% trans "Unhold" %}{% else %}{% trans "Hold" %}{% endif %}
{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}] EditDelete{% if ticket.on_hold %}{% trans "Unhold" %}{% else %}{% trans "Hold" %}{% endif %}
{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}