Merge master 0.2.3 bugfixes into develop

This commit is contained in:
Garret Wassermann 2017-12-09 23:02:12 -05:00
commit 92b43ef495
23 changed files with 3472 additions and 540 deletions

View File

@ -7,7 +7,7 @@ python:
- "3.6" - "3.6"
env: env:
- DJANGO=1.11.4 - DJANGO=1.11.8
install: install:
- pip install -q Django==$DJANGO - pip install -q Django==$DJANGO
@ -15,7 +15,7 @@ install:
- pip install -q -r requirements-testing.txt - pip install -q -r requirements-testing.txt
before_script: before_script:
- "pep8 --exclude=migrations --ignore=E501 helpdesk" - "pycodestyle --exclude=migrations --ignore=E501 helpdesk"
script: script:
- coverage run --source='.' quicktest.py helpdesk - coverage run --source='.' quicktest.py helpdesk

View File

@ -62,6 +62,7 @@ TEMPLATES = [
'DIRS': [], 'DIRS': [],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'debug': True,
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',

View File

@ -13,7 +13,7 @@ project_root = os.path.dirname(here)
NAME = 'django-helpdesk-demodesk' NAME = 'django-helpdesk-demodesk'
DESCRIPTION = 'A demo Django project using django-helpdesk' DESCRIPTION = 'A demo Django project using django-helpdesk'
README = open(os.path.join(here, 'README.rst')).read() README = open(os.path.join(here, 'README.rst')).read()
VERSION = '0.2.0' VERSION = '0.2.3'
#VERSION = open(os.path.join(project_root, 'VERSION')).read().strip() #VERSION = open(os.path.join(project_root, 'VERSION')).read().strip()
AUTHOR = 'django-helpdesk team' AUTHOR = 'django-helpdesk team'
URL = 'https://github.com/django-helpdesk/django-helpdesk' URL = 'https://github.com/django-helpdesk/django-helpdesk'

View File

@ -119,7 +119,7 @@ errors with trying to create User settings.
Ideally, accessing http://MEDIA_URL/helpdesk/attachments/ will give you a 403 access denied error. Ideally, accessing http://MEDIA_URL/helpdesk/attachments/ will give you a 403 access denied error.
7. If it's not already installed, install ``django-markdown-deux`` and ensure it's in your ``INSTALLED_APPS``:: 7. If it's not already installed, install ``markdown_deux`` and ensure it's in your ``INSTALLED_APPS``::
pip install django-markdown-deux pip install django-markdown-deux

View File

@ -1153,8 +1153,197 @@
"subject": "(Actualizado)", "subject": "(Actualizado)",
"locale": "es" "locale": "es"
} }
},
{
"pk": 97,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "已分配_CC",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示, {{ ticket.submitter_email }}的工单 <a href=\"{{ ticket.staff_url }}\"><b>{{ ticket.ticket }}</b></a> (<em>{{ ticket.title }}</em>) {% if ticket.assigned_to %}已经分配给 {{ ticket.assigned_to }}{% else %}还未分配{% endif %}.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>",
"plain_text": "您好,\r\n\r\n温馨提示, {{ ticket.submitter_email }}提交的工单 {{ ticket.ticket }} (\"{{ ticket.title }}\") 已经 {% if ticket.assigned_to %}分配给 {{ ticket.assigned_to }}{% else %}未分配{% endif %}.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }}\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n",
"heading": "工单已分配",
"subject": "(已分配)",
"locale": "zh"
}
},
{
"pk": 98,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "assigned_owner",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示, 提交者{{ ticket.submitter_email }}的工单 <a href=\"{{ ticket.staff_url }}\"><b>{{ ticket.ticket }}</b></a> (<em>{{ ticket.title }}</em>) 已经分配给 <b>you</b>.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: YOU<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>",
"plain_text": "您好,\r\n\r\n温馨提示, 工单 提交者{{ ticket.submitter_email }}的工单{{ ticket.ticket }} 已经 分配给 you.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: YOU\r\n在线查看: {{ ticket.staff_url }}\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n",
"heading": "工单已分配给您",
"subject": "(已分配给您)",
"locale": "zh"
}
},
{
"pk": 99,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "closed_cc",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">工单 <i>{{ ticket.title }}</i> ('{{ ticket.title }}'){% if ticket.assigned_to %}, 分配给 {{ ticket.get_assigned_to }}{% endif %} 已经 关闭</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">提供的解决方案为:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ resolution }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果您想在线查看, 可以访问 <a href='{{ ticket.staff_url }}'>{{ ticket.staff_url }}</a>.</p>",
"plain_text": "您好,\r\n\r\n工单 {{ ticket.title }} (\"{{ ticket.title }}\"){% if ticket.assigned_to %}, 分配给 {{ ticket.assigned_to }}{% endif %} 已经 关闭\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n提供的解决方案为:\r\n\r\n{{ resolution }}\r\n\r\n",
"heading": "工单已关闭",
"subject": "(已关闭)",
"locale": "zh"
}
},
{
"pk": 100,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "closed_owner",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">以下分配给您的工单, 已经关闭</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">提供的解决方案为:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.resolution }}</blockquote>",
"plain_text": "您好,\r\n\r\n以下分配给您的工单, 已经关闭\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n如果您想在线查看 可以访问 {{ ticket.staff_url }}.\r\n\r\n",
"heading": "工单已关闭",
"subject": "(已关闭)",
"locale": "zh"
}
},
{
"pk": 101,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "closed_submitter",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">您最近记录了主题为<i>{{ ticket.title }}</i>的工单. 本邮件确认工单已经 关闭</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">已提供的方案为:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.resolution }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果您想在线查看, 可以访问 <a href=\"{{ ticket.ticket_url }}\">{{ ticket.ticket_url }}</a>. 如果您认为还需要后续工作, 请用原标题回复此邮件.</p>",
"plain_text": "您好,\r\n\r\n您最近记录了主题为\"{{ ticket.title }}\"的工单. 本邮件确认工单已经 关闭\r\n\r\nI如果您认为还需要后续工作 请用原标题回复此邮件.\r\n\r\n如果您想在线查看 可以访问 {{ ticket.ticket_url }}.\r\n\r\n提供的解决方案为:\r\n\r\n{{ ticket.resolution }}\r\n\r\n",
"heading": "工单已关闭",
"subject": "(已关闭)",
"locale": "zh"
}
},
{
"pk": 102,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "escalated_cc",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示, 工单 <i>{{ ticket.ticket }}</i> ('{{ ticket.title }}') 已经 自动提升优先级.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>",
"plain_text": "您好,\r\n\r\n温馨提示, 工单 {{ ticket.ticket }} (\"{{ ticket.title }}\") 已经自动提升优先级.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n",
"heading": "工单 已经提升优先级",
"subject": "(已经提升优先级)",
"locale": "zh"
}
},
{
"pk": 103,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "escalated_owner",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">A 当前分配给您的工单已经自动提升优先级 as it 已经 打开时间超过预期.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>",
"plain_text": "您好,\r\n\r\nA 分配给您的当前工单已经自动提升优先级, 因为打开时间已经超过预期.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n请查看此工单并尽快提供解决方案.\r\n\r\n",
"heading": "工单 已分配给 您 已经提升优先级",
"subject": "(已经提升优先级)",
"locale": "zh"
}
},
{
"pk": 104,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "escalated_submitter",
"html": "<p style=\"font-family: sans-serif; font-size: 11pt;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 11pt;\">您最近记录了主题为<i>{{ ticket.title }}</i>的工单. 本邮件是想提醒您工单自动升级, 因为已经 打开时间超过预期.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 11pt;\">我们将尽快查看并提供解决方案.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 11pt;\">如果您想在线查看, 可以访问 <a href=\"{{ ticket.ticket_url }}\">{{ ticket.ticket_url }}</a>.</p>",
"plain_text": "您好,\r\n\r\n您最近为我们记录了一个标题为 \"{{ ticket.title }}\" 的工单. 本邮件是想提醒您工单自动升级, 因为已经打开时间超过预期.\r\n\r\n我们将尽快查看并提供解决方案.\r\n\r\n如果您想在线查看 可以访问 {{ ticket.ticket_url }}.\r\n\r\n",
"heading": "您的 工单 已经 已经提升优先级",
"subject": "(已经提升优先级)",
"locale": "zh"
}
},
{
"pk": 105,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "newticket_cc",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示: 新工单已经打开.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">Description:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.description|linebreaksbr }}</blockquote>",
"plain_text": "您好,\r\n\r\n温馨提示 新工单已经 打开.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\nDescription:\r\n{{ ticket.description }}\r\n\r\n",
"heading": "新工单已打开",
"subject": "(已打开)",
"locale": "zh"
}
},
{
"pk": 106,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "newticket_submitter",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示: 我们已收到您主题为 <i>{{ ticket.title }}</i>.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">您当前什么都不用做. 您的工单 已经 分配编号 <b>{{ ticket.ticket }}</b> 且将很快收到回复.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果您想告诉我们更多详情, 或者要查询此工单, 请在主题带上工单id <b>{{ ticket.ticket }}</b> . 最简单就是直接点这个消息的 \"回复\" 按钮.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果您希望在线查看并提供此工单的更多信息, 附加文件或者查看最近更新, 您可以访问 <a href=\"{{ ticket.ticket_url }}\">{{ ticket.ticket_url }}</a>.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">我们将调查您的问题并尽快解决. 您将通过此邮箱收到后续更新和解决方案.</p>",
"plain_text": "您好,\r\n\r\n温馨提示 我们已经收到您主题为 \"{{ ticket.title }}\" 的查询. \r\n\r\n您当前什么都不用做. 您的工单已经分配编号 {{ ticket.ticket }} 且将很快收到回复.\r\n\r\n如果您想告诉我们更多详情, 或者要查询此工单, 请在主题带上工单id '{{ ticket.ticket }}' . 最简单方式就是按下此消息 \"回复\" .\r\n\r\n如果您希望在线查看并提供此工单的更多信息, 附加文件或者查看最近更新, 您可以访问 {{ ticket.ticket_url }}.\r\n\r\n我们将调查您的问题并尽快解决. 您将通过此邮箱收到后续更新和解决方案.\r\n",
"heading": "您的工单已经打开",
"subject": "(已打开)",
"locale": "zh"
}
},
{
"pk": 107,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "解决_cc",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">以下工单 已经 解决.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">增加的解决方案为:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.resolution }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">此方案已经 邮件发送给提交者, 在您关闭之前需要他先确认.</p>",
"plain_text": "您好,\r\n\r\n以下工单 已经 解决:\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n提供的解决方案为:\r\n\r\n{{ ticket.resolution }}\r\n\r\n此方案已经 邮件发送给提交者, 在您关闭之前需要他先确认.\r\n\r\n",
"heading": "工单 解决",
"subject": "(解决)",
"locale": "zh"
}
},
{
"pk": 108,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "解决_owner",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">A 当前分配给您的工单已经 解决.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">增加的解决方案为:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ ticket.resolution }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">此方案已经 邮件发送给提交者, 在您关闭之前需要他先确认.</p>",
"plain_text": "您好,\r\n\r\nA 当前分配给您的工单已经 解决.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述为:\r\n\r\n{{ ticket.description }}\r\n\r\n提供的方案为:\r\n\r\n{{ ticket.resolution }}\r\n\r\n此方案已经 邮件发送给提交者, 在您关闭之前需要他先确认.\r\n\r\n",
"heading": "工单 解决",
"subject": "(解决)",
"locale": "zh"
}
},
{
"pk": 109,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "解决_submitter",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">您最近记录了主题为<i>{{ ticket.title }}</i>的工单. 此邮件是要告知您解决方案.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">工单添加了以下解决方案 <b>{{ ticket.ticket }}</b>:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ resolution }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">请您确认下此解决方案是否解决了您的问题, 这样我们可以关闭此工单? 如果有更多问题或者认为方案不够充分,, 请继续使用邮件的主题回复此邮件.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果您想在线查看, 可以访问 <a href=\"{{ ticket.ticket_url }}\">{{ ticket.ticket_url }}</a>.</p>",
"plain_text": "您好,\r\n\r\n您最近记录了主题为\"{{ ticket.title }}\"的工单. 此邮件是要告知您解决方案.\r\n\r\n工单添加了以下解决方案 {{ ticket.ticket }}:\r\n\r\n{{ resolution }}\r\n\r\n请您确认下此解决方案是否解决了您的问题 这样我们可以关闭此工单? 如果有更多问题或者认为方案不够充分,, 请继续使用邮件的主题回复此邮件.\r\n\r\n如果您想在线查看 可以访问 {{ ticket.ticket_url }}\r\n\r\n",
"heading": "您的 工单 已经 解决",
"subject": "(解决)",
"locale": "zh"
}
},
{
"pk": 110,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "updated_cc",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示, {{ ticket.submitter_email }} 的工单 {{ ticket.ticket }} (\"{{ ticket.title }}\") 已经更新.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">已添加以下评论:</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;\">本信息 {% if private %} 还没有 {% else %} 已经 {% endif %} 邮件发送给提交者.</p>",
"plain_text": "您好,\r\n\r\n温馨提示, 提交者{{ ticket.submitter_email }}的工单{{ ticket.ticket }} 已经更新.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述:\r\n\r\n{{ ticket.description }}\r\n\r\n已添加以下评论:\r\n\r\n{{ comment }}\r\n\r\n本信息 {% if private %}还没有{% else %} 已经 {% endif %} 邮件发送给提交者.\r\n\r\n如果您想在线查看 可以访问 {{ ticket.staff_url }}.\r\n\r\n",
"heading": "工单已更新",
"subject": "(已更新)",
"locale": "zh"
}
},
{
"pk": 111,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "updated_owner",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">温馨提示, 分配给您,提交者{{ ticket.submitter_email }}的工单{{ ticket.ticket }}, 已经更新.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">\r\n<b>工单 ID</b>: {{ ticket.ticket }}<br>\r\n<b>待办</b>: {{ queue.title }}<br>\r\n<b>标题</b>: {{ ticket.title }}<br>\r\n<b>已打开</b>: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}<br>\r\n<b>提交人</b>: {{ ticket.submitter_email|default:\"Unknown\" }}<br>\r\n<b>优先级</b>: {{ ticket.get_priority_display }}<br>\r\n<b>状态</b>: {{ ticket.get_status }}<br>\r\n<b>已分配给</b>: {{ ticket.get_assigned_to }}<br>\r\n<b><a href='{{ ticket.staff_url }}'>在线查看</a></b> 更新此工单 (需要登录)</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">原工单描述参考::</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;\">已添加以下评论:</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;\">本信息 {% if private %} 还未 {% else %} 已经 {% endif %} been 邮件发送给提交者.</p>",
"plain_text": "您好,\r\n\r\n温馨提示, 提交者分配给{{ ticket.submitter_email }} 您的工单 {{ ticket.ticket }} (\"{{ ticket.title }}\") , 已经更新.\r\n\r\n工单 ID: {{ ticket.ticket }}\r\n待办: {{ queue.title }}\r\n标题: {{ ticket.title }}\r\n已打开: {{ ticket.created|date:\"l N jS Y, \\a\\t P\" }}\r\n提交人: {{ ticket.submitter_email|default:\"Unknown\" }}\r\n优先级:{{ ticket.get_priority_display }}\r\n状态: {{ ticket.get_status }}\r\n已分配给: {{ ticket.get_assigned_to }}\r\n在线查看: {{ ticket.staff_url }} (需要登录)\r\n\r\n原始描述:\r\n\r\n{{ ticket.description }}\r\n\r\n添加了一下评论:\r\n\r\n{{ comment }}\r\n\r\n本信息 {% if private %}还没有 {% endif %} 邮件发送给提交者.\r\n\r\n如果您想在线查看 可以访问 {{ ticket.staff_url }}\r\n\r\n",
"heading": "工单已更新",
"subject": "(已更新)",
"locale": "zh"
}
},
{
"pk": 112,
"model": "helpdesk.emailtemplate",
"fields": {
"template_name": "updated_submitter",
"html": "<p style=\"font-family: sans-serif; font-size: 1em;\">您好,</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">您最近记录了主题为<i>{{ ticket.title }}</i>的工单. 此邮件是告知您工单的更新.</p>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">工单添加了以下评论 <b>{{ ticket.ticket }}</b>:</p>\r\n\r\n<blockquote style=\"font-family: sans-serif; font-size: 1em;\">{{ comment }}</blockquote>\r\n\r\n<p style=\"font-family: sans-serif; font-size: 1em;\">如果你需要提供更多信息, 请继续使用邮件的主题回复此邮件. 或者您可以在线查看和更新此工单 <a href=\"{{ ticket.ticket_url }}\">{{ ticket.ticket_url }}</a>.</p>",
"plain_text": "您好,\r\n\r\n您最近记录了主题为\"{{ ticket.title }}\"的工单. 此邮件是告知您工单的更新.\r\n\r\n工单添加了以下评论 {{ ticket.ticket }}:\r\n\r\n{{ comment }}\r\n\r\n如果你需要提供更多信息, 请继续使用邮件的主题回复此邮件. 或者您可以在线查看和更新此工单 {{ ticket.ticket_url }}\r\n\r\n",
"heading": "您的工单已经更新",
"subject": "(已更新)",
"locale": "zh"
}
} }
] ]

