mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2024-12-12 18:00:45 +01:00
commit
3975046590
@ -155,7 +155,7 @@ collaborative translation. If you want to help translate django-helpdesk into
|
||||
languages other than English, we encourage you to make use of our Transifex
|
||||
project:
|
||||
|
||||
http://www.transifex.net/projects/p/django-helpdesk/resource/core/
|
||||
http://www.transifex.com/projects/p/django-helpdesk/resource/core/
|
||||
|
||||
Once you have translated content via Transifex, please raise an issue on the
|
||||
project Github page and tag it as "translations" to let us know it's ready to
|
||||
|
@ -80,6 +80,11 @@ Django project.
|
||||
For further installation information see `docs/install.html`
|
||||
and `docs/configuration.html`
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
See quicktest.py for usage details
|
||||
|
||||
Upgrading from previous versions
|
||||
--------------------------------
|
||||
|
||||
@ -111,3 +116,4 @@ We're happy to include any type of contribution! This can be:
|
||||
For more information on contributing, please see the `CONTRIBUTING.rst` file.
|
||||
|
||||
.. _note: http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching
|
||||
|
||||
|
@ -108,8 +108,8 @@ HELPDESK_SHOW_CHANGE_PASSWORD = True
|
||||
# Instead of showing the public web portal first,
|
||||
# we can instead redirect users straight to the login page.
|
||||
HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = False
|
||||
LOGIN_URL = '/login/'
|
||||
LOGIN_REDIRECT_URL = '/login/'
|
||||
LOGIN_URL = 'helpdesk:login'
|
||||
LOGIN_REDIRECT_URL = 'helpdesk:home'
|
||||
|
||||
# Database
|
||||
# - by default, we use SQLite3 for the demo, but you can also
|
||||
|
@ -13,7 +13,7 @@ project_root = os.path.dirname(here)
|
||||
NAME = 'django-helpdesk-demodesk'
|
||||
DESCRIPTION = 'A demo Django project using django-helpdesk'
|
||||
README = open(os.path.join(here, 'README.rst')).read()
|
||||
VERSION = '0.3.0b2'
|
||||
VERSION = '0.3.0b3'
|
||||
#VERSION = open(os.path.join(project_root, 'VERSION')).read().strip()
|
||||
AUTHOR = 'django-helpdesk team'
|
||||
URL = 'https://github.com/django-helpdesk/django-helpdesk'
|
||||
|
@ -4,42 +4,35 @@ Django Helpdesk - A Django powered ticket tracker for small enterprise.
|
||||
(c) Copyright 2008 Jutda. Copyright 2018 Timothy Hobbs. All Rights Reserved.
|
||||
See LICENSE for details.
|
||||
"""
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import encoding, timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from helpdesk import settings
|
||||
from helpdesk.lib import safe_template_context, process_attachments
|
||||
from helpdesk.models import Queue, Ticket, TicketCC, FollowUp, IgnoreEmail
|
||||
|
||||
from datetime import timedelta
|
||||
import base64
|
||||
import binascii
|
||||
# import base64
|
||||
import email
|
||||
from email.header import decode_header
|
||||
from email.utils import getaddresses, parseaddr, collapse_rfc2231_value
|
||||
import imaplib
|
||||
import logging
|
||||
import mimetypes
|
||||
from os import listdir, unlink
|
||||
from os.path import isfile, join
|
||||
import os
|
||||
import poplib
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
from email.utils import getaddresses
|
||||
from os.path import isfile, join
|
||||
from time import ctime
|
||||
from optparse import make_option
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db.models import Q
|
||||
from django.utils import encoding, timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from email_reply_parser import EmailReplyParser
|
||||
|
||||
import logging
|
||||
from helpdesk import settings
|
||||
from helpdesk.lib import safe_template_context, process_attachments
|
||||
from helpdesk.models import Queue, Ticket, TicketCC, FollowUp, IgnoreEmail
|
||||
|
||||
|
||||
# import User model, which may be a custom model
|
||||
User = get_user_model()
|
||||
@ -70,37 +63,48 @@ def process_email(quiet=False):
|
||||
if q.logging_type in logging_types:
|
||||
logger.setLevel(logging_types[q.logging_type])
|
||||
elif not q.logging_type or q.logging_type == 'none':
|
||||
logging.disable(logging.CRITICAL) # disable all messages
|
||||
# disable all handlers so messages go to nowhere
|
||||
logger.handlers = []
|
||||
logger.propagate = False
|
||||
if quiet:
|
||||
logger.propagate = False # do not propagate to root logger that would log to console
|
||||
logdir = q.logging_dir or '/var/log/helpdesk/'
|
||||
|
||||
# Log messages to specific file only if the queue has it configured
|
||||
if (q.logging_type in logging_types) and q.logging_dir: # if it's enabled and the dir is set
|
||||
log_file_handler = logging.FileHandler(join(q.logging_dir, q.slug + '_get_email.log'))
|
||||
logger.addHandler(log_file_handler)
|
||||
else:
|
||||
log_file_handler = None
|
||||
|
||||
try:
|
||||
handler = logging.FileHandler(join(logdir, q.slug + '_get_email.log'))
|
||||
logger.addHandler(handler)
|
||||
|
||||
if not q.email_box_last_check:
|
||||
q.email_box_last_check = timezone.now() - timedelta(minutes=30)
|
||||
|
||||
queue_time_delta = timedelta(minutes=q.email_box_interval or 0)
|
||||
|
||||
if (q.email_box_last_check + queue_time_delta) < timezone.now():
|
||||
process_queue(q, logger=logger)
|
||||
q.email_box_last_check = timezone.now()
|
||||
q.save()
|
||||
finally:
|
||||
# we must close the file handler correctly if it's created
|
||||
try:
|
||||
handler.close()
|
||||
if log_file_handler:
|
||||
log_file_handler.close()
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
try:
|
||||
logger.removeHandler(handler)
|
||||
if log_file_handler:
|
||||
logger.removeHandler(log_file_handler)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
|
||||
|
||||
def pop3_sync(q, logger, server):
|
||||
server.getwelcome()
|
||||
try:
|
||||
server.stls()
|
||||
except Exception:
|
||||
logger.warning("POP3 StartTLS failed or unsupported. Connection will be unencrypted.")
|
||||
server.user(q.email_box_user or settings.QUEUE_EMAIL_BOX_USER)
|
||||
server.pass_(q.email_box_pass or settings.QUEUE_EMAIL_BOX_PASSWORD)
|
||||
|
||||
@ -138,17 +142,27 @@ def pop3_sync(q, logger, server):
|
||||
|
||||
def imap_sync(q, logger, server):
|
||||
try:
|
||||
try:
|
||||
server.starttl()
|
||||
except Exception:
|
||||
logger.warning("IMAP4 StartTLS unsupported or failed. Connection will be unencrypted.")
|
||||
server.login(q.email_box_user or
|
||||
settings.QUEUE_EMAIL_BOX_USER,
|
||||
q.email_box_pass or
|
||||
settings.QUEUE_EMAIL_BOX_PASSWORD)
|
||||
server.select(q.email_box_imap_folder)
|
||||
except imaplib.IMAP4.abort:
|
||||
logger.error("IMAP login failed. Check that the server is accessible and that the username and password are correct.")
|
||||
logger.error(
|
||||
"IMAP login failed. Check that the server is accessible and that "
|
||||
"the username and password are correct."
|
||||
)
|
||||
server.logout()
|
||||
sys.exit()
|
||||
except ssl.SSLError:
|
||||
logger.error("IMAP login failed due to SSL error. This is often due to a timeout. Please check your connection and try again.")
|
||||
logger.error(
|
||||
"IMAP login failed due to SSL error. This is often due to a timeout. "
|
||||
"Please check your connection and try again."
|
||||
)
|
||||
server.logout()
|
||||
sys.exit()
|
||||
|
||||
@ -171,7 +185,10 @@ def imap_sync(q, logger, server):
|
||||
else:
|
||||
logger.warn("Message %s was not successfully processed, and will be left on IMAP server" % num)
|
||||
except imaplib.IMAP4.error:
|
||||
logger.error("IMAP retrieve failed. Is the folder '%s' spelled correctly, and does it exist on the server?" % q.email_box_imap_folder)
|
||||
logger.error(
|
||||
"IMAP retrieve failed. Is the folder '%s' spelled correctly, and does it exist on the server?",
|
||||
q.email_box_imap_folder
|
||||
)
|
||||
|
||||
server.expunge()
|
||||
server.close()
|
||||
@ -243,7 +260,7 @@ def process_queue(q, logger):
|
||||
|
||||
elif email_box_type == 'local':
|
||||
mail_dir = q.email_box_local_dir or '/var/lib/mail/helpdesk/'
|
||||
mail = [join(mail_dir, f) for f in listdir(mail_dir) if isfile(join(mail_dir, f))]
|
||||
mail = [join(mail_dir, f) for f in os.listdir(mail_dir) if isfile(join(mail_dir, f))]
|
||||
logger.info("Found %d messages in local mailbox directory" % len(mail))
|
||||
|
||||
logger.info("Found %d messages in local mailbox directory" % len(mail))
|
||||
@ -253,15 +270,15 @@ def process_queue(q, logger):
|
||||
full_message = encoding.force_text(f.read(), errors='replace')
|
||||
ticket = object_from_message(message=full_message, queue=q, logger=logger)
|
||||
if ticket:
|
||||
logger.info("Successfully processed message %d, ticket/comment created." % i)
|
||||
logger.info("Successfully processed message %d, ticket/comment created.", i)
|
||||
try:
|
||||
unlink(m) # delete message file if ticket was successful
|
||||
except OSError:
|
||||
logger.error("Unable to delete message %d." % i)
|
||||
os.unlink(m) # delete message file if ticket was successful
|
||||
except OSError as e:
|
||||
logger.error("Unable to delete message %d (%s).", i, str(e))
|
||||
else:
|
||||
logger.info("Successfully deleted message %d." % i)
|
||||
logger.info("Successfully deleted message %d.", i)
|
||||
else:
|
||||
logger.warn("Message %d was not successfully processed, and will be left in local directory" % i)
|
||||
logger.warn("Message %d was not successfully processed, and will be left in local directory", i)
|
||||
|
||||
|
||||
def decodeUnknown(charset, string):
|
||||
@ -277,7 +294,11 @@ def decodeUnknown(charset, string):
|
||||
|
||||
def decode_mail_headers(string):
|
||||
decoded = email.header.decode_header(string)
|
||||
return u' '.join([str(msg, encoding=charset, errors='replace') if charset else str(msg) for msg, charset in decoded])
|
||||
return u' '.join([
|
||||
str(msg, encoding=charset, errors='replace') if charset else str(msg)
|
||||
for msg, charset
|
||||
in decoded
|
||||
])
|
||||
|
||||
|
||||
def create_ticket_cc(ticket, cc_list):
|
||||
@ -305,7 +326,7 @@ def create_ticket_cc(ticket, cc_list):
|
||||
try:
|
||||
ticket_cc = subscribe_to_ticket_updates(ticket=ticket, user=user, email=cced_email)
|
||||
new_ticket_ccs.append(ticket_cc)
|
||||
except ValidationError as err:
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
return new_ticket_ccs
|
||||
@ -362,7 +383,6 @@ def create_object_from_email_message(message, ticket_id, payload, files, logger)
|
||||
logger.debug("Created new ticket %s-%s" % (ticket.queue.slug, ticket.id))
|
||||
|
||||
new = True
|
||||
update = ''
|
||||
|
||||
# Old issue being re-opened
|
||||
elif ticket.status == Ticket.CLOSED_STATUS:
|
||||
@ -389,7 +409,10 @@ def create_object_from_email_message(message, ticket_id, payload, files, logger)
|
||||
|
||||
attached = process_attachments(f, files)
|
||||
for att_file in attached:
|
||||
logger.info("Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size))
|
||||
logger.info(
|
||||
"Attachment '%s' (with size %s) successfully added to ticket from email.",
|
||||
att_file[0], att_file[1].size
|
||||
)
|
||||
|
||||
context = safe_template_context(ticket)
|
||||
|
||||
@ -402,8 +425,8 @@ def create_object_from_email_message(message, ticket_id, payload, files, logger)
|
||||
|
||||
ticket_cc_list = TicketCC.objects.filter(ticket=ticket).all().values_list('email', flat=True)
|
||||
|
||||
for email in ticket_cc_list:
|
||||
notifications_to_be_sent.append(email)
|
||||
for email_address in ticket_cc_list:
|
||||
notifications_to_be_sent.append(email_address)
|
||||
|
||||
# send mail to appropriate people now depending on what objects
|
||||
# were created and who was CC'd
|
||||
@ -453,8 +476,6 @@ def object_from_message(message, queue, logger):
|
||||
# correctly. Not ideal, but this seems to work for now.
|
||||
sender_email = email.utils.getaddresses(['\"' + sender.replace('<', '\" <')])[0][1]
|
||||
|
||||
body_plain, body_html = '', ''
|
||||
|
||||
cc = message.get_all('cc', None)
|
||||
if cc:
|
||||
# first, fixup the encoding if necessary
|
||||
@ -530,18 +551,23 @@ def object_from_message(message, queue, logger):
|
||||
if not name:
|
||||
ext = mimetypes.guess_extension(part.get_content_type())
|
||||
name = "part-%i%s" % (counter, ext)
|
||||
|
||||
# FIXME: this code gets the paylods, then does something with it and then completely ignores it
|
||||
# writing the part.get_payload(decode=True) instead; and then the payload variable is
|
||||
# replaced by some dict later.
|
||||
# the `payloadToWrite` has been also ignored so was commented
|
||||
payload = part.get_payload()
|
||||
if isinstance(payload, list):
|
||||
payload = payload.pop().as_string()
|
||||
payloadToWrite = payload
|
||||
# payloadToWrite = payload
|
||||
# check version of python to ensure use of only the correct error type
|
||||
non_b64_err = TypeError
|
||||
try:
|
||||
logger.debug("Try to base64 decode the attachment payload")
|
||||
payloadToWrite = base64.decodebytes(payload)
|
||||
# payloadToWrite = base64.decodebytes(payload)
|
||||
except non_b64_err:
|
||||
logger.debug("Payload was not base64 encoded, using raw bytes")
|
||||
payloadToWrite = payload
|
||||
# payloadToWrite = payload
|
||||
files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0]))
|
||||
logger.debug("Found MIME attachment %s" % name)
|
||||
|
||||
|
@ -739,7 +739,7 @@
|
||||
"heading" : "Ticket mis à jour",
|
||||
"subject" : "(Mis à jour)",
|
||||
"template_name" : "updated_cc",
|
||||
"html" : "<p style=\"font-family: sans-serif; font-size: 1em;\">Bonjour,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Ce courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }} a été mis à jour.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>File d'attente</b> : {{ ticket.ticket }}<br>\r\n<b>Queue</b> : {{ queue.title }}<br>\r\n<b>Titre</b> : {{ ticket.title }}<br>\r\n<b>Ouvert le</b> : {{ ticket.created|date:\"l j F Y à H:i\" }}<br>\r\n<b>Soumis par</b> : {{ ticket.submitter_email|default:\"Inconnu\" }}<br>\r\n<b>Priorité</b> : {{ ticket.get_priority_display }}<br>\r\n<b>Statut</b> : {{ ticket.get_status }}<br>\r\n<b>Assigné à</b> : {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>Voir le ticket en ligne</a></b> pour le mettre à jour (après authentification)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Pour mémoire, la description originelle était :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Le commentaire suivant a été ajouté :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ comment }}</blockquote>\r\n\r\n<p style=\"font-family: Tahoma, Arial, sans-serif; font-size: 11pt;\">Cette information{% if private %}n' a pas{% else %}a{% endif %}été envoyé par mail à l'émetteur.</p>"
|
||||
"html" : "<p style=\"font-family: sans-serif; font-size: 1em;\">Bonjour,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Ce courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }} a été mis à jour.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>File d'attente</b> : {{ ticket.ticket }}<br>\r\n<b>Queue</b> : {{ queue.title }}<br>\r\n<b>Titre</b> : {{ ticket.title }}<br>\r\n<b>Ouvert le</b> : {{ ticket.created|date:\"l j F Y à H:i\" }}<br>\r\n<b>Soumis par</b> : {{ ticket.submitter_email|default:\"Inconnu\" }}<br>\r\n<b>Priorité</b> : {{ ticket.get_priority_display }}<br>\r\n<b>Statut</b> : {{ ticket.get_status }}<br>\r\n<b>Assigné à</b> : {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>Voir le ticket en ligne</a></b> pour le mettre à jour (après authentification)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Pour mémoire, la description originelle était :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Le commentaire suivant a été ajouté :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ comment }}</blockquote>\r\n\r\n<p style=\"font-family: Tahoma, Arial, sans-serif; font-size: 11pt;\">Cette information {% if private %}n' a pas{% else %}a{% endif %} été envoyé par mail à l'émetteur.</p>"
|
||||
},
|
||||
"pk" : 62
|
||||
},
|
||||
@ -750,8 +750,8 @@
|
||||
"heading" : "Ticket mis à jour",
|
||||
"template_name" : "updated_owner",
|
||||
"subject" : "(Mis à jour - à vous)",
|
||||
"html" : "<p style=\"font-family: sans-serif; font-size: 1em;\">Bonjour,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Ce courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assigné, a été mis à jour.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>File d'attente</b> : {{ ticket.ticket }}<br>\r\n<b>Queue</b> : {{ queue.title }}<br>\r\n<b>Titre</b> : {{ ticket.title }}<br>\r\n<b>Ouvert le</b> : {{ ticket.created|date:\"l j F Y à H:i\" }}<br>\r\n<b>Soumis par</b> : {{ ticket.submitter_email|default:\"Inconnu\" }}<br>\r\n<b>Priorité</b> : {{ ticket.get_priority_display }}<br>\r\n<b>Statut</b> : {{ ticket.get_status }}<br>\r\n<b>Assigné à</b> : {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>Voir le ticket en ligne</a></b> pour le mettre à jour (après authentification)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Pour mémoire, la description originelle était :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Le commentaire suivant a été ajouté :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ comment }}</blockquote>\r\n\r\n<p style=\"font-family: Tahoma, Arial, sans-serif; font-size: 11pt;\">Cette information{% if private %}n' a pas{% else %}a{% endif %}été envoyé par mail à l'émetteur.</p>",
|
||||
"plain_text" : "Hello,\r\n\r\nCe courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assigné, a été mis à jour.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l j F Y à H:i\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Inconnu\" }}\r\nPriorité : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssigné à : {{ 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 été ajouté :\r\n\r\n{{ comment }}\r\n\r\nCette information{% if private %}n' a pas{% else %}a{% endif %}été envoyé par mail à l'émetteur.\r\n\r\n",
|
||||
"html" : "<p style=\"font-family: sans-serif; font-size: 1em;\">Bonjour,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Ce courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assigné, a été mis à jour.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>File d'attente</b> : {{ ticket.ticket }}<br>\r\n<b>Queue</b> : {{ queue.title }}<br>\r\n<b>Titre</b> : {{ ticket.title }}<br>\r\n<b>Ouvert le</b> : {{ ticket.created|date:\"l j F Y à H:i\" }}<br>\r\n<b>Soumis par</b> : {{ ticket.submitter_email|default:\"Inconnu\" }}<br>\r\n<b>Priorité</b> : {{ ticket.get_priority_display }}<br>\r\n<b>Statut</b> : {{ ticket.get_status }}<br>\r\n<b>Assigné à</b> : {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>Voir le ticket en ligne</a></b> pour le mettre à jour (après authentification)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Pour mémoire, la description originelle était :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Le commentaire suivant a été ajouté :</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ comment }}</blockquote>\r\n\r\n<p style=\"font-family: Tahoma, Arial, sans-serif; font-size: 11pt;\">Cette information {% if private %}n' a pas{% else %}a{% endif %} été envoyé par mail à l'émetteur.</p>",
|
||||
"plain_text" : "Hello,\r\n\r\nCe courriel indicatif permet de vous prévenir que le ticket {{ ticket.ticket }} (\"{{ ticket.title }}\") par {{ ticket.submitter_email }}, qui vous est assigné, a été mis à jour.\r\n\r\nIdentifiant : {{ ticket.ticket }}\r\nFile d'attente : {{ queue.title }}\r\nTitre : {{ ticket.title }}\r\nOuvert le : {{ ticket.created|date:\"l j F Y à H:i\" }}\r\nSoumis par : {{ ticket.submitter_email|default:\"Inconnu\" }}\r\nPriorité : {{ ticket.get_priority_display }}\r\nStatut : {{ ticket.get_status }}\r\nAssigné à : {{ 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 été ajouté :\r\n\r\n{{ comment }}\r\n\r\nCette information {% if private %}n' a pas{% else %}a{% endif %} été envoyé par mail à l'émetteur.\r\n\r\n",
|
||||
"locale" : "fr"
|
||||
}
|
||||
},
|
||||
|
@ -98,6 +98,9 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
||||
try:
|
||||
current_value = TicketCustomFieldValue.objects.get(ticket=self.instance, field=field)
|
||||
initial_value = current_value.value
|
||||
# If it is boolean field, transform the value to a real boolean instead of a string
|
||||
if current_value.field.data_type == 'boolean':
|
||||
initial_value = initial_value == 'True'
|
||||
except TicketCustomFieldValue.DoesNotExist:
|
||||
initial_value = None
|
||||
instanceargs = {
|
||||
|
@ -502,7 +502,7 @@ msgstr "Složka pro log soubory"
|
||||
#: third_party/django-helpdesk/helpdesk/models.py:306
|
||||
msgid ""
|
||||
"If logging is enabled, what directory should we use to store log files for "
|
||||
"this queue? If no directory is set, default to /var/log/helpdesk/"
|
||||
"this queue? The standard logging mechanims are used if no directory is set"
|
||||
msgstr ""
|
||||
|
||||
#: third_party/django-helpdesk/helpdesk/models.py:317
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -522,11 +522,10 @@ msgstr "Dossier de logs"
|
||||
#: .\models.py:308
|
||||
msgid ""
|
||||
"If logging is enabled, what directory should we use to store log files for "
|
||||
"this queue? If no directory is set, default to /var/log/helpdesk/"
|
||||
"this queue? The standard logging mechanims are used if no directory is set"
|
||||
msgstr ""
|
||||
"Si les logs sont activés, quel dossier doit être utilisé pour stocker les "
|
||||
"fichiers de logs pour cette file ? Si aucun dossier n'est défini, cela sera /"
|
||||
"var/log/helpdesk/ par défaut"
|
||||
"fichiers de logs pour cette file?"
|
||||
|
||||
#: .\models.py:319
|
||||
msgid "Default owner"
|
||||
|
@ -531,13 +531,15 @@ msgstr ""
|
||||
|
||||
#: models.py:247
|
||||
msgid "Logging Directory"
|
||||
msgstr ""
|
||||
msgstr "Директория логов"
|
||||
|
||||
#: models.py:251
|
||||
msgid ""
|
||||
"If logging is enabled, what directory should we use to store log files for "
|
||||
"this queue? If no directory is set, default to /var/log/helpdesk/"
|
||||
"this queue? The standard logging mechanims are used if no directory is set"
|
||||
msgstr ""
|
||||
"Директория в которую будут сохраняться файлы с логами; стандартная конфигурация "
|
||||
"используется если ничего не указано"
|
||||
|
||||
#: models.py:261
|
||||
msgid "Default owner"
|
||||
|
@ -470,7 +470,7 @@ msgstr ""
|
||||
#: models.py:308
|
||||
msgid ""
|
||||
"If logging is enabled, what directory should we use to store log files for "
|
||||
"this queue? If no directory is set, default to /var/log/helpdesk/"
|
||||
"this queue? The standard logging mechanims are used if no directory is set"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:319
|
||||
|
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='queue',
|
||||
name='logging_dir',
|
||||
field=models.CharField(blank=True, help_text='If logging is enabled, what directory should we use to store log files for this queue? If no directory is set, default to /var/log/helpdesk/', max_length=200, null=True, verbose_name='Logging Directory'),
|
||||
field=models.CharField(blank=True, help_text='If logging is enabled, what directory should we use to store log files for this queue? The standard logging mechanims are used if no directory is set', max_length=200, null=True, verbose_name='Logging Directory'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='queue',
|
||||
|
@ -307,7 +307,7 @@ class Queue(models.Model):
|
||||
null=True,
|
||||
help_text=_('If logging is enabled, what directory should we use to '
|
||||
'store log files for this queue? '
|
||||
'If no directory is set, default to /var/log/helpdesk/'),
|
||||
'The standard logging mechanims are used if no directory is set'),
|
||||
)
|
||||
|
||||
default_owner = models.ForeignKey(
|
||||
|
@ -34,7 +34,7 @@
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for='id_{{ field.name }}'>
|
||||
{% trans field.label %}
|
||||
{{ field.label }}
|
||||
{% if not field.field.required %}
|
||||
({% trans "Optional" %})
|
||||
{% endif %}
|
||||
|
@ -153,9 +153,10 @@ class GetEmailParametricTemplate(object):
|
||||
else:
|
||||
# Test local email reading
|
||||
if self.method == 'local':
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)):
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||
|
||||
@ -224,9 +225,10 @@ class GetEmailParametricTemplate(object):
|
||||
else:
|
||||
# Test local email reading
|
||||
if self.method == 'local':
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open' if six.PY3 else '__builtin__.open', mock.mock_open(read_data=test_email)):
|
||||
mock.patch('builtins.open' if six.PY3 else '__builtin__.open', mock.mock_open(read_data=test_email)), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||
|
||||
@ -299,9 +301,10 @@ class GetEmailParametricTemplate(object):
|
||||
else:
|
||||
# Test local email reading
|
||||
if self.method == 'local':
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)):
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||
|
||||
@ -412,9 +415,10 @@ class GetEmailParametricTemplate(object):
|
||||
else:
|
||||
# Test local email reading
|
||||
if self.method == 'local':
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=msg.as_string())):
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=msg.as_string())), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||
|
||||
@ -502,9 +506,10 @@ class GetEmailParametricTemplate(object):
|
||||
else:
|
||||
# Test local email reading
|
||||
if self.method == 'local':
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)):
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1']
|
||||
|
||||
@ -670,9 +675,10 @@ class GetEmailCCHandling(TestCase):
|
||||
test_email = "To: queue@example.com\nCc: " + test_email_cc_one + ", " + test_email_cc_one + ", " + test_email_cc_two + ", " + test_email_cc_three + "\nCC: " + test_email_cc_one + ", " + test_email_cc_three + ", " + test_email_cc_four + ", " + ticket_user_emails + "\nFrom: " + test_email_from + "\nSubject: " + test_email_subject + "\n\n" + test_email_body
|
||||
test_mail_len = len(test_email)
|
||||
|
||||
with mock.patch('helpdesk.email.listdir') as mocked_listdir, \
|
||||
with mock.patch('os.listdir') as mocked_listdir, \
|
||||
mock.patch('helpdesk.email.isfile') as mocked_isfile, \
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)):
|
||||
mock.patch('builtins.open', mock.mock_open(read_data=test_email)), \
|
||||
mock.patch('os.unlink'):
|
||||
mocked_isfile.return_value = True
|
||||
mocked_listdir.return_value = ['filename1']
|
||||
|
||||
@ -708,7 +714,10 @@ class GetEmailCCHandling(TestCase):
|
||||
|
||||
# build matrix of test cases
|
||||
case_methods = [c[0] for c in Queue._meta.get_field('email_box_type').choices]
|
||||
case_socks = [False] + [c[0] for c in Queue._meta.get_field('socks_proxy_type').choices]
|
||||
|
||||
# uncomment if you want to run tests with socks - which is much slover
|
||||
# case_socks = [False] + [c[0] for c in Queue._meta.get_field('socks_proxy_type').choices]
|
||||
case_socks = [False]
|
||||
case_matrix = list(itertools.product(case_methods, case_socks))
|
||||
|
||||
# Populate TestCases from the matrix of parameters
|
||||
|
@ -1,3 +1,10 @@
|
||||
"""
|
||||
Usage:
|
||||
$ python -m venv .venv
|
||||
$ source .venv/bin/activate
|
||||
$ pip install -r requirements-testing.txt -r requirements.txt
|
||||
$ python ./quicktest.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
@ -65,7 +72,6 @@ class QuickDjangoTest(object):
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.tests = args
|
||||
self._tests()
|
||||
@ -102,6 +108,7 @@ class QuickDjangoTest(object):
|
||||
if failures:
|
||||
sys.exit(failures)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
What do when the user hits this file from the shell.
|
||||
|
Loading…
Reference in New Issue
Block a user