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)): return HttpResponseForbidden(_('Sorry, you need to login to do that.')) ticket = get_object_or_404(Ticket, id=ticket_id) comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) title = request.POST.get('title', '') public = request.POST.get('public', False) owner = int(request.POST.get('owner', None)) priority = int(request.POST.get('priority', ticket.priority)) tags = request.POST.get('tags', '') # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) if request.user.is_staff: f.user = request.user f.public = public reassigned = False if owner is not None: if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): new_user = User.objects.get(id=owner) f.title = _('Assigned to %(username)s') % { 'username': new_user.username, } ticket.assigned_to = new_user reassigned = True elif owner == 0 and ticket.assigned_to is not None: f.title = _('Unassigned') ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += ' and %s' % ticket.get_status_display() else: f.title = '%s' % ticket.get_status_display() if not f.title: if f.comment: f.title = _('Comment') else: f.title = _('Updated') f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist('attachment'): filename = file.name.replace(' ', '_') a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange( followup=f, field=_('Title'), old_value=ticket.title, new_value=title, ) c.save() ticket.title = title if priority != ticket.priority: c = TicketChange( followup=f, field=_('Priority'), old_value=ticket.priority, new_value=priority, ) c.save() ticket.priority = priority if HAS_TAG_SUPPORT: if tags != ticket.tags: c = TicketChange( followup=f, field=_('Tags'), old_value=ticket.tags, new_value=tags, ) c.save() ticket.tags = tags if f.new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment messages_sent_to = [] # ticket might have changed above, so we re-instantiate context with the # (possibly) updated ticket. context = safe_template_context(ticket) context.update( resolution=ticket.resolution, comment=f.comment, ) if ticket.submitter_email and public and (f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if f.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_submitter' elif f.new_status == Ticket.CLOSED_STATUS: template = 'closed_submitter' else: template = 'updated_submitter' send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = 'assigned_owner' elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_owner' elif f.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_owner' else: template_staff = 'updated_owner' if (not reassigned or ( reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_assign', False))) or (not reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_change', False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif f.new_status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() if request.user.is_staff: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url)
def ticket_from_message(message, queue, quiet): # 'message' must be an RFC822 formatted message. msg = message message = email.message_from_string(msg) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) subject = subject.replace("Re: ", "").replace("Fw: ", "").replace( "RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = parseaddr(sender)[1] body_plain, body_html = '', '' for ignore in IgnoreEmail.objects.filter( Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') else: ticket = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name == None: if part.get_content_subtype() == 'plain': body_plain = EmailReplyParser.parse_reply( decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))) else: body_html = part.get_payload(decode=True) else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append( { 'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type() }, ) counter += 1 if body_plain: body = body_plain else: body = _( 'No plain-text email body available. Please see attachment email_html_body.html.' ) if body_html: files.append({ 'filename': _("email_html_body.html"), 'content': body_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 smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = ('high', 'important', '1', 'urgent') if smtp_priority in high_priority_types or smtp_importance in high_priority_types: priority = 2 if ticket == None: # trim subject if len > 200 if len(subject) > 200: subject = subject[:197] + "..." t = Ticket( title=subject, queue=queue, submitter_email=sender_email, created=now, description=body, 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) 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
def create_object_from_email_message(message, ticket_id, payload, files, logger): ticket, previous_followup, new = None, None, False now = timezone.now() queue = payload['queue'] sender_email = payload['sender_email'] to_list = getaddresses(message.get_all('To', [])) cc_list = getaddresses(message.get_all('Cc', [])) message_id = message.get('Message-Id') in_reply_to = message.get('In-Reply-To') 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) except Ticket.DoesNotExist: ticket = None else: new = False # Check if the ticket has been merged to another ticket if ticket.merged_to: logger.info("Ticket has been merged to %s" % 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 <Ticket> instance if ticket is None: if not settings.QUEUE_EMAIL_BOX_UPDATE_ONLY: ticket = Ticket.objects.create( title=payload['subject'], queue=queue, submitter_email=sender_email, created=now, description=payload['body'], priority=payload['priority'], ) ticket.save() logger.debug("Created new ticket %s-%s" % (ticket.queue.slug, ticket.id)) new = True update = '' # Old issue being re-opened elif ticket.status == Ticket.CLOSED_STATUS: 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() logger.debug("Created new FollowUp for Ticket") logger.info("[%s-%s] %s" % ( ticket.queue.slug, ticket.id, ticket.title, )) attached = process_attachments(f, files) for att_file in attached: logger.info( "Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size)) context = safe_template_context(ticket) new_ticket_ccs = [] new_ticket_ccs.append(create_ticket_cc(ticket, to_list + cc_list)) notifications_to_be_sent = [sender_email] if queue.enable_notifications_on_email_events and len( 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) # send mail to appropriate people now depending on what objects # were created and who was CC'd if new: ticket.send( { 'submitter': ('newticket_submitter', context), 'new_ticket_cc': ('newticket_cc', context), 'ticket_cc': ('newticket_cc', context) }, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) else: context.update(comment=f.comment) ticket.send( { 'submitter': ('newticket_submitter', context), 'assigned_to': ('updated_owner', context) }, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) if queue.enable_notifications_on_email_events: ticket.send( {'ticket_cc': ('updated_cc', context)}, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) return ticket
def ticket_from_message(message, queue, logger): # 'message' must be an RFC822 formatted message. message = email.message_from_string(message) if six.PY3 else email.message_from_string(message.encode('utf-8')) subject = message.get('subject', _('Comment from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) for affix in STRIPPED_SUBJECT_STRINGS: subject = subject.replace(affix, "") subject = subject.strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = email.utils.parseaddr(sender)[1] cc = message.get_all('cc', None) if cc: # first, fixup the encoding if necessary cc = [decode_mail_headers(decodeUnknown(message.get_charset(), x)) for x in cc] # get_all checks if multiple CC headers, but individual emails may be comma separated too tempcc = [] for hdr in cc: tempcc.extend(hdr.split(',')) # use a set to ensure no duplicates cc = set([x.strip() for x in tempcc]) for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket)) else: logger.info("No tracking ID matched.") ticket = None body = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = email.utils.collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name is None: if part.get_content_subtype() == 'plain': body = EmailReplyParser.parse_reply( decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) ) # workaround to get unicode text out rather than escaped text try: body = body.encode('ascii').decode('unicode_escape') except UnicodeEncodeError: body.encode('utf-8') logger.debug("Discovered plain text MIME part") else: files.append( SimpleUploadedFile(_("email_html_body.html"), encoding.smart_bytes(part.get_payload()), 'text/html') ) logger.debug("Discovered HTML MIME part") else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) payload = part.get_payload() if isinstance(payload, list): payload = payload.pop().as_string() 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: logger.debug("Try to base64 decode the attachment payload") if six.PY2: payloadToWrite = base64.decodestring(payload) else: payloadToWrite = base64.decodebytes(payload) except non_b64_err: logger.debug("Payload was not base64 encoded, using raw bytes") payloadToWrite = payload files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0])) logger.debug("Found MIME attachment %s" % name) counter += 1 if not body: mail = BeautifulSoup(part.get_payload(), "lxml") if ">" in mail.text: body = mail.find('body') body = body.text body = body.encode('ascii', errors='ignore') else: body = mail.text if ticket: try: t = Ticket.objects.get(id=ticket) except Ticket.DoesNotExist: logger.info("Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket)) ticket = None else: logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id)) if t.status == Ticket.CLOSED_STATUS: t.status = Ticket.REOPENED_STATUS t.save() new = False smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = {'high', 'important', '1', 'urgent'} priority = 2 if high_priority_types & {smtp_priority, smtp_importance} else 3 if ticket is None: if settings.QUEUE_EMAIL_BOX_UPDATE_ONLY: return None new = True t = Ticket.objects.create( title=subject, queue=queue, submitter_email=sender_email, created=timezone.now(), description=body, priority=priority, ) logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id)) if cc: # get list of currently CC'd emails current_cc = TicketCC.objects.filter(ticket=ticket) current_cc_emails = [x.email for x in current_cc if x.email] # get emails of any Users CC'd to email, if defined # (some Users may not have an associated email, e.g, when using LDAP) current_cc_users = [x.user.email for x in current_cc if x.user and x.user.email] # ensure submitter, assigned user, queue email not added other_emails = [queue.email_address] if t.submitter_email: other_emails.append(t.submitter_email) if t.assigned_to: other_emails.append(t.assigned_to.email) current_cc = set(current_cc_emails + current_cc_users + other_emails) # first, add any User not previously CC'd (as identified by User's email) all_users = User.objects.all() all_user_emails = set([x.email for x in all_users]) users_not_currently_ccd = all_user_emails.difference(set(current_cc)) users_to_cc = cc.intersection(users_not_currently_ccd) for user in users_to_cc: tcc = TicketCC.objects.create( ticket=t, user=User.objects.get(email=user), can_view=True, can_update=False ) tcc.save() # then add remaining emails alphabetically, makes testing easy new_cc = cc.difference(current_cc).difference(all_user_emails) new_cc = sorted(list(new_cc)) for ccemail in new_cc: tcc = TicketCC.objects.create( ticket=t, email=ccemail.replace('\n', ' ').replace('\r', ' '), can_view=True, can_update=False ) tcc.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() logger.debug("Created new FollowUp for Ticket") if six.PY2: logger.info(("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace')) elif six.PY3: logger.info("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)) attached = process_attachments(f, files) for att_file in attached: logger.info("Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size)) context = safe_template_context(t) 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.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
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) ) ): return HttpResponseForbidden(_("Sorry, you need to login to do that.")) ticket = get_object_or_404(Ticket, id=ticket_id) comment = request.POST.get("comment", "") new_status = int(request.POST.get("new_status", ticket.status)) title = request.POST.get("title", "") public = request.POST.get("public", False) owner = int(request.POST.get("owner", None)) priority = int(request.POST.get("priority", ticket.priority)) due_year = int(request.POST.get("due_date_year")) due_month = int(request.POST.get("due_date_month")) due_day = int(request.POST.get("due_date_day")) due_date = datetime(due_year, due_month, due_day) if due_year and due_month and due_day else ticket.due_date tags = request.POST.get("tags", "") # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) # this line sometimes creates problems if code is sent as a comment. # if comment contains some django code, like "why does {% if bla %} crash", # then the following line will give us a crash, since django expects {% if %} # to be closed with an {% endif %} tag. comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: f.user = request.user f.public = public reassigned = False if owner is not None: if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): new_user = User.objects.get(id=owner) f.title = _("Assigned to %(username)s") % {"username": new_user.username} ticket.assigned_to = new_user reassigned = True # user changed owner to 'unassign' elif owner == 0 and ticket.assigned_to is not None: f.title = _("Unassigned") ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += " and %s" % ticket.get_status_display() else: f.title = "%s" % ticket.get_status_display() if not f.title: if f.comment: f.title = _("Comment") else: f.title = _("Updated") f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist("attachment"): filename = file.name.replace(" ", "_") a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or "application/octet-stream", size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, "MAX_EMAIL_ATTACHMENT_SIZE", 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange(followup=f, field=_("Title"), old_value=ticket.title, new_value=title) c.save() ticket.title = title if priority != ticket.priority: c = TicketChange(followup=f, field=_("Priority"), old_value=ticket.priority, new_value=priority) c.save() ticket.priority = priority if due_date != ticket.due_date: c = TicketChange(followup=f, field=_("Due on"), old_value=ticket.due_date, new_value=due_date) if helpdesk_settings.HELPDESK_UPDATE_CALENDAR: from helpdesk import calendars calendars.update_calendar(request, search_date=ticket.due_date) c.save() ticket.due_date = due_date if HAS_TAGGING_SUPPORT: if tags != ticket.tags: c = TicketChange(followup=f, field=_("Tags"), old_value=ticket.tags, new_value=tags) c.save() ticket.tags = tags if HAS_TAGGIT_SUPPORT: old_tags = [tag.name for tag in ticket.tags.all()] old_tags.sort() new_tags = tags.replace(" ", "").strip(",").split(",") new_tags.sort() if new_tags != old_tags: c = TicketChange(followup=f, field=_("Tags"), old_value=", ".join(old_tags), new_value=", ".join(new_tags)) c.save() ticket.tags.set(*new_tags) if new_status in [Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS]: ticket.resolution = comment messages_sent_to = [] # ticket might have changed above, so we re-instantiate context with the # (possibly) updated ticket. context = safe_template_context(ticket) context.update(resolution=ticket.resolution, comment=f.comment) if ( ticket.submitter_email and public and (f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))) ): if f.new_status == Ticket.RESOLVED_STATUS: template = "resolved_submitter" elif f.new_status == Ticket.CLOSED_STATUS: template = "closed_submitter" else: template = "updated_submitter" send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True ) messages_sent_to.append(cc.email_address) if ( ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to ): # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = "assigned_owner" elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = "resolved_owner" elif f.new_status == Ticket.CLOSED_STATUS: template_staff = "closed_owner" else: template_staff = "updated_owner" if ( not reassigned or (reassigned and ticket.assigned_to.usersettings.settings.get("email_on_ticket_assign", False)) ) or (not reassigned and ticket.assigned_to.usersettings.settings.get("email_on_ticket_change", False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = "assigned_cc" elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = "resolved_cc" elif f.new_status == Ticket.CLOSED_STATUS: template_cc = "closed_cc" else: template_cc = "updated_cc" send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url)
def ticket_from_message(message, queue, quiet): # 'message' must be an RFC822 formatted message. msg = message message = email.message_from_string(msg) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_name = parseaddr(sender)[0] sender_email = parseaddr(sender)[1] body_plain, body_html = '', '' for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r"^\[(?P<queue>[-A-Za-z0-9]+)-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') else: ticket = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name == None: if part.get_content_subtype() == 'plain': body_plain = decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) else: body_html = part.get_payload(decode=True) else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append({ 'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()}, ) counter += 1 if body_plain: body = body_plain else: body = _('No plain-text email body available. Please see attachment email_html_body.html.') if body_html: files.append({ 'filename': _("email_html_body.html"), 'content': body_html, 'type': 'text/html', }) now = datetime.now() if ticket: try: t = Ticket.objects.get(id=ticket) new = False except Ticket.DoesNotExist: ticket = None priority = 3 smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = ('high', 'important', '1', 'urgent') if smtp_priority in high_priority_types or smtp_importance in high_priority_types: priority = 2 update = '' if ticket == None: t = Ticket( title=subject, queue=queue, submitter_name=sender_name, submitter_email=sender_email, created=now, description=body, priority=priority, ) t.save() new = True 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 = datetime.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%s" % (t.queue.slug, t.id, t.title, update)).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) 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
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)): return HttpResponseForbidden(_('Sorry, you need to login to do that.')) ticket = get_object_or_404(Ticket, id=ticket_id) comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) title = request.POST.get('title', ticket.title) public = request.POST.get('public', public) owner = int(request.POST.get('owner', 0)) priority = int(request.POST.get('priority', ticket.priority)) tags = request.POST.get('tags', '') if public: ticket.notify_owner = True else: ticket.notify_owner = False # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) if request.user.is_authenticated(): f.user = request.user f.public = public reassigned = False if owner is not None: if owner != 0 and ( (ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): new_user = User.objects.get(id=owner) f.title = _('Assigned to %(username)s') % { 'username': new_user.username, } ticket.assigned_to = new_user reassigned = True elif owner == 0 and ticket.assigned_to is not None: f.title = _('Unassigned') ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += ' and %s' % ticket.get_status_display() else: f.title = '%s' % ticket.get_status_display() if not f.title: if f.comment: f.title = _('Comment') else: f.title = _('Updated') f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist('attachment'): filename = file.name.replace(' ', '_') a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange( followup=f, field=_('Title'), old_value=ticket.title, new_value=title, ) c.save() ticket.title = title if priority != ticket.priority: c = TicketChange( followup=f, field=_('Priority'), old_value=ticket.priority, new_value=priority, ) c.save() ticket.priority = priority if HAS_TAG_SUPPORT: if tags != ticket.tags: c = TicketChange( followup=f, field=_('Tags'), old_value=ticket.tags, new_value=tags, ) c.save() ticket.tags = tags if f.new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment messages_sent_to = [] context.update( resolution=ticket.resolution, comment=f.comment, ) if ticket.submitter_email and public and ( f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if f.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_owner' elif f.new_status == Ticket.CLOSED_STATUS: template = 'closed_owner' else: template = 'updated_owner' send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.assigned_to and request.user.account != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = 'assigned_to' elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_asigned_to' elif f.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_assigned_to' else: template_staff = 'updated_assigned_to' if (not reassigned or (reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_assign', False))) or ( not reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_change', False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif f.new_status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() if request.user.is_staff: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url)
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))): return HttpResponseForbidden(_('Sorry, you need to login to do that.')) ticket = get_object_or_404(Ticket, id=ticket_id) comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) title = request.POST.get('title', '') public = request.POST.get('public', False) owner = int(request.POST.get('owner', None)) priority = int(request.POST.get('priority', ticket.priority)) due_year = int(request.POST.get('due_date_year')) due_month = int(request.POST.get('due_date_month')) due_day = int(request.POST.get('due_date_day')) due_date = datetime( due_year, due_month, due_day) if due_year and due_month and due_day else ticket.due_date tags = request.POST.get('tags', '') # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) # this line sometimes creates problems if code is sent as a comment. # if comment contains some django code, like "why does {% if bla %} crash", # then the following line will give us a crash, since django expects {% if %} # to be closed with an {% endif %} tag. comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: f.user = request.user f.public = public reassigned = False if owner is not None: if owner != 0 and ( (ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): new_user = User.objects.get(id=owner) f.title = _('Assigned to %(username)s') % { 'username': new_user.username, } ticket.assigned_to = new_user reassigned = True # user changed owner to 'unassign' elif owner == 0 and ticket.assigned_to is not None: f.title = _('Unassigned') ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += ' and %s' % ticket.get_status_display() else: f.title = '%s' % ticket.get_status_display() if not f.title: if f.comment: f.title = _('Comment') else: f.title = _('Updated') f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist('attachment'): filename = file.name.replace(' ', '_') a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange( followup=f, field=_('Title'), old_value=ticket.title, new_value=title, ) c.save() ticket.title = title if priority != ticket.priority: c = TicketChange( followup=f, field=_('Priority'), old_value=ticket.priority, new_value=priority, ) c.save() ticket.priority = priority if due_date != ticket.due_date: c = TicketChange( followup=f, field=_('Due on'), old_value=ticket.due_date, new_value=due_date, ) if helpdesk_settings.HELPDESK_UPDATE_CALENDAR: from helpdesk import calendars calendars.update_calendar(request, search_date=ticket.due_date) c.save() ticket.due_date = due_date if HAS_TAGGING_SUPPORT: if tags != ticket.tags: c = TicketChange( followup=f, field=_('Tags'), old_value=ticket.tags, new_value=tags, ) c.save() ticket.tags = tags if HAS_TAGGIT_SUPPORT: old_tags = [tag.name for tag in ticket.tags.all()] old_tags.sort() new_tags = tags.replace(' ', '').strip(',').split(',') new_tags.sort() if new_tags != old_tags: c = TicketChange( followup=f, field=_('Tags'), old_value=', '.join(old_tags), new_value=', '.join(new_tags), ) c.save() ticket.tags.set(*new_tags) if new_status in [Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS]: ticket.resolution = comment messages_sent_to = [] # ticket might have changed above, so we re-instantiate context with the # (possibly) updated ticket. context = safe_template_context(ticket) context.update( resolution=ticket.resolution, comment=f.comment, ) if ticket.submitter_email and public and ( f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if f.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_submitter' elif f.new_status == Ticket.CLOSED_STATUS: template = 'closed_submitter' else: template = 'updated_submitter' send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = 'assigned_owner' elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_owner' elif f.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_owner' else: template_staff = 'updated_owner' if (not reassigned or (reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_assign', False))) or ( not reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_change', False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif f.new_status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() if request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url)
def ticket_from_message(message, queue, logger): # 'message' must be an RFC822 formatted message. message = email.message_from_string(message) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) for affix in STRIPPED_SUBJECT_STRINGS: subject = subject.replace(affix, "") subject = subject.strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = email.utils.parseaddr(sender)[1] for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket)) else: logger.info("No tracking ID matched.") ticket = None body = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = email.utils.collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name is None: if part.get_content_subtype() == 'plain': body = EmailReplyParser.parse_reply( decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) ) logger.debug("Discovered plain text MIME part") else: files.append( SimpleUploadedFile(_("email_html_body.html"), encoding.smart_bytes(part.get_payload()), 'text/html') ) logger.debug("Discovered HTML MIME part") else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append(SimpleUploadedFile(name, encoding.smart_bytes(part.get_payload()), part.get_content_type())) logger.debug("Found MIME attachment %s" % name) counter += 1 if not body: body = _('No plain-text email body available. Please see attachment "email_html_body.html".') if ticket: try: t = Ticket.objects.get(id=ticket) except Ticket.DoesNotExist: logger.info("Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket)) ticket = None else: logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id)) if t.status == Ticket.CLOSED_STATUS: t.status = Ticket.REOPENED_STATUS t.save() new = False smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = {'high', 'important', '1', 'urgent'} priority = 2 if high_priority_types & {smtp_priority, smtp_importance} else 3 if ticket is None: new = True t = Ticket.objects.create( title=subject, queue=queue, submitter_email=sender_email, created=timezone.now(), description=body, priority=priority, ) logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id)) 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() logger.debug("Created new FollowUp for Ticket") if six.PY2: logger.info(("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace')) elif six.PY3: logger.info("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)) attached = process_attachments(f, files) for att_file in attached: logger.info("Attachment '%s' successfully added to ticket from email." % att_file[0]) context = safe_template_context(t) 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.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
def update_ticket(request, ticket_id, public=False): ticket = get_object_or_404(Ticket, id=ticket_id,owner=request.user) comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) title = request.POST.get('title', ticket.title) #public = request.POST.get('public', public) public=True owner = ticket.owner #priority = int(request.POST.get('priority', ticket.priority)) tags = request.POST.get('tags', '') # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) comment = loader.get_template_from_string(comment).render(Context(context)) #if owner is None and ticket.assigned_to: # owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) #if request.user.is_authenticated(): f.account = request.user.account f.public = True reassigned = False if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += _(u'%(STATUS)s %(USER)s ') % {'USER': request.user.account, 'STATUS': ticket.get_status_display()} else: f.title = _(u'%(STATUS)s %(USER)s ') % {'USER': request.user.account, 'STATUS': ticket.get_status_display()} if not f.title: if f.comment: f.title = _(u'Добавлен комментарий от %(USER)s ') % {'USER': request.user.account} else: f.title = _(u'Обновлено %(USER)s ') % {'USER': request.user.account} f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist('attachment'): filename = file.name.replace(' ', '_') a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange( followup=f, field=_('Title'), old_value=ticket.title, new_value=title, ) c.save() ticket.title = title if HAS_TAG_SUPPORT: if tags != ticket.tags: c = TicketChange( followup=f, field=_('Tags'), old_value=ticket.tags, new_value=tags, ) c.save() ticket.tags = tags if f.new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment messages_sent_to = [] context.update( resolution=ticket.resolution, comment=f.comment, ) if ticket.submitter_email and public and (f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if f.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_owner' elif f.new_status == Ticket.CLOSED_STATUS: template = 'closed_owner' else: template = 'updated_owner' send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = 'assigned_to' elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_assigned_to' elif f.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_assigned_to' else: template_staff = 'updated_assigned_to' if (not reassigned or ( reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_assign', False))) or (not reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_change', False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif f.new_status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() return HttpResponseRedirect(ticket.ticket_url)
def ticket_from_message(message, queue, logger): # 'message' must be an RFC822 formatted message. message = email.message_from_string( message) if six.PY3 else email.message_from_string( message.encode('utf-8')) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) for affix in STRIPPED_SUBJECT_STRINGS: subject = subject.replace(affix, "") subject = subject.strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = email.utils.parseaddr(sender)[1] for ignore in IgnoreEmail.objects.filter( Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket)) else: logger.info("No tracking ID matched.") ticket = None body = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = email.utils.collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name is None: if part.get_content_subtype() == 'plain': body = EmailReplyParser.parse_reply( decodeUnknown(part.get_content_charset(), part.get_payload(decode=True))) # workaround to get unicode text out rather than escaped text body = body.encode('ascii').decode( 'unicode_escape') if six.PY3 else body.encode('utf-8') logger.debug("Discovered plain text MIME part") else: files.append( SimpleUploadedFile( _("email_html_body.html"), encoding.smart_bytes(part.get_payload()), 'text/html')) logger.debug("Discovered HTML MIME part") else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append( SimpleUploadedFile(name, encoding.smart_bytes(part.get_payload()), part.get_content_type())) logger.debug("Found MIME attachment %s" % name) counter += 1 if not body: body = _( 'No plain-text email body available. Please see attachment "email_html_body.html".' ) if ticket: try: t = Ticket.objects.get(id=ticket) except Ticket.DoesNotExist: logger.info( "Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket)) ticket = None else: logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id)) if t.status == Ticket.CLOSED_STATUS: t.status = Ticket.REOPENED_STATUS t.save() new = False smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = {'high', 'important', '1', 'urgent'} priority = 2 if high_priority_types & {smtp_priority, smtp_importance } else 3 if ticket is None: new = True t = Ticket.objects.create( title=subject, queue=queue, submitter_email=sender_email, created=timezone.now(), description=body, priority=priority, ) logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id)) 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() logger.debug("Created new FollowUp for Ticket") if six.PY2: logger.info(("[%s-%s] %s" % ( t.queue.slug, t.id, t.title, )).encode('ascii', 'replace')) elif six.PY3: logger.info("[%s-%s] %s" % ( t.queue.slug, t.id, t.title, )) attached = process_attachments(f, files) for att_file in attached: logger.info( "Attachment '%s' successfully added to ticket from email." % att_file[0]) context = safe_template_context(t) 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.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
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'] to_list = getaddresses(message.get_all('To', [])) cc_list = getaddresses(message.get_all('Cc', [])) message_id = message.get('Message-Id') in_reply_to = message.get('In-Reply-To') 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 = [] new_ticket_ccs.append(create_ticket_cc(ticket, to_list)) new_ticket_ccs.append(create_ticket_cc(ticket, cc_list)) notification_template = None notifications_to_be_sent = [sender_email,] if queue.enable_notifications_on_email_events and len(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 new: notification_template = 'newticket_cc' if sender_email: send_templated_mail( 'newticket_submitter', context, recipients=notifications_to_be_sent, 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, ) if queue.enable_notifications_on_email_events: if queue.updated_ticket_cc: send_templated_mail( 'updated_cc', context, recipients=notifications_to_be_sent, sender=queue.from_address, fail_silently=True, ) return ticket
def ticket_from_message(message, queue, quiet): is_cc = False update = None # 'message' must be an RFC822 formatted message. msg = message message = email.message_from_string(msg) subject = message.get('subject', _('Created from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = parseaddr(sender)[1] body_plain, body_html = '', '' for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True # Check if we're being CC'ed, in which case we might not want to send emails or filter dest = decode_mail_headers(decodeUnknown(message.get_charset(), message.get('to', _('Unknown Sender')))) dest_email = parseaddr(dest) if (not helpdesk_settings.HELPDESK_EMAIL_CONFIRM_CC or helpdesk_settings.HELPDESK_FILTER_CC_ALTERNATE) \ and dest_email[1] != queue.email_address: is_cc = True # If we want to filter CC'd messages to a seperate queue, do so # Try to filter to a queue based on gmail labels reset_queue = False if helpdesk_settings.HELPDESK_FILTER_LABEL_TO_QUEUE: match_info = re.match(r"^(.*?)\++(?P<label>.*?)@.*", dest_email[1]) if match_info: try: new_queue = Queue.objects.get(slug=match_info.group('label').lower()) if new_queue: logger.info(" ++ Matched label '%s' to queue '%s'" % (match_info.group('label').lower(), new_queue)) queue = new_queue reset_queue = True except: logger.error(" !! Failed to match label '%s' to a queue, not moving message" % match_info.group('label').lower()) # Check we want to filter CCs, but only if we have a queue and # a queue was not already modified because we matched a label if helpdesk_settings.HELPDESK_FILTER_CC_ALTERNATE and is_cc and queue.alternate_queue is not None \ and not reset_queue: logger.info(" ++ We think this is CC'd") queue = queue.alternate_queue matchobj = re.match(r"^\[(?P<queue>[-A-Za-z0-9]+)-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') else: ticket = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name == None: if part.get_content_subtype() == 'plain': try: body_plain = decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) except: # We could get a unicode exception here, in which case, we really don't know anymore body_plain = None else: body_html = part.get_payload(decode=True) else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) files.append({ 'filename': name, 'content': part.get_payload(decode=True), 'type': part.get_content_type()}, ) counter += 1 plain = html = False if body_plain: body = body_plain plain = True else: body = _('No plain-text email body available. Please see attachment email_html_body.html.') if body_html: html = True files.append({ 'filename': _("email_html_body.html"), 'content': body_html, 'type': 'text/html', }) now = datetime.now() if ticket: try: t = Ticket.objects.get(id=ticket) new = False except Ticket.DoesNotExist: logger.debug("Didn't find a ticket with ID %s" % ticket) ticket = None priority = 3 smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = ('high', 'important', '1', 'urgent') if smtp_priority in high_priority_types or smtp_importance in high_priority_types: priority = 2 if ticket == None: logger.debug("Creating new ticket for email") t = Ticket( title=subject, queue=queue, submitter_email=sender_email, created=now, description=body, priority=priority, ) t.save() new = True update = '' elif t.status == Ticket.CLOSED_STATUS: logger.debug("Reopening ticket") t.status = Ticket.REOPENED_STATUS t.save() f = FollowUp( ticket = t, title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}), date = datetime.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: logger.info(" [%s-%s] %s%s" % (t.queue.slug, t.id, t.title, update)).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: logger.info(" - %s" % filename) context = safe_template_context(t) if new: if helpdesk_settings.HELPDESK_SEND_SUBMITTER_EMAIL and sender_email and not is_cc: send_templated_mail( 'newticket_submitter', context, recipients=sender_email, sender=queue.from_address, fail_silently=True, ) if queue.new_ticket_cc and not is_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 and not is_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 and not is_cc: send_templated_mail( 'updated_owner', context, recipients=t.assigned_to.email, sender=queue.from_address, fail_silently=True, ) if queue.updated_ticket_cc and not is_cc: send_templated_mail( 'updated_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, ) return t