View File

@ -267,13 +267,14 @@ def text_is_spam(text, request):
# False if it is not spam. If it cannot be checked for some reason, we # False if it is not spam. If it cannot be checked for some reason, we
# assume it isn't spam. # assume it isn't spam.
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
try: try:
from helpdesk.akismet import Akismet from helpdesk.akismet import Akismet
except: except ImportError:
return False return False
try: try:
site = Site.objects.get_current() site = Site.objects.get_current()
except: except ImproperlyConfigured:
site = Site(domain='configure-django-sites.com') site = Site(domain='configure-django-sites.com')
ak = Akismet( ak = Akismet(

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,8 @@ scripts/get_email.py - Designed to be run from cron, this script checks the
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import timedelta from datetime import timedelta
import base64
import binascii
import email import email
import imaplib import imaplib
import mimetypes import mimetypes
@ -21,10 +23,12 @@ from os.path import isfile, join
import poplib import poplib
import re import re
import socket import socket
import base64 import ssl
import binascii import sys
from time import ctime from time import ctime
from bs4 import BeautifulSoup
from email_reply_parser import EmailReplyParser from email_reply_parser import EmailReplyParser
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
@ -158,7 +162,17 @@ def process_queue(q, logger):
messagesInfo = server.list()[1] messagesInfo = server.list()[1]
logger.info("Received %d messages from POP3 server" % len(messagesInfo)) logger.info("Received %d messages from POP3 server" % len(messagesInfo))
for msg in messagesInfo: for msgRaw in messagesInfo:
if six.PY3 and type(msgRaw) is bytes:
# in py3, msgRaw may be a bytes object, decode to str
try:
msg = msgRaw.decode("utf-8")
except UnicodeError:
# if couldn't decode easily, just leave it raw
msg = msgRaw
else:
# already a str
msg = msgRaw
msgNum = msg.split(" ")[0] msgNum = msg.split(" ")[0]
logger.info("Processing message %s" % msgNum) logger.info("Processing message %s" % msgNum)
@ -189,11 +203,20 @@ def process_queue(q, logger):
logger.info("Attempting IMAP server login") logger.info("Attempting IMAP server login")
try:
server.login(q.email_box_user or server.login(q.email_box_user or
settings.QUEUE_EMAIL_BOX_USER, settings.QUEUE_EMAIL_BOX_USER,
q.email_box_pass or q.email_box_pass or
settings.QUEUE_EMAIL_BOX_PASSWORD) settings.QUEUE_EMAIL_BOX_PASSWORD)
server.select(q.email_box_imap_folder) server.select(q.email_box_imap_folder)
except imaplib.IMAP.abort:
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.")
server.logout()
sys.exit()
try: try:
status, data = server.search(None, 'NOT', 'DELETED') status, data = server.search(None, 'NOT', 'DELETED')
@ -232,7 +255,7 @@ def process_queue(q, logger):
logger.info("Successfully processed message %d, ticket/comment created." % i) logger.info("Successfully processed message %d, ticket/comment created." % i)
try: try:
unlink(m) # delete message file if ticket was successful unlink(m) # delete message file if ticket was successful
except: except OSError:
logger.error("Unable to delete message %d." % i) logger.error("Unable to delete message %d." % i)
else: else:
logger.info("Successfully deleted message %d." % i) logger.info("Successfully deleted message %d." % i)
@ -245,7 +268,7 @@ def decodeUnknown(charset, string):
if not charset: if not charset:
try: try:
return string.decode('utf-8', 'replace') return string.decode('utf-8', 'replace')
except: except UnicodeError:
return string.decode('iso8859-1', 'replace') return string.decode('iso8859-1', 'replace')
return unicode(string, charset) return unicode(string, charset)
elif six.PY3: elif six.PY3:
@ -253,7 +276,7 @@ def decodeUnknown(charset, string):
if not charset: if not charset:
try: try:
return str(string, encoding='utf-8', errors='replace') return str(string, encoding='utf-8', errors='replace')
except: except UnicodeError:
return str(string, encoding='iso8859-1', errors='replace') return str(string, encoding='iso8859-1', errors='replace')
return str(string, encoding=charset, errors='replace') return str(string, encoding=charset, errors='replace')
return string return string
@ -344,10 +367,15 @@ def ticket_from_message(message, queue, logger):
if isinstance(payload, list): if isinstance(payload, list):
payload = payload.pop().as_string() payload = payload.pop().as_string()
payloadToWrite = payload payloadToWrite = payload
# check version of python to ensure use of only the correct error type
if six.PY2:
non_b64_err = binascii.Error
else:
non_b64_err = TypeError
try: try:
logger.debug("Try to base64 decode the attachment payload") logger.debug("Try to base64 decode the attachment payload")
payloadToWrite = base64.decodestring(payload) payloadToWrite = base64.decodestring(payload)
except (binascii.Error, TypeError): except non_b64_err:
logger.debug("Payload was not base64 encoded, using raw bytes") 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])) files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0]))
@ -356,7 +384,12 @@ def ticket_from_message(message, queue, logger):
counter += 1 counter += 1
if not body: if not body:
body = _('No plain-text email body available. Please see attachment "email_html_body.html".') mail = BeautifulSoup(part.get_payload(), "lxml")
if ">" in mail.text:
message_body = mail.text.split(">")[1]
body = message_body.encode('ascii', errors='ignore')
else:
body = mail.text
if ticket: if ticket:
try: try:

