mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2025-01-15 10:28:37 +01:00
Merge branch 'feature/2__mail_threading' into develop
This commit is contained in:
commit
f08b6d5b96
@ -540,6 +540,16 @@ class TicketCCForm(forms.ModelForm):
|
|||||||
model = TicketCC
|
model = TicketCC
|
||||||
exclude = ('ticket',)
|
exclude = ('ticket',)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
cleaned_data = super(TicketCCForm, self).clean()
|
||||||
|
|
||||||
|
user = cleaned_data.get('user', None)
|
||||||
|
email = cleaned_data.get('email', '')
|
||||||
|
|
||||||
|
if user is None and len(email) == 0:
|
||||||
|
raise forms.ValidationError(_('When you add somebody on Cc, you must provided either an User or a valid email.'))
|
||||||
|
|
||||||
class TicketDependencyForm(forms.ModelForm):
|
class TicketDependencyForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TicketDependency
|
model = TicketDependency
|
||||||
|
@ -22,7 +22,7 @@ logger = logging.getLogger('helpdesk')
|
|||||||
|
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
def send_templated_mail(template_name, email_context, recipients, sender=None, bcc=None, fail_silently=False, files=None):
|
def send_templated_mail(template_name, email_context, recipients, sender=None, bcc=None, fail_silently=False, files=None, extra_headers={}):
|
||||||
"""
|
"""
|
||||||
send_templated_mail() is a warpper around Django's e-mail routines that
|
send_templated_mail() is a warpper around Django's e-mail routines that
|
||||||
allows us to easily send multipart (text/plain & text/html) e-mails using
|
allows us to easily send multipart (text/plain & text/html) e-mails using
|
||||||
@ -82,7 +82,7 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
|
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
|
||||||
except EmailTemplate.DoesNotExist:
|
except EmailTemplate.DoesNotExist:
|
||||||
logger.warning('template "%s" does not exist, no mail sent' %
|
logger.warning('template "%s" does not exist, no mail sent' %
|
||||||
template_name)
|
template_name)
|
||||||
return # just ignore if template doesn't exist
|
return # just ignore if template doesn't exist
|
||||||
|
|
||||||
if not sender:
|
if not sender:
|
||||||
@ -133,7 +133,9 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
text_part,
|
text_part,
|
||||||
sender,
|
sender,
|
||||||
recipients,
|
recipients,
|
||||||
bcc=bcc)
|
bcc=bcc,
|
||||||
|
headers=extra_headers,
|
||||||
|
)
|
||||||
msg.attach_alternative(html_part, "text/html")
|
msg.attach_alternative(html_part, "text/html")
|
||||||
|
|
||||||
if files:
|
if files:
|
||||||
|
@ -36,7 +36,7 @@ except ImportError:
|
|||||||
from datetime import datetime as timezone
|
from datetime import datetime as timezone
|
||||||
|
|
||||||
from helpdesk.lib import send_templated_mail, safe_template_context
|
from helpdesk.lib import send_templated_mail, safe_template_context
|
||||||
from helpdesk.models import Queue, Ticket, FollowUp, Attachment, IgnoreEmail
|
from helpdesk.models import Queue, Ticket, TicketCC, FollowUp, Attachment, IgnoreEmail
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -124,7 +124,7 @@ def process_queue(q, quiet=False):
|
|||||||
msgSize = msg.split(" ")[1]
|
msgSize = msg.split(" ")[1]
|
||||||
|
|
||||||
full_message = "\n".join(server.retr(msgNum)[1])
|
full_message = "\n".join(server.retr(msgNum)[1])
|
||||||
ticket = ticket_from_message(message=full_message, queue=q, quiet=quiet)
|
ticket = object_from_message(message=full_message, queue=q, quiet=quiet)
|
||||||
|
|
||||||
if ticket:
|
if ticket:
|
||||||
server.dele(msgNum)
|
server.dele(msgNum)
|
||||||
@ -147,7 +147,7 @@ def process_queue(q, quiet=False):
|
|||||||
msgnums = data[0].split()
|
msgnums = data[0].split()
|
||||||
for num in msgnums:
|
for num in msgnums:
|
||||||
status, data = server.fetch(num, '(RFC822)')
|
status, data = server.fetch(num, '(RFC822)')
|
||||||
ticket = ticket_from_message(message=data[0][1], queue=q, quiet=quiet)
|
ticket = object_from_message(message=data[0][1], queue=q, quiet=quiet)
|
||||||
if ticket:
|
if ticket:
|
||||||
server.store(num, '+FLAGS', '\\Deleted')
|
server.store(num, '+FLAGS', '\\Deleted')
|
||||||
|
|
||||||
@ -168,10 +168,208 @@ def decode_mail_headers(string):
|
|||||||
decoded = decode_header(string)
|
decoded = decode_header(string)
|
||||||
return u' '.join([unicode(msg, charset or 'utf-8') for msg, charset in decoded])
|
return u' '.join([unicode(msg, charset or 'utf-8') for msg, charset in decoded])
|
||||||
|
|
||||||
def ticket_from_message(message, queue, quiet):
|
def create_ticket_cc(ticket, cc_list):
|
||||||
|
|
||||||
|
# Local import to deal with non-defined / circular reference problem
|
||||||
|
from helpdesk.views.staff import User, subscribe_to_ticket_updates
|
||||||
|
|
||||||
|
new_ticket_ccs = []
|
||||||
|
for cced_email in cc_list:
|
||||||
|
|
||||||
|
user = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=cced_email)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ticket_cc = subscribe_to_ticket_updates(ticket=ticket, user=user, email=cced_email)
|
||||||
|
new_ticket_ccs.append(ticket_cc)
|
||||||
|
|
||||||
|
return new_ticket_ccs
|
||||||
|
|
||||||
|
def create_object_from_email_message(message, ticket_id, payload, files, quiet):
|
||||||
|
|
||||||
|
ticket, previous_followup, new = None, None, False
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
queue = payload['queue']
|
||||||
|
sender_email = payload['sender_email']
|
||||||
|
|
||||||
|
message_id = message.get('Message-Id')
|
||||||
|
in_reply_to = message.get('In-Reply-To')
|
||||||
|
cc_list = message.get('Cc')
|
||||||
|
|
||||||
|
if in_reply_to is not None:
|
||||||
|
try:
|
||||||
|
queryset = FollowUp.objects.filter(message_id=in_reply_to).order_by('-date')
|
||||||
|
if queryset.count() > 0:
|
||||||
|
previous_followup = queryset.first()
|
||||||
|
ticket = previous_followup.ticket
|
||||||
|
except FollowUp.DoesNotExist:
|
||||||
|
pass #play along. The header may be wrong
|
||||||
|
|
||||||
|
if previous_followup is None and ticket_id is not None:
|
||||||
|
try:
|
||||||
|
ticket = Ticket.objects.get(id=ticket_id)
|
||||||
|
new = False
|
||||||
|
except Ticket.DoesNotExist:
|
||||||
|
ticket = None
|
||||||
|
|
||||||
|
# New issue, create a new <Ticket> instance
|
||||||
|
if ticket is None:
|
||||||
|
ticket = Ticket.objects.create(
|
||||||
|
title = payload['subject'],
|
||||||
|
queue = queue,
|
||||||
|
submitter_email = sender_email,
|
||||||
|
created = now,
|
||||||
|
description = payload['body'],
|
||||||
|
priority = payload['priority'],
|
||||||
|
)
|
||||||
|
ticket.save()
|
||||||
|
|
||||||
|
new = True
|
||||||
|
update = ''
|
||||||
|
|
||||||
|
# Old issue being re-openned
|
||||||
|
elif ticket.status == Ticket.CLOSED_STATUS:
|
||||||
|
ticket.status = Ticket.REOPENED_STATUS
|
||||||
|
ticket.save()
|
||||||
|
|
||||||
|
f = FollowUp(
|
||||||
|
ticket = ticket,
|
||||||
|
title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
|
||||||
|
date = now,
|
||||||
|
public = True,
|
||||||
|
comment = payload['body'],
|
||||||
|
message_id = message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ticket.status == Ticket.REOPENED_STATUS:
|
||||||
|
f.new_status = Ticket.REOPENED_STATUS
|
||||||
|
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
||||||
|
|
||||||
|
f.save()
|
||||||
|
|
||||||
|
if not quiet:
|
||||||
|
print (" [%s-%s] %s" % (ticket.queue.slug, ticket.id, ticket.title,)).encode('ascii', 'replace')
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file['content']:
|
||||||
|
filename = file['filename'].encode('ascii', 'replace').replace(' ', '_')
|
||||||
|
filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)
|
||||||
|
a = Attachment(
|
||||||
|
followup=f,
|
||||||
|
filename=filename,
|
||||||
|
mime_type=file['type'],
|
||||||
|
size=len(file['content']),
|
||||||
|
)
|
||||||
|
a.file.save(filename, ContentFile(file['content']), save=False)
|
||||||
|
a.save()
|
||||||
|
if not quiet:
|
||||||
|
print " - %s" % filename
|
||||||
|
|
||||||
|
|
||||||
|
context = safe_template_context(ticket)
|
||||||
|
|
||||||
|
new_ticket_ccs = []
|
||||||
|
if cc_list is not None:
|
||||||
|
new_ticket_ccs = create_ticket_cc(ticket, cc_list.split(','))
|
||||||
|
|
||||||
|
notification_template = None
|
||||||
|
|
||||||
|
if new:
|
||||||
|
|
||||||
|
notification_template = 'newticket_cc'
|
||||||
|
|
||||||
|
if sender_email:
|
||||||
|
send_templated_mail(
|
||||||
|
'newticket_submitter',
|
||||||
|
context,
|
||||||
|
recipients=sender_email,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
extra_headers={'In-Reply-To': message_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
if queue.new_ticket_cc:
|
||||||
|
|
||||||
|
send_templated_mail(
|
||||||
|
'newticket_cc',
|
||||||
|
context,
|
||||||
|
recipients=queue.new_ticket_cc,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
extra_headers={'In-Reply-To': message_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
||||||
|
send_templated_mail(
|
||||||
|
'newticket_cc',
|
||||||
|
context,
|
||||||
|
recipients=queue.updated_ticket_cc,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
extra_headers={'In-Reply-To': message_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
notification_template = 'updated_cc'
|
||||||
|
|
||||||
|
context.update(comment=f.comment)
|
||||||
|
|
||||||
|
if ticket.status == Ticket.REOPENED_STATUS:
|
||||||
|
update = _(' (Reopened)')
|
||||||
|
else:
|
||||||
|
update = _(' (Updated)')
|
||||||
|
|
||||||
|
if ticket.assigned_to:
|
||||||
|
send_templated_mail(
|
||||||
|
'updated_owner',
|
||||||
|
context,
|
||||||
|
recipients=ticket.assigned_to.email,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if queue.updated_ticket_cc:
|
||||||
|
send_templated_mail(
|
||||||
|
'updated_cc',
|
||||||
|
context,
|
||||||
|
recipients=queue.updated_ticket_cc,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
notifications_to_be_sent = []
|
||||||
|
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)
|
||||||
|
|
||||||
|
if len(notifications_to_be_sent):
|
||||||
|
|
||||||
|
send_templated_mail(
|
||||||
|
notification_template,
|
||||||
|
context,
|
||||||
|
recipients=notifications_to_be_sent,
|
||||||
|
sender=queue.from_address,
|
||||||
|
fail_silently=True,
|
||||||
|
extra_headers={'In-Reply-To': message_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
return ticket
|
||||||
|
|
||||||
|
|
||||||
|
def object_from_message(message, queue, quiet):
|
||||||
# 'message' must be an RFC822 formatted message.
|
# 'message' must be an RFC822 formatted message.
|
||||||
|
|
||||||
msg = message
|
msg = message
|
||||||
|
|
||||||
|
#import ipdb;ipdb.set_trace()
|
||||||
message = email.message_from_string(msg)
|
message = email.message_from_string(msg)
|
||||||
|
|
||||||
subject = message.get('subject', _('Created from e-mail'))
|
subject = message.get('subject', _('Created from e-mail'))
|
||||||
subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
|
subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
|
||||||
subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip()
|
subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip()
|
||||||
@ -194,9 +392,9 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject)
|
matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject)
|
||||||
if matchobj:
|
if matchobj:
|
||||||
# This is a reply or forward.
|
# This is a reply or forward.
|
||||||
ticket = matchobj.group('id')
|
ticket_id = matchobj.group('id')
|
||||||
else:
|
else:
|
||||||
ticket = None
|
ticket_id = None
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
files = []
|
files = []
|
||||||
@ -239,14 +437,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
'type': 'text/html',
|
'type': 'text/html',
|
||||||
})
|
})
|
||||||
|
|
||||||
now = timezone.now()
|
|
||||||
|
|
||||||
if ticket:
|
|
||||||
try:
|
|
||||||
t = Ticket.objects.get(id=ticket)
|
|
||||||
new = False
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
ticket = None
|
|
||||||
|
|
||||||
priority = 3
|
priority = 3
|
||||||
|
|
||||||
@ -258,115 +449,17 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
|
if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
|
||||||
priority = 2
|
priority = 2
|
||||||
|
|
||||||
if ticket == None:
|
payload = {
|
||||||
t = Ticket(
|
'body': body,
|
||||||
title=subject,
|
'subject': subject,
|
||||||
queue=queue,
|
'queue': queue,
|
||||||
submitter_email=sender_email,
|
'sender_email': sender_email,
|
||||||
created=now,
|
'priority': priority,
|
||||||
description=body,
|
'files': files,
|
||||||
priority=priority,
|
}
|
||||||
)
|
|
||||||
t.save()
|
|
||||||
new = True
|
|
||||||
update = ''
|
|
||||||
|
|
||||||
elif t.status == Ticket.CLOSED_STATUS:
|
|
||||||
t.status = Ticket.REOPENED_STATUS
|
|
||||||
t.save()
|
|
||||||
|
|
||||||
f = FollowUp(
|
|
||||||
ticket = t,
|
|
||||||
title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
|
|
||||||
date = timezone.now(),
|
|
||||||
public = True,
|
|
||||||
comment = body,
|
|
||||||
)
|
|
||||||
|
|
||||||
if t.status == Ticket.REOPENED_STATUS:
|
|
||||||
f.new_status = Ticket.REOPENED_STATUS
|
|
||||||
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
|
||||||
|
|
||||||
f.save()
|
|
||||||
|
|
||||||
if not quiet:
|
|
||||||
print (" [%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace')
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
if file['content']:
|
|
||||||
filename = file['filename'].encode('ascii', 'replace').replace(' ', '_')
|
|
||||||
filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)
|
|
||||||
a = Attachment(
|
|
||||||
followup=f,
|
|
||||||
filename=filename,
|
|
||||||
mime_type=file['type'],
|
|
||||||
size=len(file['content']),
|
|
||||||
)
|
|
||||||
a.file.save(filename, ContentFile(file['content']), save=False)
|
|
||||||
a.save()
|
|
||||||
if not quiet:
|
|
||||||
print " - %s" % filename
|
|
||||||
|
|
||||||
|
|
||||||
context = safe_template_context(t)
|
return create_object_from_email_message(message, ticket_id, payload, files, quiet=quiet)
|
||||||
|
|
||||||
if new:
|
|
||||||
|
|
||||||
if sender_email:
|
|
||||||
send_templated_mail(
|
|
||||||
'newticket_submitter',
|
|
||||||
context,
|
|
||||||
recipients=sender_email,
|
|
||||||
sender=queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if queue.new_ticket_cc:
|
|
||||||
send_templated_mail(
|
|
||||||
'newticket_cc',
|
|
||||||
context,
|
|
||||||
recipients=queue.new_ticket_cc,
|
|
||||||
sender=queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
|
||||||
send_templated_mail(
|
|
||||||
'newticket_cc',
|
|
||||||
context,
|
|
||||||
recipients=queue.updated_ticket_cc,
|
|
||||||
sender=queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
context.update(comment=f.comment)
|
|
||||||
|
|
||||||
if t.status == Ticket.REOPENED_STATUS:
|
|
||||||
update = _(' (Reopened)')
|
|
||||||
else:
|
|
||||||
update = _(' (Updated)')
|
|
||||||
|
|
||||||
if t.assigned_to:
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_owner',
|
|
||||||
context,
|
|
||||||
recipients=t.assigned_to.email,
|
|
||||||
sender=queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if queue.updated_ticket_cc:
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_cc',
|
|
||||||
context,
|
|
||||||
recipients=queue.updated_ticket_cc,
|
|
||||||
sender=queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process_email()
|
process_email()
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.1 on 2016-02-07 19:51
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('helpdesk', '0011_admin_related_improvements'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='submitter_email_id',
|
||||||
|
field=models.CharField(blank=True, editable=False, help_text="The Message ID of the submitter's email.", max_length=256, null=True, verbose_name='Submitter E-Mail ID'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.1 on 2016-02-16 18:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('helpdesk', '0012_add_submitter_email_id_field_to_ticket'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='submitter_email_id',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='followup',
|
||||||
|
name='message_id',
|
||||||
|
field=models.CharField(blank=True, editable=False, help_text="The Message ID of the submitter's email.", max_length=256, null=True, verbose_name='E-Mail ID'),
|
||||||
|
),
|
||||||
|
]
|
@ -428,6 +428,24 @@ class Ticket(models.Model):
|
|||||||
'automatically by management/commands/escalate_tickets.py.'),
|
'automatically by management/commands/escalate_tickets.py.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Separate RFC 2822 (email) exclusive fields for later processing
|
||||||
|
self.rfc_2822_items = {}
|
||||||
|
|
||||||
|
for field, value in kwargs.iteritems():
|
||||||
|
if field.startswith('rfc_2822'):
|
||||||
|
self.rfc_2822_items[field] = value
|
||||||
|
|
||||||
|
# Submitter Message-Id is an exception here, since it's a <Ticket> attribute
|
||||||
|
if 'rfc_2822_submitter_email_id' in kwargs:
|
||||||
|
kwargs['submitter_email_id'] = kwargs['rfc_2822_submitter_email_id']
|
||||||
|
|
||||||
|
for field in self.rfc_2822_items.iterkeys():
|
||||||
|
kwargs.pop(field)
|
||||||
|
|
||||||
|
super(Ticket, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _get_assigned_to(self):
|
def _get_assigned_to(self):
|
||||||
""" Custom property to allow us to easily print 'Unassigned' if a
|
""" Custom property to allow us to easily print 'Unassigned' if a
|
||||||
ticket has no owner, or the users name if it's assigned. If the user
|
ticket has no owner, or the users name if it's assigned. If the user
|
||||||
@ -540,7 +558,7 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return ('helpdesk_view', (self.id,))
|
return ('helpdesk_view', (self.id,))
|
||||||
get_absolute_url = models.permalink(get_absolute_url)
|
get_absolute_url = models.permalink(get_absolute_url)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.id:
|
if not self.id:
|
||||||
@ -623,6 +641,15 @@ class FollowUp(models.Model):
|
|||||||
help_text=_('If the status was changed, what was it changed to?'),
|
help_text=_('If the status was changed, what was it changed to?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
message_id = models.CharField(
|
||||||
|
_('E-Mail ID'),
|
||||||
|
max_length=256,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text=_("The Message ID of the submitter's email."),
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
objects = FollowUpManager()
|
objects = FollowUpManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
from helpdesk.models import Queue, CustomField, Ticket
|
|
||||||
|
import email
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from helpdesk.models import Queue, CustomField, FollowUp, Ticket, TicketCC
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.forms import ValidationError
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from helpdesk.management.commands.get_email import object_from_message, create_ticket_cc
|
||||||
|
|
||||||
try: # python 3
|
try: # python 3
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
except ImportError: # python 2
|
except ImportError: # python 2
|
||||||
@ -24,13 +32,385 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
|
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
def test_create_ticket_direct(self):
|
def test_create_ticket_instance_from_payload(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that a <Ticket> instance is created whenever an email is sent to a public queue.
|
||||||
|
"""
|
||||||
|
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
ticket_data = dict(queue=self.queue_public, **self.ticket_data)
|
ticket_data = dict(queue=self.queue_public, **self.ticket_data)
|
||||||
ticket = Ticket.objects.create(**ticket_data)
|
ticket = Ticket.objects.create(**ticket_data)
|
||||||
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
self.assertEqual(email_count, len(mail.outbox))
|
self.assertEqual(email_count, len(mail.outbox))
|
||||||
|
|
||||||
|
def test_create_ticket_from_email_with_message_id(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that a <Ticket> instance is created whenever an email is sent to a public queue.
|
||||||
|
Also, make sure that the RFC 2822 field "message-id" is stored on the <Ticket.submitter_email_id>
|
||||||
|
field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
#print email_count
|
||||||
|
#for m in mail.outbox:
|
||||||
|
# print m.to, m.subject
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1)
|
||||||
|
# and the new and update queues (+2)
|
||||||
|
self.assertEqual(email_count + 1 + 2, len(mail.outbox))
|
||||||
|
|
||||||
|
def test_create_ticket_from_email_without_message_id(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that a <Ticket> instance is created whenever an email is sent to a public queue.
|
||||||
|
Also, make sure that the RFC 2822 field "message-id" is stored on the <Ticket.submitter_email_id>
|
||||||
|
field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = email.message.Message()
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
ticket = Ticket.objects.get(title=self.ticket_data['title'], queue=self.queue_public, submitter_email=submitter_email)
|
||||||
|
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1)
|
||||||
|
# and the new and update queues (+2)
|
||||||
|
self.assertEqual(email_count + 1 + 2, len(mail.outbox))
|
||||||
|
|
||||||
|
def test_create_ticket_from_email_with_carbon_copy(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that an instance of <TicketCC> is created for every valid element of the
|
||||||
|
"rfc_2822_cc" field when creating a <Ticket> instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1),
|
||||||
|
# the new and update queues (+2) and contacts on the cc_list (+1 as it's
|
||||||
|
# treated as a list)
|
||||||
|
self.assertEqual(email_count + 1 + 2 + 1, len(mail.outbox))
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
|
||||||
|
def test_create_ticket_from_email_with_invalid_carbon_copy(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that no <TicketCC> instance is created if an invalid element of the
|
||||||
|
"rfc_2822_cc" field is provided when creating a <Ticket> instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['null@example', 'invalid@foobar']
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
self.assertRaises(ValidationError, object_from_message, str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
def test_create_followup_from_email_with_valid_message_id_with_when_no_initial_cc_list(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that if a message is received with an valid In-Reply-To ID,
|
||||||
|
the expected <TicketCC> instances are created even if the there were
|
||||||
|
no <TicketCC>s so far.
|
||||||
|
"""
|
||||||
|
|
||||||
|
### Ticket and TicketCCs creation ###
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
### end of the Ticket and TicketCCs creation ###
|
||||||
|
|
||||||
|
# Reply message
|
||||||
|
reply = email.message.Message()
|
||||||
|
|
||||||
|
reply_message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
reply.__setitem__('Message-ID', reply_message_id)
|
||||||
|
reply.__setitem__('In-Reply-To', message_id)
|
||||||
|
reply.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
reply.__setitem__('From', submitter_email)
|
||||||
|
reply.__setitem__('To', self.queue_public.email_address)
|
||||||
|
reply.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
reply.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
reply.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
object_from_message(str(reply), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
# Even after 2 messages with the same cc_list, <get> MUST return only
|
||||||
|
# one object
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1)
|
||||||
|
# and the new and update queues (+2)
|
||||||
|
expected_email_count = 1 + 2
|
||||||
|
|
||||||
|
# As an update was made, we increase the expected_email_count with:
|
||||||
|
# cc_list: +1
|
||||||
|
# public_update_queue: +1
|
||||||
|
expected_email_count += 1 + 1
|
||||||
|
self.assertEqual(expected_email_count, len(mail.outbox))
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_followup_from_email_with_valid_message_id_with_original_cc_list_included(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that if a message is received with an valid In-Reply-To ID,
|
||||||
|
the expected <TicketCC> instances are created but if there's any
|
||||||
|
overlap with the previous Cc list, no duplicates are created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
### Ticket and TicketCCs creation ###
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
self.assertTrue(ticket_cc.can_view, True)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1),
|
||||||
|
# the new and update queues (+2) and contacts on the cc_list (+1 as it's
|
||||||
|
# treated as a list)
|
||||||
|
self.assertEqual(email_count + 1 + 2 + 1, len(mail.outbox))
|
||||||
|
### end of the Ticket and TicketCCs creation ###
|
||||||
|
|
||||||
|
# Reply message
|
||||||
|
reply = email.message.Message()
|
||||||
|
|
||||||
|
reply_message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
reply.__setitem__('Message-ID', reply_message_id)
|
||||||
|
reply.__setitem__('In-Reply-To', message_id)
|
||||||
|
reply.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
reply.__setitem__('From', submitter_email)
|
||||||
|
reply.__setitem__('To', self.queue_public.email_address)
|
||||||
|
reply.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
reply.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
reply.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
object_from_message(str(reply), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
# Even after 2 messages with the same cc_list,
|
||||||
|
# <get> MUST return only one object
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1),
|
||||||
|
# the new and update queues (+2) and contacts on the cc_list (+1 as it's
|
||||||
|
# treated as a list)
|
||||||
|
expected_email_count = 1 + 2 + 1
|
||||||
|
|
||||||
|
# As an update was made, we increase the expected_email_count with:
|
||||||
|
# cc_list: +1
|
||||||
|
# public_update_queue: +1
|
||||||
|
expected_email_count += 1 + 1
|
||||||
|
self.assertEqual(expected_email_count, len(mail.outbox))
|
||||||
|
|
||||||
|
def test_create_followup_from_email_with_invalid_message_id(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ensure that if a message is received with an invalid In-Reply-To ID and we
|
||||||
|
can infer the original Ticket ID by the message's subject, the expected
|
||||||
|
<TicketCC> instances are created
|
||||||
|
"""
|
||||||
|
|
||||||
|
### Ticket and TicketCCs creation ###
|
||||||
|
msg = email.message.Message()
|
||||||
|
|
||||||
|
message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
msg.__setitem__('Message-ID', message_id)
|
||||||
|
msg.__setitem__('Subject', self.ticket_data['title'])
|
||||||
|
msg.__setitem__('From', submitter_email)
|
||||||
|
msg.__setitem__('To', self.queue_public.email_address)
|
||||||
|
msg.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
msg.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
msg.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(msg), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
self.assertTrue(ticket_cc.can_view, True)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1),
|
||||||
|
# the new and update queues (+2) and contacts on the cc_list (+1 as it's
|
||||||
|
# treated as a list)
|
||||||
|
self.assertEqual(email_count + 1 + 2 + 1, len(mail.outbox))
|
||||||
|
### end of the Ticket and TicketCCs creation ###
|
||||||
|
|
||||||
|
# Reply message
|
||||||
|
reply = email.message.Message()
|
||||||
|
|
||||||
|
reply_message_id = uuid.uuid4().hex
|
||||||
|
submitter_email = 'foo@bar.py'
|
||||||
|
cc_list = ['bravo@example.net', 'charlie@foobar.com']
|
||||||
|
|
||||||
|
invalid_message_id = 'INVALID'
|
||||||
|
reply_subject = 'Re: ' + self.ticket_data['title']
|
||||||
|
|
||||||
|
reply.__setitem__('Message-ID', reply_message_id)
|
||||||
|
reply.__setitem__('In-Reply-To', invalid_message_id)
|
||||||
|
reply.__setitem__('Subject', reply_subject)
|
||||||
|
reply.__setitem__('From', submitter_email)
|
||||||
|
reply.__setitem__('To', self.queue_public.email_address)
|
||||||
|
reply.__setitem__('Cc', ','.join(cc_list))
|
||||||
|
reply.__setitem__('Content-Type', 'text/plain;')
|
||||||
|
reply.set_payload(self.ticket_data['description'])
|
||||||
|
|
||||||
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
|
object_from_message(str(reply), self.queue_public, quiet=True)
|
||||||
|
|
||||||
|
followup = FollowUp.objects.get(message_id=message_id)
|
||||||
|
ticket = Ticket.objects.get(id=followup.ticket.id)
|
||||||
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
|
|
||||||
|
# Ensure that <TicketCC> is created
|
||||||
|
for cc_email in cc_list:
|
||||||
|
# Even after 2 messages with the same cc_list, <get> MUST return only
|
||||||
|
# one object
|
||||||
|
ticket_cc = TicketCC.objects.get(ticket=ticket, email=cc_email)
|
||||||
|
self.assertTrue(ticket_cc.ticket, ticket)
|
||||||
|
self.assertTrue(ticket_cc.email, cc_email)
|
||||||
|
|
||||||
|
# As we have created an Ticket from an email, we notify the sender (+1),
|
||||||
|
# the new and update queues (+2) and contacts on the cc_list (+1 as it's
|
||||||
|
# treated as a list)
|
||||||
|
self.assertEqual(email_count + 1 + 2 + 1, len(mail.outbox))
|
||||||
|
|
||||||
|
|
||||||
def test_create_ticket_public(self):
|
def test_create_ticket_public(self):
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
|
@ -338,16 +338,39 @@ def return_ticketccstring_and_show_subscribe(user, ticket):
|
|||||||
|
|
||||||
return ticketcc_string, SHOW_SUBSCRIBE
|
return ticketcc_string, SHOW_SUBSCRIBE
|
||||||
|
|
||||||
|
def subscribe_to_ticket_updates(ticket, user=None, email=None, can_view=True, can_update=False):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'user': user,
|
||||||
|
'email': email,
|
||||||
|
'can_view': can_view,
|
||||||
|
'can_update': can_update
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket_cc_form = TicketCCForm(data)
|
||||||
|
if ticket is not None and ticket_cc_form.is_valid():
|
||||||
|
|
||||||
|
queryset = TicketCC.objects.filter(ticket=ticket, user=user, email=email)
|
||||||
|
|
||||||
|
if queryset.count() > 0:
|
||||||
|
return queryset.first()
|
||||||
|
|
||||||
|
ticketcc = ticket_cc_form.save(commit=False)
|
||||||
|
ticketcc.ticket = ticket
|
||||||
|
ticketcc.save()
|
||||||
|
return ticketcc
|
||||||
|
else:
|
||||||
|
raise ValidationError(
|
||||||
|
_('Could not create subscribe contact to ticket updated. Errors: {}'.format(ticket_cc_form.errors))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def subscribe_staff_member_to_ticket(ticket, user, email=''):
|
||||||
|
|
||||||
def subscribe_staff_member_to_ticket(ticket, user):
|
|
||||||
''' used in view_ticket() and update_ticket() '''
|
''' used in view_ticket() and update_ticket() '''
|
||||||
ticketcc = TicketCC()
|
|
||||||
ticketcc.ticket = ticket
|
|
||||||
ticketcc.user = user
|
|
||||||
ticketcc.can_view = True
|
|
||||||
ticketcc.can_update = True
|
|
||||||
ticketcc.save()
|
|
||||||
|
|
||||||
|
return subscribe_to_ticket_updates(ticket=ticket, user=user, email=email, can_view=can_view, can_update=can_update)
|
||||||
|
|
||||||
|
|
||||||
def update_ticket(request, ticket_id, public=False):
|
def update_ticket(request, ticket_id, public=False):
|
||||||
if not (public or (request.user.is_authenticated() and request.user.is_active and (request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE))):
|
if not (public or (request.user.is_authenticated() and request.user.is_active and (request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE))):
|
||||||
|
Loading…
Reference in New Issue
Block a user