diff --git a/README.rst b/README.rst
index 16a25755..49bcab6e 100644
--- a/README.rst
+++ b/README.rst
@@ -57,7 +57,7 @@ Installation
`django-helpdesk` requires:
* Python 3.8+
-* Django 3.2 LTS highly recommended (early adopters may test Django 4)
+* Django 3.2 LTS or Django 4.*
You can quickly install the latest stable version of `django-helpdesk`
app via `pip`::
@@ -78,6 +78,9 @@ Developer Environment
---------------------
Follow these steps to set up your development environment to contribute to helpdesk:
+ - check out the helpdesk app to your local file system::
+ git clone https://github.com/django-helpdesk/django-helpdesk.git
+
- install a virtual environment
- using virtualenv from the helpdesk base folder do::
virtualenv .venv && source .venv/bin/activate
@@ -85,6 +88,12 @@ Follow these steps to set up your development environment to contribute to helpd
- install the requirements for development::
pip install -r requirements.txt -r requirements-dev.txt
+ - install the requirements for testing as well::
+ pip install -r requirements.txt -r requirements-dev.txt -r requirements-testing.txt
+
+To reactivate a VENV just run:
+ source .venv/bin/activate
+
To see option for the Makefile run: `make`
The project enforces a standardized formatting in the CI/CD pipeline. To ensure you have the correct formatting run::
diff --git a/docs/settings.rst b/docs/settings.rst
index d8f5f7af..874afa34 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -37,7 +37,6 @@ If you want to override the default settings for your users, create ``HELPDESK_D
'tickets_per_page': 25
}
-
Generic Options
---------------
These changes are visible throughout django-helpdesk
@@ -86,6 +85,10 @@ These changes are visible throughout django-helpdesk
**Default:** ``HELPDESK_MAX_EMAIL_ATTACHMENT_SIZE = 512000``
+- **VALID_EXTENSIONS** Valid extensions for file types that can be attached to tickets
+
+ **Default:** ``VALID_EXTENSIONS = ['.txt', '.asc', '.htm', '.html', '.pdf', '.doc', '.docx', '.odt', '.jpg', '.png', '.eml']
+
- **QUEUE_EMAIL_BOX_UPDATE_ONLY** Only process mail with a valid tracking ID; all other mail will be ignored instead of creating a new ticket.
**Default:** ``QUEUE_EMAIL_BOX_UPDATE_ONLY = False``
diff --git a/helpdesk/email.py b/helpdesk/email.py
index 68aa06b6..08e14f96 100644
--- a/helpdesk/email.py
+++ b/helpdesk/email.py
@@ -6,8 +6,6 @@ See LICENSE for details.
"""
# import base64
-
-
from bs4 import BeautifulSoup
from datetime import timedelta
from django.conf import settings as django_settings
@@ -18,7 +16,8 @@ from django.db.models import Q
from django.utils import encoding, timezone
from django.utils.translation import gettext as _
import email
-from email.message import Message
+from email import policy
+from email.message import EmailMessage, MIMEPart
from email.utils import getaddresses
from email_reply_parser import EmailReplyParser
from helpdesk import settings
@@ -39,7 +38,7 @@ import ssl
import sys
from time import ctime
import typing
-from typing import List, Tuple
+from typing import List
# import User model, which may be a custom model
@@ -53,6 +52,9 @@ STRIPPED_SUBJECT_STRINGS = [
"Automatic reply: ",
]
+# Allow a custom default attached email name for the HTML formatted email if one is found
+HTML_EMAIL_ATTACHMENT_FILENAME = _("email_html_body.html")
+
def process_email(quiet=False):
for q in Queue.objects.filter(
@@ -75,7 +77,6 @@ def process_email(quiet=False):
logger.propagate = False
if quiet:
logger.propagate = False # do not propagate to root logger that would log to console
-
# 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(
@@ -141,7 +142,7 @@ def pop3_sync(q, logger, server):
full_message = encoding.force_str(
"\n".join(raw_content), errors='replace')
try:
- ticket = object_from_message(message=full_message, queue=q, logger=logger)
+ ticket = extract_email_metadata(message=full_message, queue=q, logger=logger)
except IgnoreTicketException:
logger.warn(
"Message %s was ignored and will be left on POP3 server" % msgNum)
@@ -198,7 +199,7 @@ def imap_sync(q, logger, server):
data = server.fetch(num, '(RFC822)')[1]
full_message = encoding.force_str(data[0][1], errors='replace')
try:
- ticket = object_from_message(message=full_message, queue=q, logger=logger)
+ ticket = extract_email_metadata(message=full_message, queue=q, logger=logger)
except IgnoreTicketException:
logger.warn("Message %s was ignored and will be left on IMAP server" % num)
except DeleteIgnoredTicketException:
@@ -252,13 +253,11 @@ def imap_oauth_sync(q, logger, server):
)
server.debug = settings.HELPDESK_IMAP_DEBUG_LEVEL
-
# TODO: Perhaps store the authentication string template externally? Settings? Queue Table?
server.authenticate(
"XOAUTH2",
lambda x: f"user={q.email_box_user}\x01auth=Bearer {token['access_token']}\x01\x01".encode(),
)
-
# Select the Inbound Mailbox folder
server.select(q.email_box_imap_folder)
@@ -285,7 +284,7 @@ def imap_oauth_sync(q, logger, server):
full_message = encoding.force_str(data[0][1], errors='replace')
try:
- ticket = object_from_message(message=full_message, queue=q, logger=logger)
+ ticket = extract_email_metadata(message=full_message, queue=q, logger=logger)
except IgnoreTicketException as itex:
logger.warn(f"Message {num} was ignored. {itex}")
@@ -312,7 +311,6 @@ def imap_oauth_sync(q, logger, server):
"IMAP retrieve failed. Is the folder '%s' spelled correctly, and does it exist on the server?",
q.email_box_imap_folder
)
-
# Purged Flagged Messages & Logout
server.expunge()
server.close()
@@ -405,7 +403,7 @@ def process_queue(q, logger):
with open(m, 'r') as f:
full_message = encoding.force_str(f.read(), errors='replace')
try:
- ticket = object_from_message(message=full_message, queue=q, logger=logger)
+ ticket = extract_email_metadata(message=full_message, queue=q, logger=logger)
except IgnoreTicketException:
logger.warn("Message %d was ignored and will be left in local directory", i)
except DeleteIgnoredTicketException:
@@ -429,7 +427,7 @@ def process_queue(q, logger):
def decodeUnknown(charset, string):
- if type(string) is not str:
+ if string and not isinstance(string, str):
if not charset:
try:
return str(string, encoding='utf-8', errors='replace')
@@ -468,11 +466,10 @@ def is_autoreply(message):
def create_ticket_cc(ticket, cc_list):
if not cc_list:
return []
-
# Local import to deal with non-defined / circular reference problem
- from helpdesk.views.staff import subscribe_to_ticket_updates, User
new_ticket_ccs = []
+ from helpdesk.views.staff import subscribe_to_ticket_updates, User
for __, cced_email in cc_list:
cced_email = cced_email.strip()
@@ -538,7 +535,6 @@ def create_object_from_email_message(message, ticket_id, payload, files, logger)
ticket.merged_to.ticket)
# Use the ticket in which it was merged to for next operations
ticket = ticket.merged_to
-
# New issue, create a new
" - - payload = ( - '' - '
' - '' - '' - '%s' - '