View File

@ -526,10 +526,11 @@ class Ticket(models.Model):
a URL to the submitter of a ticket. a URL to the submitter of a ticket.
""" """
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
try: try:
site = Site.objects.get_current() site = Site.objects.get_current()
except: except ImproperlyConfigured:
site = Site(domain='configure-django-sites.com') site = Site(domain='configure-django-sites.com')
return u"http://%s%s?ticket=%s&email=%s" % ( return u"http://%s%s?ticket=%s&email=%s" % (
site.domain, site.domain,
@ -545,10 +546,11 @@ class Ticket(models.Model):
a staff member (in emails etc) a staff member (in emails etc)
""" """
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
try: try:
site = Site.objects.get_current() site = Site.objects.get_current()
except: except ImproperlyConfigured:
site = Site(domain='configure-django-sites.com') site = Site(domain='configure-django-sites.com')
return u"http://%s%s" % ( return u"http://%s%s" % (
site.domain, site.domain,
@ -1149,6 +1151,7 @@ def create_usersettings(sender, instance, created, **kwargs):
if created: if created:
UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS) UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS)
models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL) models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL)

View File

@ -9,7 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
try: try:
DEFAULT_USER_SETTINGS = settings.HELPDESK_DEFAULT_SETTINGS DEFAULT_USER_SETTINGS = settings.HELPDESK_DEFAULT_SETTINGS
except: except AttributeError:
DEFAULT_USER_SETTINGS = None DEFAULT_USER_SETTINGS = None
if not isinstance(DEFAULT_USER_SETTINGS, dict): if not isinstance(DEFAULT_USER_SETTINGS, dict):

View File

@ -21,5 +21,6 @@ from django import template
def in_list(value, arg): def in_list(value, arg):
return value in (arg or []) return value in (arg or [])
register = template.Library() register = template.Library()
register.filter(in_list) register.filter(in_list)

View File

@ -19,5 +19,6 @@ def load_helpdesk_settings(request):
print(e, file=sys.stderr) print(e, file=sys.stderr)
return '' return ''
register = Library() register = Library()
register.filter('load_helpdesk_settings', load_helpdesk_settings) register.filter('load_helpdesk_settings', load_helpdesk_settings)

View File

@ -22,5 +22,6 @@ def saved_queries(user):
print(e, file=sys.stderr) print(e, file=sys.stderr)
return '' return ''
register = Library() register = Library()
register.filter('saved_queries', saved_queries) register.filter('saved_queries', saved_queries)

View File

@ -42,5 +42,6 @@ def num_to_link(text):
text[:match.start() + 1], url, style, match.groups()[0], text[match.end():]) text[:match.start() + 1], url, style, match.groups()[0], text[match.end():])
return mark_safe(text) return mark_safe(text)
register = template.Library() register = template.Library()
register.filter(num_to_link) register.filter(num_to_link)

View File

@ -24,5 +24,6 @@ def user_admin_url(action):
user._meta.app_label, model_name, user._meta.app_label, model_name,
action) action)
register = template.Library() register = template.Library()
register.filter(user_admin_url) register.filter(user_admin_url)

View File

@ -355,6 +355,251 @@ class GetEmailParametricTemplate(object):
self.assertEqual(attach2.followup.id, 2) self.assertEqual(attach2.followup.id, 2)
self.assertEqual(attach2.filename, 'email_html_body.html') self.assertEqual(attach2.filename, 'email_html_body.html')
def test_read_pgp_signed_email(self):
"""Tests reading a PGP signed email to ensure we handle base64
and PGP signatures appropriately."""
# example email text from #567 on GitHub
test_email = """Delivered-To: djangohelpdesk@example.com
Received: by 10.25.26.207 with SMTP id a198csp5858981lfa;
Wed, 8 Nov 2017 13:30:22 -0800 (PST)
X-Received: by 10.107.107.3 with SMTP id g3mr2603398ioc.250.1510176622046;
Wed, 08 Nov 2017 13:30:22 -0800 (PST)
ARC-Seal: i=2; a=rsa-sha256; t=1510176621; cv=pass;
d=google.com; s=arc-20160816;
b=qQ8kBj8+yIoWcJwFNHUlJDYz7P2NfILAxFsn9uPYzXNn/aRw695T1aNFgGL75KUhkA
nDw+h49SUGKDh9ehC+DEiPjwJIxAoz+86rqGWV6XPGW4gQ7GUkHs96CxWndTSD0hdcOl
vygeZrsgzpIOvDxJWrujDPZzcEjsPC2qy3KGsTqtbZGEsNhhRUD8rs/hBVVXaGBatLF+
Sz2krwBZz8Lm+mWRhScjmF12QIHcXe6qYrDLOLEK0+bRkRMS+ZXg9+GPwqHlp58GaHn+
6JncesW3q7k88RQsLlj/8PEw0z1wMndgBVWIcCEtLt4UhZtt/BDxmZSukNN0SzoH4e3k
mxOw==
ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
h=mime-version:user-agent:date:message-id:subject:from:to
:dkim-signature:arc-authentication-results:arc-message-signature
:arc-authentication-results;
bh=cQvDBdivwtDmp1Td9ZWaEf0S4IuZ4hPwaprxSv7XZuE=;
b=p/0Y4PgvEfGWZ8W3eqxzRnSGLbT9gObSU2OI/sLwiN4KFfVmGrBJYkx7DGija0A5eU
DBbETW/16pib+W0IOUtdD7Pt12oWA3Z/uRf7ybXnHIKZ+MObdCXqRJFkga6nY8tWD0H3
maquQR07Q54mYslVMEIKJUKJzVM86npLN2C756ZzZTXiGXf33iowO4/lciGmTAgi+y5p
fEDQCTMoSQ9iGbquFRgNHgMtIM5NWjeMksWKpnfbvZyKs0ZICcPklNxQkDCmDlrOBokT
Zs1RVsWZ7NyPdTomJ0SRyPeysM040aatmnwxFAzwe4GYFNUWZjaep7uPKKlZ4sV/aHBB
iHOQ==
ARC-Authentication-Results: i=2; mx.google.com;
dkim=pass header.i=@gmail.com header.s=20161025 header.b=AArzbi/1;
arc=pass (i=1 spf=pass spfdomain=gmail.com dkim=pass dkdomain=gmail.com dmarc=pass fromdomain=gmail.com);
spf=pass (google.com: domain of bugreporter@example.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=bugreporter@example.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gmail.com
Return-Path: <bugreporter@example.com>
Received: from mail-sor-f41.google.com (mail-sor-f41.google.com. [209.85.220.41])
by mx.google.com with SMTPS id i86sor2420323ioo.204.2017.11.08.13.30.21
for <djangohelpdesk@example.com>
(Google Transport Security);
Wed, 08 Nov 2017 13:30:21 -0800 (PST)
Received-SPF: pass (google.com: domain of bugreporter@example.com designates 209.85.220.41 as permitted sender) client-ip=209.85.220.41;
Authentication-Results: mx.google.com;
dkim=pass header.i=@gmail.com header.s=20161025 header.b=AArzbi/1;
arc=pass (i=1 spf=pass spfdomain=gmail.com dkim=pass dkdomain=gmail.com dmarc=pass fromdomain=gmail.com);
spf=pass (google.com: domain of bugreporter@example.com designates 209.85.220.41 as permitted sender) smtp.mailfrom=bugreporter@example.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gmail.com
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20161025;
h=x-gm-message-state:dkim-signature:to:from:subject:message-id:date
:user-agent:mime-version;
bh=cQvDBdivwtDmp1Td9ZWaEf0S4IuZ4hPwaprxSv7XZuE=;
b=MCiZzHu6ZV3kMTQBRL/b5uBy4jbHFS97+z9apL239dYS+z0LlTiHpKbs3qohFe3As1
gu2l0SAcdGw0qeplgmOlX9HXvKetBRLldfHeX/JJZ2yokpjc6CxVT8gF8YP2UmfAs0cb
JI8TTDqiWmhayf7xfblRIUP7vfwyTH9cLmvKMMAqWvrppyUlqlxWgyO7xtzV9jdThpqP
O0jO9CqsRmbEDc4vZAtOTXm1O69jCz66oll6H4T5Nka9HUpyHFZzv7Z0j0F/5djfzjCQ
HCFZhzobEgZAmBC9o2Y5aDvKCnWJGR5kVTtBQaFCuxr57o4zq0D359V3gMMPRGMdujDP
hXAQ==
X-Google-Smtp-Source: ABhQp+SbAIRuabSw2EkD+7YFXtLiCFINtymAshxVYuNZhApd39ymv2m9UnIM3rZNIHonQBywtZ3VjalQxeN8lVuWD6OquEskEc8=
ARC-Seal: i=1; a=rsa-sha256; t=1510176621; cv=none;
d=google.com; s=arc-20160816;
b=mOqnqVV4oq14hoOdEA+yVvQYQd/sv/Qr//xmW6r94dKaUczdbFG+Uy8x7EbuF/ILJt
ByFmE8+HUH8tosfHn8+zFmsHFr3Wi7il64wdeuVqoOuDQS1HejcH9ln5LVjwsr7EE6Ly
6gCT7QupvSQ+FkhyNH+zNHuGztw5F4Sa2r5UlmR5VAJ4+V1MEfVYwzEr7vgPnmEj8jga
PtmD05EfYWrWt27Cw8oS+CgS0CNcHaaiRr7JX3EQbNRrLp5M9GjKhiq/ckt2a5NKJYMH
zISYQzxk7EgHGFrwn+JZx+oKqG3Zl2pd5oKmzJkFeSaGT+qYp3SES4z3Vi6z4VxGduox
f38g==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
h=mime-version:user-agent:date:message-id:subject:from:to
:dkim-signature:arc-authentication-results;
bh=cQvDBdivwtDmp1Td9ZWaEf0S4IuZ4hPwaprxSv7XZuE=;
b=R5FsED2qOoEJshMotswEPOAn8GyvaHHd4zM9wAH+qnzuoV9RFhSChbkAkypi73SPs/
D7K49dYKSfsuWPF1RXoD8qchVfROF5Y7kD0JHy7KJcuHXzwb5gYLNrZpB2R9XbBOGe1j
lgQvnEVwmgeJiLXKQVeQDECxs8DFlkIpPIbmJK02Ry/Q0S8TnBEs0mrWn49l70IsZB6U
0XCpUPAt9NhsIUxoZKZv+zOwpQq6uwJkqRa5ukH0OPRr891MpeZldw7+gINjxxEmPAS9
GYfMeCpX9afFbQMUizbUbKwOZPt7ahn3x1C5x4AwgQmtzXYfA/quyiXAukTzoYk8FUqs
U1QA==
ARC-Authentication-Results: i=1; gmr-mx.google.com;
dkim=pass header.i=@gmail.com header.s=20161025 header.b=AArzbi/1;
spf=pass (google.com: domain of bugreporter@example.com designates 2607:f8b0:400e:c00::233 as permitted sender) smtp.mailfrom=bugreporter@example.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gmail.com
Return-Path: <bugreporter@example.com>
Received: from mail-pf0-x233.google.com (mail-pf0-x233.google.com. [2607:f8b0:400e:c00::233])
by gmr-mx.google.com with ESMTPS id l10si463482ioc.2.2017.11.08.13.30.21
for <djangohelpdesk@example.com>
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Wed, 08 Nov 2017 13:30:21 -0800 (PST)
Received-SPF: pass (google.com: domain of bugreporter@example.com designates 2607:f8b0:400e:c00::233 as permitted sender) client-ip=2607:f8b0:400e:c00::233;
Received: by mail-pf0-x233.google.com with SMTP id p87so2672006pfj.3
for <djangohelpdesk@example.com>; Wed, 08 Nov 2017 13:30:21 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=gmail.com; s=20161025;
h=to:from:subject:message-id:date:user-agent:mime-version;
bh=cQvDBdivwtDmp1Td9ZWaEf0S4IuZ4hPwaprxSv7XZuE=;
b=AArzbi/1RXhgTnCQBzU6vCwndc0/vqLV9FCgiOTp3deq8kFYhtdJCaEBX9s7iJduV+
HobvLGsbmWU04Y1O3w8m4jyq5H4HJ1jAr1+i0Tf5jl264kmyu4eowOMkwIFo6UaSVQ/a
zP+EYW09fWSSNhljubLkGf62vZ9gD/RF5Awoady6u5/N1GU4GPVCEgsmiK7DmPB2EtSE
7YPz3o9l+kDy8bRnUFw0744B7VKiXrAcIqpfltJuItM4T7bS/jyjYMQbRn8W2MXpyGlI
LNwt3vUNdKtkcPTK54cs44HMaVA8wGCDaMHFP8JmoTKWSsOgZQja3cdEj/rooM8uz+dq
er5g==
X-Received: by 10.99.191.78 with SMTP id i14mr1746749pgo.220.1510176620834;
Wed, 08 Nov 2017 13:30:20 -0800 (PST)
Return-Path: <bugreporter@example.com>
Received: from [10.1.1.4] (d114-72-199-247.hum1.act.optusnet.com.au. [114.72.199.247])
by smtp.gmail.com with ESMTPSA id u131sm8656745pgc.89.2017.11.08.13.30.18
for <djangohelpdesk@example.com>
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Wed, 08 Nov 2017 13:30:19 -0800 (PST)
To: djangohelpdesk@example.com
From: Bug Reporter <bugreporter@example.com>
Subject: example email that crashes django-helpdesk get_email
Message-ID: <8eef2077-8aff-9fb4-0e2a-9876ba2530b1@gmail.com>
Date: Thu, 9 Nov 2017 08:30:15 +1100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
Thunderbird/52.4.0
MIME-Version: 1.0
Content-Type: multipart/signed; micalg=pgp-sha256;
protocol="application/pgp-signature";
boundary="vnaePdRl5oElllhQPTiU2WarPFVGINT69"
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
--vnaePdRl5oElllhQPTiU2WarPFVGINT69
Content-Type: multipart/mixed; boundary="ckOQ1U5bPjO3W1sVnjdBaEigXBiwem2Rn";
protected-headers="v1"
From: Bug Reporter <bugreporter@example.com>
To: djangohelpdesk@example.com
Message-ID: <8eef2077-8aff-9fb4-0e2a-9876ba2530b1@gmail.com>
Subject: example email that crashes django-helpdesk get_email
--ckOQ1U5bPjO3W1sVnjdBaEigXBiwem2Rn
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
Content-Language: en-US
hi, thanks for looking into this :)
https://github.com/django-helpdesk/django-helpdesk/issues/567#issuecommen=
t-342954233
--ckOQ1U5bPjO3W1sVnjdBaEigXBiwem2Rn--
--vnaePdRl5oElllhQPTiU2WarPFVGINT69
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"
-----BEGIN PGP SIGNATURE-----
iQIcBAEBCAAGBQJaA3dnAAoJELBLc7QPITnLN54P/3Zsu7+AIQWDFTvziJfCqswG
u99fG+iWa6ER+iuZG0YU1BdIxIjSKt1pvqB0yXITlT9FCdf1zc0pmeJ08I0a5pVa
iaym5prVUro5BNQ6Vqoo0jvOCKNrACtFNv85zDzXbPNP8TrUss41U+ackPHkOHov
cmJ5YZFQebYXXpibFSIDimVGfwI57vyTWvolttZFLSI1mgGX7MvHaKh253QLdXIo
EUih40rOw3f/nYPEKyW8QA72ImBsZdcZI5buiiCC1bgMkKSFSNAFiIanYEpGNMnO
3zYKBpbpBhnWSi5orwx47/v4/Yb/qVr5ppuV23+YoMfEGT8cHPTAdYpnpE27ByAv
jvpxKEwmkUzD1WxOmQdCcPJPyWz1OBUVvjj0nn0Espnz8V8esl9+IFs739lpFBHu
fWWA315LTmIJMGH5Ujf4myiQeXDo6Gsy6WhE13q7MKTq3tnyi5dJG9GJCBf646dL
RwcDf9O7MvKSV2kSPmryLnUF7D+2fva+Cy+CvJDVJCo5zr4ucXPXZ4htpI6Pjpd5
oPHvbqxSCMJrQ7eAFTYmBNGauSyr0XvGM1qmHBZD/laQEJHYgLT2ILrymZhVDHtK
W7tXhGjMoUvqAxiKkmG3UHFqN4k3EYo13PwoOWyJHD1M9ArbX/Sk9l8DDguCh3DW
a9eiiQ+3V1v+7wWHXCzq
=6JeP
-----END PGP SIGNATURE-----
--vnaePdRl5oElllhQPTiU2WarPFVGINT69--
"""
test_mail_len = len(test_email)
if self.socks:
from socks import ProxyConnectionError
with self.assertRaisesRegexp(ProxyConnectionError, '%s:%s' % (unrouted_socks_server, unused_port)):
call_command('get_email')
else:
# Test local email reading
if self.method == 'local':
with mock.patch('helpdesk.management.commands.get_email.listdir') as mocked_listdir, \
mock.patch('helpdesk.management.commands.get_email.isfile') as mocked_isfile, \
mock.patch('builtins.open' if six.PY3 else '__builtin__.open', mock.mock_open(read_data=test_email)):
mocked_isfile.return_value = True
mocked_listdir.return_value = ['filename1']
call_command('get_email')
mocked_listdir.assert_called_with('/var/lib/mail/helpdesk/')
mocked_isfile.assert_any_call('/var/lib/mail/helpdesk/filename1')
elif self.method == 'pop3':
# mock poplib.POP3's list and retr methods to provide responses as per RFC 1939
pop3_emails = {
'1': ("+OK", test_email.split('\n')),
}
pop3_mail_list = ("+OK 1 message", ("1 %d" % test_mail_len))
mocked_poplib_server = mock.Mock()
mocked_poplib_server.list = mock.Mock(return_value=pop3_mail_list)
mocked_poplib_server.retr = mock.Mock(side_effect=lambda x: pop3_emails['1'])
with mock.patch('helpdesk.management.commands.get_email.poplib', autospec=True) as mocked_poplib:
mocked_poplib.POP3 = mock.Mock(return_value=mocked_poplib_server)
call_command('get_email')
elif self.method == 'imap':
# mock imaplib.IMAP4's search and fetch methods with responses from RFC 3501
imap_emails = {
"1": ("OK", (("1", test_email),)),
}
imap_mail_list = ("OK", ("1",))
mocked_imaplib_server = mock.Mock()
mocked_imaplib_server.search = mock.Mock(return_value=imap_mail_list)
# we ignore the second arg as the data item/mime-part is constant (RFC822)
mocked_imaplib_server.fetch = mock.Mock(side_effect=lambda x, _: imap_emails[x])
with mock.patch('helpdesk.management.commands.get_email.imaplib', autospec=True) as mocked_imaplib:
mocked_imaplib.IMAP4 = mock.Mock(return_value=mocked_imaplib_server)
call_command('get_email')
ticket1 = get_object_or_404(Ticket, pk=1)
self.assertEqual(ticket1.ticket_for_url, "QQ-%s" % ticket1.id)
self.assertEqual(ticket1.title, "example email that crashes django-helpdesk get_email")
self.assertEqual(ticket1.description, """hi, thanks for looking into this :)\n\nhttps://github.com/django-helpdesk/django-helpdesk/issues/567#issuecomment-342954233""")
# MIME part should be attached to follow up
followup1 = get_object_or_404(FollowUp, pk=1)
self.assertEqual(followup1.ticket.id, 1)
attach1 = get_object_or_404(Attachment, pk=1)
self.assertEqual(attach1.followup.id, 1)
self.assertEqual(attach1.filename, 'signature.asc')
self.assertEqual(attach1.file.read(), b"""-----BEGIN PGP SIGNATURE-----
iQIcBAEBCAAGBQJaA3dnAAoJELBLc7QPITnLN54P/3Zsu7+AIQWDFTvziJfCqswG
u99fG+iWa6ER+iuZG0YU1BdIxIjSKt1pvqB0yXITlT9FCdf1zc0pmeJ08I0a5pVa
iaym5prVUro5BNQ6Vqoo0jvOCKNrACtFNv85zDzXbPNP8TrUss41U+ackPHkOHov
cmJ5YZFQebYXXpibFSIDimVGfwI57vyTWvolttZFLSI1mgGX7MvHaKh253QLdXIo
EUih40rOw3f/nYPEKyW8QA72ImBsZdcZI5buiiCC1bgMkKSFSNAFiIanYEpGNMnO
3zYKBpbpBhnWSi5orwx47/v4/Yb/qVr5ppuV23+YoMfEGT8cHPTAdYpnpE27ByAv
jvpxKEwmkUzD1WxOmQdCcPJPyWz1OBUVvjj0nn0Espnz8V8esl9+IFs739lpFBHu
fWWA315LTmIJMGH5Ujf4myiQeXDo6Gsy6WhE13q7MKTq3tnyi5dJG9GJCBf646dL
RwcDf9O7MvKSV2kSPmryLnUF7D+2fva+Cy+CvJDVJCo5zr4ucXPXZ4htpI6Pjpd5
oPHvbqxSCMJrQ7eAFTYmBNGauSyr0XvGM1qmHBZD/laQEJHYgLT2ILrymZhVDHtK
W7tXhGjMoUvqAxiKkmG3UHFqN4k3EYo13PwoOWyJHD1M9ArbX/Sk9l8DDguCh3DW
a9eiiQ+3V1v+7wWHXCzq
=6JeP
-----END PGP SIGNATURE-----
""")
# should this be 'application/pgp-signature'?
# self.assertEqual(attach1.mime_type, 'text/plain')
class GetEmailCCHandling(TestCase): class GetEmailCCHandling(TestCase):
"""TestCase that checks CC handling in email. Needs its own test harness.""" """TestCase that checks CC handling in email. Needs its own test harness."""

View File

@ -29,6 +29,7 @@ class DirectTemplateView(TemplateView):
context[key] = value context[key] = value
return context return context
app_name = 'helpdesk' app_name = 'helpdesk'
urlpatterns = [ urlpatterns = [

View File

@ -168,6 +168,8 @@ def dashboard(request):
'all_tickets_reported_by_current_user': all_tickets_reported_by_current_user, 'all_tickets_reported_by_current_user': all_tickets_reported_by_current_user,
'basic_ticket_stats': basic_ticket_stats, 'basic_ticket_stats': basic_ticket_stats,
}) })
dashboard = staff_member_required(dashboard) dashboard = staff_member_required(dashboard)
@helpdesk_staff_member_required @helpdesk_staff_member_required
@ -183,6 +185,8 @@ def delete_ticket(request, ticket_id):
else: else:
ticket.delete() ticket.delete()
return HttpResponseRedirect(reverse('helpdesk:home')) return HttpResponseRedirect(reverse('helpdesk:home'))
delete_ticket = staff_member_required(delete_ticket) delete_ticket = staff_member_required(delete_ticket)
@ -234,6 +238,8 @@ def followup_edit(request, ticket_id, followup_id):
# delete old followup # delete old followup
followup.delete() followup.delete()
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id])) return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
followup_edit = staff_member_required(followup_edit) followup_edit = staff_member_required(followup_edit)
@ -248,6 +254,8 @@ def followup_delete(request, ticket_id, followup_id):
followup = get_object_or_404(FollowUp, id=followup_id) followup = get_object_or_404(FollowUp, id=followup_id)
followup.delete() followup.delete()
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id])) return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
followup_delete = staff_member_required(followup_delete) followup_delete = staff_member_required(followup_delete)
@ -317,6 +325,8 @@ def view_ticket(request, ticket_id):
'ticketcc_string': ticketcc_string, 'ticketcc_string': ticketcc_string,
'SHOW_SUBSCRIBE': show_subscribe, 'SHOW_SUBSCRIBE': show_subscribe,
}) })
view_ticket = staff_member_required(view_ticket) view_ticket = staff_member_required(view_ticket)
@ -759,6 +769,8 @@ def mass_update(request):
t.delete() t.delete()
return HttpResponseRedirect(reverse('helpdesk:list')) return HttpResponseRedirect(reverse('helpdesk:list'))
mass_update = staff_member_required(mass_update) mass_update = staff_member_required(mass_update)
@ -943,6 +955,8 @@ def ticket_list(request):
saved_query=saved_query, saved_query=saved_query,
search_message=search_message, search_message=search_message,
)) ))
ticket_list = staff_member_required(ticket_list) ticket_list = staff_member_required(ticket_list)
@ -961,6 +975,8 @@ def edit_ticket(request, ticket_id):
form = EditTicketForm(instance=ticket) form = EditTicketForm(instance=ticket)
return render(request, 'helpdesk/edit_ticket.html', {'form': form}) return render(request, 'helpdesk/edit_ticket.html', {'form': form})
edit_ticket = staff_member_required(edit_ticket) edit_ticket = staff_member_required(edit_ticket)
@ -999,6 +1015,8 @@ def create_ticket(request):
form.fields['assigned_to'].widget = forms.HiddenInput() form.fields['assigned_to'].widget = forms.HiddenInput()
return render(request, 'helpdesk/create_ticket.html', {'form': form}) return render(request, 'helpdesk/create_ticket.html', {'form': form})
create_ticket = staff_member_required(create_ticket) create_ticket = staff_member_required(create_ticket)
@ -1021,6 +1039,9 @@ def raw_details(request, type):
raise Http404 raise Http404
raw_details = staff_member_required(raw_details)
@helpdesk_staff_member_required @helpdesk_staff_member_required
def hold_ticket(request, ticket_id, unhold=False): def hold_ticket(request, ticket_id, unhold=False):
ticket = get_object_or_404(Ticket, id=ticket_id) ticket = get_object_or_404(Ticket, id=ticket_id)
@ -1048,14 +1069,22 @@ def hold_ticket(request, ticket_id, unhold=False):
return HttpResponseRedirect(ticket.get_absolute_url()) return HttpResponseRedirect(ticket.get_absolute_url())
hold_ticket = staff_member_required(hold_ticket)
@helpdesk_staff_member_required @helpdesk_staff_member_required
def unhold_ticket(request, ticket_id): def unhold_ticket(request, ticket_id):
return hold_ticket(request, ticket_id, unhold=True) return hold_ticket(request, ticket_id, unhold=True)
unhold_ticket = staff_member_required(unhold_ticket)
@helpdesk_staff_member_required @helpdesk_staff_member_required
def rss_list(request): def rss_list(request):
return render(request, 'helpdesk/rss_list.html', {'queues': Queue.objects.all()}) return render(request, 'helpdesk/rss_list.html', {'queues': Queue.objects.all()})
rss_list = staff_member_required(rss_list) rss_list = staff_member_required(rss_list)
@ -1105,6 +1134,8 @@ def report_index(request):
'basic_ticket_stats': basic_ticket_stats, 'basic_ticket_stats': basic_ticket_stats,
'dash_tickets': dash_tickets, 'dash_tickets': dash_tickets,
}) })
report_index = staff_member_required(report_index) report_index = staff_member_required(report_index)
@ -1138,7 +1169,7 @@ def run_report(request, report):
query_params = json.loads(b64decode(str(saved_query.query)).decode()) query_params = json.loads(b64decode(str(saved_query.query)).decode())
else: else:
query_params = json.loads(b64decode(str(saved_query.query))) query_params = json.loads(b64decode(str(saved_query.query)))
except: except json.JSONDecodeError:
return HttpResponseRedirect(reverse('helpdesk:report_index')) return HttpResponseRedirect(reverse('helpdesk:report_index'))
report_queryset = apply_query(report_queryset, query_params) report_queryset = apply_query(report_queryset, query_params)
@ -1306,6 +1337,8 @@ def run_report(request, report):
'from_saved_query': from_saved_query, 'from_saved_query': from_saved_query,
'saved_query': saved_query, 'saved_query': saved_query,
}) })
run_report = staff_member_required(run_report) run_report = staff_member_required(run_report)
@ -1324,6 +1357,8 @@ def save_query(request):
query.save() query.save()
return HttpResponseRedirect('%s?saved_query=%s' % (reverse('helpdesk:list'), query.id)) return HttpResponseRedirect('%s?saved_query=%s' % (reverse('helpdesk:list'), query.id))
save_query = staff_member_required(save_query) save_query = staff_member_required(save_query)
@ -1336,6 +1371,8 @@ def delete_saved_query(request, id):
return HttpResponseRedirect(reverse('helpdesk:list')) return HttpResponseRedirect(reverse('helpdesk:list'))
else: else:
return render(request, 'helpdesk/confirm_delete_saved_query.html', {'query': query}) return render(request, 'helpdesk/confirm_delete_saved_query.html', {'query': query})
delete_saved_query = staff_member_required(delete_saved_query) delete_saved_query = staff_member_required(delete_saved_query)
@ -1351,6 +1388,8 @@ def user_settings(request):
form = UserSettingsForm(s.settings) form = UserSettingsForm(s.settings)
return render(request, 'helpdesk/user_settings.html', {'form': form}) return render(request, 'helpdesk/user_settings.html', {'form': form})
user_settings = staff_member_required(user_settings) user_settings = staff_member_required(user_settings)
@ -1359,6 +1398,8 @@ def email_ignore(request):
return render(request, 'helpdesk/email_ignore_list.html', { return render(request, 'helpdesk/email_ignore_list.html', {
'ignore_list': IgnoreEmail.objects.all(), 'ignore_list': IgnoreEmail.objects.all(),
}) })
email_ignore = superuser_required(email_ignore) email_ignore = superuser_required(email_ignore)
@ -1373,6 +1414,8 @@ def email_ignore_add(request):
form = EmailIgnoreForm(request.GET) form = EmailIgnoreForm(request.GET)
return render(request, 'helpdesk/email_ignore_add.html', {'form': form}) return render(request, 'helpdesk/email_ignore_add.html', {'form': form})
email_ignore_add = superuser_required(email_ignore_add) email_ignore_add = superuser_required(email_ignore_add)
@ -1384,6 +1427,8 @@ def email_ignore_del(request, id):
return HttpResponseRedirect(reverse('helpdesk:email_ignore')) return HttpResponseRedirect(reverse('helpdesk:email_ignore'))
else: else:
return render(request, 'helpdesk/email_ignore_del.html', {'ignore': ignore}) return render(request, 'helpdesk/email_ignore_del.html', {'ignore': ignore})
email_ignore_del = superuser_required(email_ignore_del) email_ignore_del = superuser_required(email_ignore_del)
@ -1398,6 +1443,8 @@ def ticket_cc(request, ticket_id):
'copies_to': copies_to, 'copies_to': copies_to,
'ticket': ticket, 'ticket': ticket,
}) })
ticket_cc = staff_member_required(ticket_cc) ticket_cc = staff_member_required(ticket_cc)
@ -1423,6 +1470,8 @@ def ticket_cc_add(request, ticket_id):
'form_email': form_email, 'form_email': form_email,
'form_user': form_user, 'form_user': form_user,
}) })
ticket_cc_add = staff_member_required(ticket_cc_add) ticket_cc_add = staff_member_required(ticket_cc_add)
@ -1435,6 +1484,8 @@ def ticket_cc_del(request, ticket_id, cc_id):
return HttpResponseRedirect(reverse('helpdesk:ticket_cc', return HttpResponseRedirect(reverse('helpdesk:ticket_cc',
kwargs={'ticket_id': cc.ticket.id})) kwargs={'ticket_id': cc.ticket.id}))
return render(request, 'helpdesk/ticket_cc_del.html', {'cc': cc}) return render(request, 'helpdesk/ticket_cc_del.html', {'cc': cc})
ticket_cc_del = staff_member_required(ticket_cc_del) ticket_cc_del = staff_member_required(ticket_cc_del)
@ -1457,6 +1508,8 @@ def ticket_dependency_add(request, ticket_id):
'ticket': ticket, 'ticket': ticket,
'form': form, 'form': form,
}) })
ticket_dependency_add = staff_member_required(ticket_dependency_add) ticket_dependency_add = staff_member_required(ticket_dependency_add)
@ -1467,6 +1520,8 @@ def ticket_dependency_del(request, ticket_id, dependency_id):
dependency.delete() dependency.delete()
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id])) return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id]))
return render(request, 'helpdesk/ticket_dependency_del.html', {'dependency': dependency}) return render(request, 'helpdesk/ticket_dependency_del.html', {'dependency': dependency})
ticket_dependency_del = staff_member_required(ticket_dependency_del) ticket_dependency_del = staff_member_required(ticket_dependency_del)
@ -1484,6 +1539,8 @@ def attachment_del(request, ticket_id, attachment_id):
'attachment': attachment, 'attachment': attachment,
'filename': attachment.filename, 'filename': attachment.filename,
}) })
attachment_del = staff_member_required(attachment_del) attachment_del = staff_member_required(attachment_del)

View File

@ -1,5 +1,5 @@
pysocks pysocks
pep8 pycodestyle
codecov codecov
coverage coverage
argparse argparse

View File

@ -2,6 +2,8 @@ Django>=1.11,<2
django-bootstrap-form>=3.3,<4 django-bootstrap-form>=3.3,<4
email-reply-parser email-reply-parser
django-markdown-deux django-markdown-deux
beautifulsoup4
lxml
simplejson simplejson
pytz pytz
six six