def _send_messages(ticket, queue, followup, files, user=None): context = safe_template_context(ticket) context["comment"] = followup.comment messages_sent_to = [] if ticket.submitter_email: send_templated_mail( "newticket_submitter", context, recipients=ticket.submitter_email, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) if ( ticket.assigned_to and ticket.assigned_to != user and ticket.assigned_to.usersettings.settings.get("email_on_ticket_assign", False) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to ): send_templated_mail( "assigned_owner", context, recipients=ticket.assigned_to.email, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if queue.new_ticket_cc and queue.new_ticket_cc not in messages_sent_to: send_templated_mail( "newticket_cc", context, recipients=queue.new_ticket_cc, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(queue.new_ticket_cc) if ( queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc and queue.updated_ticket_cc not in messages_sent_to ): send_templated_mail( "newticket_cc", context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, files=files, )
def _send_messages(ticket, queue, followup, files, user=None): context = safe_template_context(ticket) context['comment'] = followup.comment messages_sent_to = [] if ticket.submitter_email: send_templated_mail( 'newticket_submitter', context, recipients=ticket.submitter_email, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) if ticket.assigned_to and \ ticket.assigned_to != user and \ ticket.assigned_to.usersettings_helpdesk.settings.get('email_on_ticket_assign', False) and \ ticket.assigned_to.email and \ ticket.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=ticket.assigned_to.email, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if queue.new_ticket_cc and queue.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=queue.new_ticket_cc, sender=queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(queue.new_ticket_cc) if queue.updated_ticket_cc and \ queue.updated_ticket_cc != queue.new_ticket_cc and \ queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, files=files, )
def _send_messages(ticket, queue, followup, files, user=None): context = safe_template_context(ticket) context['comment'] = followup.comment roles = {'submitter': ('newticket_submitter', context), 'new_ticket_cc': ('newticket_cc', context), 'ticket_cc': ('newticket_cc', context)} if ticket.assigned_to and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_assign: roles['assigned_to'] = ('assigned_owner', context) ticket.send( roles, fail_silently=True, files=files, )
def api_public_add_followup(self): try: ticket = Ticket.objects.get( id=self.request.POST.get('ticket', False)) except Ticket.DoesNotExist: return api_return(STATUS_ERROR, "Invalid ticket ID") message = self.request.POST.get('message', None) public = self.request.POST.get('public', 'n') if public not in ['y', 'n']: return api_return(STATUS_ERROR, "Invalid 'public' flag") if not message: return api_return(STATUS_ERROR, "Blank message") f = FollowUp( ticket=ticket, date=timezone.now(), comment=message, user=self.request.user, title='Comment Added', ) if public: f.public = True f.save() context = safe_template_context(ticket) context['comment'] = f.comment messages_sent_to = [] if public and ticket.submitter_email: send_templated_mail( 'updated_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.submitter_email) if public: for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( 'updated_submitter', context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'updated_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.queue.updated_ticket_cc) if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr( ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False ) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: send_templated_mail( 'updated_owner', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, ) ticket.save() return api_return(STATUS_OK)
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 save(self, user): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data["queue"])) t = Ticket( title=self.cleaned_data["title"], submitter_email=self.cleaned_data["submitter_email"], created=datetime.now(), status=Ticket.OPEN_STATUS, queue=q, description=self.cleaned_data["body"], priority=self.cleaned_data["priority"], due_date=self.cleaned_data["due_date"], ) if HAS_TAG_SUPPORT: t.tags = self.cleaned_data["tags"] if self.cleaned_data["assigned_to"]: try: u = User.objects.get(id=self.cleaned_data["assigned_to"]) t.assigned_to = u except User.DoesNotExist: t.assigned_to = None t.save() for field, value in self.cleaned_data.items(): if field.startswith("custom_"): field_name = field.replace("custom_", "") customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket=t, title=_("Ticket Opened"), date=datetime.now(), public=True, comment=self.cleaned_data["body"], user=user, ) if self.cleaned_data["assigned_to"]: f.title = _("Ticket Opened & Assigned to %(name)s") % {"name": t.get_assigned_to} f.save() files = [] if self.cleaned_data["attachment"]: import mimetypes file = self.cleaned_data["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) context = safe_template_context(t) context["comment"] = f.comment messages_sent_to = [] if t.submitter_email: send_templated_mail( "newticket_submitter", context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if ( t.assigned_to and t.assigned_to != user and getattr(t.assigned_to.usersettings.settings, "email_on_ticket_assign", False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to ): send_templated_mail( "assigned_owner", context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( "newticket_cc", context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if ( q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to ): send_templated_mail( "newticket_cc", context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
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 mass_update(request): tickets = request.POST.getlist("ticket_id") action = request.POST.get("action", None) if not (tickets and action): return HttpResponseRedirect(reverse("helpdesk_list")) if action.startswith("assign_"): parts = action.split("_") user = User.objects.get(id=parts[1]) action = "assign" elif action == "take": user = request.user action = "assign" for t in Ticket.objects.filter(id__in=tickets): if action == "assign" and t.assigned_to != user: t.assigned_to = user t.save() f = FollowUp( ticket=t, date=datetime.now(), title=_("Assigned to %(username)s in bulk update" % {"username": user.username}), public=True, user=request.user, ) f.save() elif action == "unassign" and t.assigned_to is not None: t.assigned_to = None t.save() f = FollowUp( ticket=t, date=datetime.now(), title=_("Unassigned in bulk update"), public=True, user=request.user ) f.save() elif action == "close" and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp( ticket=t, date=datetime.now(), title=_("Closed in bulk update"), public=False, user=request.user, new_status=Ticket.CLOSED_STATUS, ) f.save() elif action == "close_public" and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp( ticket=t, date=datetime.now(), title=_("Closed in bulk update"), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS, ) f.save() # Send email to Submitter, Owner, Queue CC context = safe_template_context(t) context.update(resolution=t.resolution, queue=t.queue) messages_sent_to = [] if t.submitter_email: send_templated_mail( "closed_submitter", context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.submitter_email) for cc in t.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( "closed_submitter", context, recipients=cc.email_address, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ( t.assigned_to and request.user != t.assigned_to and t.assigned_to.email and t.assigned_to.email not in messages_sent_to ): send_templated_mail( "closed_owner", context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.assigned_to.email) if t.queue.updated_ticket_cc and t.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( "closed_cc", context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, ) elif action == "delete": t.delete() return HttpResponseRedirect(reverse("helpdesk_list"))
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
def save(self): """ Writes and returns a Ticket() object """ q = self.cleaned_data["queue"] t = Ticket( title=self.cleaned_data["title"], submitter_email=self.cleaned_data["submitter_email"], created=timezone.now(), status=Ticket.OPEN_STATUS, queue=q, description=self.cleaned_data["body"], priority=self.cleaned_data["priority"], due_date=self.cleaned_data["due_date"], ) t.save() for field, value in self.cleaned_data.items(): if field.startswith("custom_"): field_name = field.replace("custom_", "", 1) customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket=t, title=_("Ticket Opened Via Web"), date=timezone.now(), public=True, comment=self.cleaned_data["body"], ) f.save() files = [] if self.cleaned_data["attachment"]: import mimetypes file = self.cleaned_data["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.filename, a.file]) context = safe_template_context(t) messages_sent_to = [] send_templated_mail( "newticket_submitter", context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( "newticket_cc", context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if ( q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to ): send_templated_mail( "newticket_cc", context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
def api_public_resolve(self): try: ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False)) except Ticket.DoesNotExist: return api_return(STATUS_ERROR, "Invalid ticket ID") resolution = self.request.POST.get('resolution', None) if not resolution: return api_return(STATUS_ERROR, "Blank resolution") f = FollowUp( ticket=ticket, date=datetime.now(), comment=resolution, user=self.request.user, title='Resolved', public=True, ) f.save() context = safe_template_context(ticket) context['resolution'] = f.comment subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title) messages_sent_to = [] if ticket.submitter_email: send_templated_mail( 'resolved_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, ) 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( 'resolved_submitter', context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'resolved_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.queue.updated_ticket_cc) if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: send_templated_mail( 'resolved_resolved', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, ) ticket.resoltuion = f.comment ticket.status = Ticket.RESOLVED_STATUS ticket.save() return api_return(STATUS_OK)
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 form_valid(self, form): old_ticket = form.instance ticket = form.save(commit=False) no_changes = all([ not self.request.FILES, not self.request.POST['comment'], ticket.status == old_ticket.status, ticket.title == old_ticket.title, ticket.priority == int(old_ticket.priority), ticket.due_date == old_ticket.due_date, ticket.owner_id != old_ticket.owner_id ]) if no_changes: self.object = old_ticket return HttpResponseRedirect(self.get_success_url()) ticket.save() self.object = ticket followup = FollowUp( ticket_id=ticket.pk, date = timezone.now(), comment = self.request.POST['comment'], user_id = self.request.user.pk, public = self.request.POST['public'], title = '' ) reassigned = False if ticket.owner_id != old_ticket.owner_id: followup.title = _('Asigned to %s' % User.objects.get(id=ticket.owner_id)) reassigned = True if ticket.status != old_ticket.status: if followup.title != '': followup.title += ' and ' followup.title += ticket.get_status_display() followup.save() files = [] if self.request.FILES: import mimetypes, os for file in self.request.FILES.getlist('attachment'): filename = file.name.encode('ascii', 'ignore') a = Attachment( followup_id=followup.pk, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(filename, 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.filename, a.file]) if old_ticket.title != ticket.title: change = TicketChange( followup_id=followup.id, field=_('Title'), old_value = old_ticket.title, new_value = ticket.title ) if old_ticket.priority != ticket.priority: change = TicketChange( followup_id=followup.id, field=_('Priority'), old_value = old_ticket.priority, new_value = ticket.priority ) if old_ticket.due_date != ticket.due_date: change = TicketChange( followup_id=followup.id, field=_('Due on'), old_value = old_ticket.due_date, new_value = ticket.due_date ) # 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. # get_template_from_string was removed in Django 1.8 http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html try: from django.template import engines template_func = engines['django'].from_string except ImportError: # occurs in django < 1.8 template_func = loader.get_template_from_string # RemovedInDjango110Warning: render() must be called with a dict, not a Context. if VERSION < (1, 8): context = Context(context) comment = template_func(followup.comment).render(context) if ticket.status in [ Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS ]: if ticket.status == Ticket.RESOLVED_STATUS or ticket.resolution is None: ticket.resolution = comment messages_sent_to = [] if followup.public and (followup.comment or (followup.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if followup.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_' elif followup.new_status == Ticket.CLOSED_STATUS: template = 'closed_' else: template = 'updated_' template_suffix = 'submitter' if ticket.submitter_email: send_templated_mail( template + template_suffix, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) template_suffix = 'cc' for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template + template_suffix, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(cc.email_address) if old_ticket.assigned_to_id and self.request.user.pk != ticket.assigned_to_id \ 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 followup.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_owner' elif followup.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_owner' else: template_staff = 'updated_owner' if (not reassigned or \ ( reassigned and old_ticket.assigned_to.usersettings.settings.get('email_on_ticket_assign', False))) \ or (not reassigned and old_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 followup.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif followup.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() # auto subscribe user if enabled if helpdesk_settings.HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE: ticketcc_string, SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe(self.request.user, ticket) if SHOW_SUBSCRIBE: subscribe_staff_member_to_ticket(ticket, self.request.user) return HttpResponseRedirect(self.get_success_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 save(self, followup=None, files=None, send_email=False, *args, **kwargs): # print('!'*80) # print('Ticket.save') if not self.id: # This is a new ticket as no ID yet exists. self.created = timezone.now() if not self.priority: self.priority = 3 self.modified = timezone.now() old = None changed = False if self.id: old = Ticket.objects.get(id=self.id) reassigned = old and old.assigned_to != self.assigned_to super(Ticket, self).save(*args, **kwargs) if old: changed = bool([ 1 for f in self._meta.fields if getattr(old, f.name) != getattr(self, f.name) ]) else: changed = True # Select email template based on the type of change. if reassigned: template_staff = 'assigned_owner' elif self.status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_owner' elif self.status == Ticket.CLOSED_STATUS: template_staff = 'closed_owner' else: template_staff = 'updated_owner' messages_sent_to = [] context = safe_template_context(self) context.update( resolution=self.resolution, comment=followup.comment if followup else None, ) # Send status changes to the submitter. if send_email and followup and followup.public and (followup.comment or (self.status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if self.status == Ticket.RESOLVED_STATUS: template = 'resolved_' elif self.status == Ticket.CLOSED_STATUS: template = 'closed_' else: template = 'updated_' template_suffix = 'submitter' if self.submitter_email: send_templated_mail( template + template_suffix, context, recipients=self.submitter_email, sender=self.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(self.submitter_email) # print('messages_sent_to:',messages_sent_to) template_suffix = 'cc' for cc in self.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template + template_suffix, context, recipients=cc.email_address, sender=self.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) # print('messages_sent_to:',messages_sent_to) # Send status updates to CC users. if send_email and followup and self.queue.updated_ticket_cc \ and self.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif self.status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif self.status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=self.queue.updated_ticket_cc, sender=self.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(self.queue.updated_ticket_cc) # print('messages_sent_to:',messages_sent_to) # Send reassignment email to the new assigned user. email_on_ticket_assign = self.assigned_to \ and self.assigned_to.usersettings.settings.get( 'email_on_ticket_assign', False) email_on_ticket_change = self.assigned_to \ and self.assigned_to.usersettings.settings.get( 'email_on_ticket_change', False) send_change_email = \ send_email \ and self.assigned_to \ and self.assigned_to.email \ and self.assigned_to.email not in messages_sent_to \ and self.assigned_to.is_staff \ and self.assigned_to.is_active \ and ( (reassigned and email_on_ticket_assign) or (changed and email_on_ticket_change) ) # print('send_change_email:',send_change_email) if send_change_email: send_templated_mail( template_staff, context, recipients=self.assigned_to.email, sender=self.queue.from_address, fail_silently=True, files=files, )
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 escalate_tickets(queues, verbose): """ Only include queues with escalation configured """ queryset = Queue.objects.filter(escalate_days__isnull=False).exclude( escalate_days=0) if queues: queryset = queryset.filter(slug__in=queues) for q in queryset: last = date.today() - timedelta(days=q.escalate_days) today = date.today() workdate = last days = 0 while workdate < today: if EscalationExclusion.objects.filter(date=workdate).count() == 0: days += 1 workdate = workdate + timedelta(days=1) req_last_escl_date = date.today() - timedelta(days=days) if verbose: print("Processing: %s" % q) for t in q.ticket_set.filter( Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).exclude(priority=1).filter( Q(on_hold__isnull=True) | Q(on_hold=False)).filter( Q(last_escalation__lte=req_last_escl_date) | Q(last_escalation__isnull=True, created__lte=req_last_escl_date)): t.last_escalation = timezone.now() t.priority -= 1 t.save() context = safe_template_context(t) if t.submitter_email: send_templated_mail( 'escalated_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, ) if t.queue.updated_ticket_cc: send_templated_mail( 'escalated_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, ) if t.assigned_to: send_templated_mail( 'escalated_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, ) if verbose: print(" - Esclating %s from %s>%s" % (t.ticket, t.priority + 1, t.priority)) f = FollowUp( ticket=t, title='Ticket Escalated', date=timezone.now(), public=True, comment=_('Ticket escalated after %s days' % q.escalate_days), ) f.save() tc = TicketChange( followup=f, field=_('Priority'), old_value=t.priority + 1, new_value=t.priority, ) tc.save()
def save(self, user): """ Writes and returns a Ticket() object """ q = self.cleaned_data['queue'] t = Ticket( title=self.cleaned_data['title'], submitter_email=self.cleaned_data['submitter_email'], created=datetime.now(), status=Ticket.OPEN_STATUS, queue=q, description=self.cleaned_data['description'], priority=self.cleaned_data['priority'], due_date=self.cleaned_data['due_date'], ) if HAS_TAGGING_SUPPORT: t.tags = self.cleaned_data['tags'] if self.cleaned_data['assigned_to']: try: u = self.cleaned_data['assigned_to'] t.assigned_to = u except User.DoesNotExist: t.assigned_to = None t.save() if HAS_TAGGIT_SUPPORT: t.tags.set(*self.cleaned_data['tags']) for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '') customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket=t, title=_('Ticket Opened'), date=datetime.now(), public=True, comment=self.cleaned_data['description'] if helpdesk_settings.HELPDESK_INCLUDE_DESCRIPTION_IN_FOLLOWUP else None, user=user, ) if self.cleaned_data['assigned_to']: f.title = _('Ticket Opened & Assigned to %(name)s') % { 'name': t.get_assigned_to } f.save() files = [] if self.cleaned_data['attachment']: import mimetypes file = self.cleaned_data['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) context = safe_template_context(t) context['comment'] = f.comment messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if t.assigned_to and t.assigned_to != user and getattr( t.assigned_to.usersettings.settings, 'email_on_ticket_assign', False ) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
def save(self, followup=None, files=None, send_email=False, *args, **kwargs): if not self.id: # This is a new ticket as no ID yet exists. self.created = timezone.now() if not self.priority: self.priority = 3 self.modified = timezone.now() old = None changed = False if self.id: old = Ticket.objects.get(id=self.id) reassigned = old and old.assigned_to != self.assigned_to super(Ticket, self).save(*args, **kwargs) if old: changed = bool([ 1 for f in self._meta.fields if getattr(old, f.name) != getattr(self, f.name) ]) else: changed = True # Select email template based on the type of change. if reassigned: template_staff = 'assigned_owner' elif self.status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_owner' elif self.status == Ticket.CLOSED_STATUS: template_staff = 'closed_owner' else: template_staff = 'updated_owner' messages_sent_to = [] context = safe_template_context(self) context.update( resolution=self.resolution, comment=followup.comment if followup else None, ) # Send status changes to the submitter. if send_email and followup and followup.public and ( followup.comment or (self.status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if self.status == Ticket.RESOLVED_STATUS: template = 'resolved_' elif self.status == Ticket.CLOSED_STATUS: template = 'closed_' else: template = 'updated_' template_suffix = 'submitter' if self.submitter_email: send_templated_mail( template + template_suffix, context, recipients=self.submitter_email, sender=self.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(self.submitter_email) template_suffix = 'cc' for cc in self.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template + template_suffix, context, recipients=cc.email_address, sender=self.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) # Send status updates to CC users. if send_email and followup and self.queue.updated_ticket_cc \ and self.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif self.status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif self.status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=self.queue.updated_ticket_cc, sender=self.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(self.queue.updated_ticket_cc) # Send reassignment email to the new assigned user. email_on_ticket_assign = self.assigned_to \ and self.assigned_to.usersettings.settings.get( 'email_on_ticket_assign', False) email_on_ticket_change = self.assigned_to \ and self.assigned_to.usersettings.settings.get( 'email_on_ticket_change', False) send_change_email = send_email \ and self.assigned_to \ and self.assigned_to.email \ and self.assigned_to.email not in messages_sent_to \ and self.assigned_to.is_staff \ and self.assigned_to.is_active \ and ( (reassigned and email_on_ticket_assign) or (changed and email_on_ticket_change) ) if send_change_email: send_templated_mail( template_staff, context, recipients=self.assigned_to.email, sender=self.queue.from_address, fail_silently=True, files=files, )
def mass_update(request): tickets = request.POST.getlist('ticket_id') action = request.POST.get('action', None) if not (tickets and action): return HttpResponseRedirect(reverse('helpdesk_list')) if action.startswith('assign_'): parts = action.split('_') user = User.objects.get(id=parts[1]) action = 'assign' elif action == 'take': user = request.user action = 'assign' for t in Ticket.objects.filter(id__in=tickets): if action == 'assign' and t.assigned_to != user: t.assigned_to = user t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Assigned to %(username)s in bulk update' % {'username': user.username}), public=True, user=request.user) f.save() elif action == 'unassign' and t.assigned_to is not None: t.assigned_to = None t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Unassigned in bulk update'), public=True, user=request.user) f.save() elif action == 'close' and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=False, user=request.user, new_status=Ticket.CLOSED_STATUS) f.save() elif action == 'close_public' and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS) f.save() # Send email to Submitter, Owner, Queue CC context = safe_template_context(t) context.update( resolution = t.resolution, queue = t.queue, ) messages_sent_to = [] if t.submitter_email: send_templated_mail( 'closed_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.submitter_email) for cc in t.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( 'closed_submitter', context, recipients=cc.email_address, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if t.assigned_to and request.user != t.assigned_to and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'closed_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.assigned_to.email) if t.queue.updated_ticket_cc and t.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'closed_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, ) elif action == 'delete': t.delete() return HttpResponseRedirect(reverse('helpdesk_list'))
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 save(self): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data['queue'])) t = Ticket( title=self.cleaned_data['title'], submitter_email=self.cleaned_data['submitter_email'], created=timezone.now(), status=Ticket.OPEN_STATUS, queue=q, description=self.cleaned_data['body'], priority=self.cleaned_data['priority'], due_date=self.cleaned_data['due_date'], ) if q.default_owner and not t.assigned_to: t.assigned_to = q.default_owner t.save() for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '', 1) customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket=t, title=_('Ticket Opened Via Web'), date=timezone.now(), public=True, comment=self.cleaned_data['body'], ) f.save() files = [] if self.cleaned_data['attachment']: import mimetypes file = self.cleaned_data['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.filename, a.file]) context = safe_template_context(t) messages_sent_to = [] send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if t.assigned_to and \ t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and \ t.assigned_to.email and \ t.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and \ q.updated_ticket_cc != q.new_ticket_cc and \ q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
class TicketForm(forms.Form): queue = forms.ChoiceField(label=_('Queue'), required=True, choices=()) title = forms.CharField( max_length=100, required=True, widget=forms.TextInput(attrs={'size': '60'}), label=_('Summary of the problem'), ) submitter_email = forms.EmailField( required=False, label=_('Submitter E-Mail Address'), widget=forms.TextInput(attrs={'size': '60'}), help_text=_('This e-mail address will receive copies of all public ' 'updates to this ticket.'), ) error_msg = forms.CharField( widget=forms.Textarea(attrs={ 'cols': 47, 'rows': 4 }), label=_('Error Message'), required=False, help_text= _('Was an error message displaced? Please copy and paste this error message here. ' ), ) body = forms.CharField( widget=forms.Textarea(attrs={ 'cols': 47, 'rows': 7 }), label=_('Description of Issue'), required=True, ) assigned_to = forms.ChoiceField( choices=(), required=False, label=_('Case owner'), help_text=_('If you select an owner other than yourself, they\'ll be ' 'e-mailed details of this ticket immediately.'), ) priority = forms.ChoiceField( choices=Ticket.PRIORITY_CHOICES, required=False, initial='3', label=_('Priority'), help_text=_('Please select a priority carefully. If unsure, leave it ' 'as \'3\'.'), ) type = forms.ChoiceField( choices=Ticket.TICKET_TYPE, required=True, initial='3', label=_('Type of Ticket'), help_text=_( 'Enhancements requests or Bugs/Problems with Tola software.'), ) due_date = forms.DateTimeField( widget=extras.SelectDateWidget, required=False, label=_('Due on'), ) tags = forms.ModelMultipleChoiceField( queryset=Tag.objects.all(), widget=M2MSelect, required=False, ) def clean_due_date(self): data = self.cleaned_data['due_date'] #TODO: add Google calendar update hook #if not hasattr(self, 'instance') or self.instance.due_date != new_data: # print "you changed!" return data def __init__(self, *args, **kwargs): """ Add any custom fields that are defined to the form """ super(TicketForm, self).__init__(*args, **kwargs) for field in CustomField.objects.all(): instanceargs = { 'label': field.label, 'help_text': field.help_text, 'required': field.required, } self.customfield_to_field(field, instanceargs) #Crispy Form Helper to add Bootstrap and layout helper = FormHelper() helper.form_method = 'post' helper.form_class = 'form-horizontal' helper.label_class = 'col-sm-3' helper.field_class = 'col-sm-8' helper.form_error_title = 'Form Errors' helper.error_text_inline = True helper.help_text_inline = True helper.html5_required = True helper.form_tag = False def save(self, user): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data['queue'])) q = Queue.objects.get(id=int(self.cleaned_data['queue'])) org = Organization.objects.get(id=1) t = Ticket(title=self.cleaned_data['title'], submitter_email=self.cleaned_data['submitter_email'], created=timezone.now(), status=Ticket.OPEN_STATUS, queue=q, organization=org, description=self.cleaned_data['body'], error_msg=self.cleaned_data['error_msg'], priority=self.cleaned_data['priority'], type=self.cleaned_data['type'], due_date=self.cleaned_data['due_date']) t.assigned_to = None if self.cleaned_data['assigned_to']: try: u = User.objects.get(id=self.cleaned_data['assigned_to']) t.assigned_to = u except User.DoesNotExist: pass t.save() for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '', 1) customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() ticket = t user = None try: user = t.assigned_to except Exception, e: pass f = FollowUp( ticket=ticket, title=_('Ticket Opened'), date=timezone.now(), public=True, comment=self.cleaned_data['body'], user=user, ) if self.cleaned_data['assigned_to']: f.title = _('Ticket Opened & Assigned to %(name)s') % { 'name': t.get_assigned_to } # f.save() context = safe_template_context(t) context['comment'] = f.comment #send email notifications for new ticket messages_sent_to = [] if t.submitter_email: email(t, t.description, "NEW", t.submitter_email) messages_sent_to.append(t.submitter_email) try: if t.assigned_to and t.assigned_to != user and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: email(t, t.description, "NEW", t.assigned_to.email) messages_sent_to.append(t.assigned_to.email) except Exception, e: pass
def form_valid(self, form): form.instance.created = timezone.now() form.instance.status = Ticket.OPEN_STATUS self.object = form.save() t = self.object f = FollowUp( ticket_id = t.id, title = _('Ticket Opened'), date = timezone.now(), public = True, comment = '', user_id = t.assigned_to_id ) if t.assigned_to_id: f.title = _('Ticket Opened & Assigned to %(name)s') % { 'name': t.get_assigned_to.get_full_name() } f.save() files = [] if form.cleaned_data.get('attachment'): import mimetypes file = form.cleaned_data['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. try: files.append([a.filename, a.file]) except NotImplementedError: pass context = safe_template_context(t) context['comment'] = f.comment messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) #if t.assigned_to and t.assigned_to != user and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: if t.assigned_to and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) \ and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if t.queue.new_ticket_cc and t.queue.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=t.queue.new_ticket_cc, sender=t.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.queue.new_ticket_cc) if t.queue.updated_ticket_cc and t.queue.updated_ticket_cc != t.queue.new_ticket_cc \ and t.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, files=files, ) return HttpResponseRedirect(self.get_success_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 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 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 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 save(self, user): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data['queue'])) t = Ticket( title = self.cleaned_data['title'], submitter_email = self.cleaned_data['submitter_email'], created = timezone.now(), status = Ticket.OPEN_STATUS, queue = q, description = self.cleaned_data['body'], priority = self.cleaned_data['priority'], due_date = self.cleaned_data['due_date'], ) if HAS_TAG_SUPPORT: t.tags = self.cleaned_data['tags'] if self.cleaned_data['assigned_to']: try: u = User.objects.get(id=self.cleaned_data['assigned_to']) t.assigned_to = u except User.DoesNotExist: t.assigned_to = None t.save() for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '') customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket = t, title = _('Ticket Opened'), date = timezone.now(), public = True, comment = self.cleaned_data['body'], user = user, ) if self.cleaned_data['assigned_to']: f.title = _('Ticket Opened & Assigned to %(name)s') % { 'name': t.get_assigned_to } f.save() files = [] if self.cleaned_data['attachment']: import mimetypes file = self.cleaned_data['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) context = safe_template_context(t) context['comment'] = f.comment messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if t.assigned_to and t.assigned_to != user and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
def save(self): """ Writes and returns a Ticket() object """ q = Queue.objects.get(title = 'public') t = Ticket( title = self.cleaned_data['title'], submitter_email = self.cleaned_data['submitter_email'], created = timezone.now(), status = Ticket.OPEN_STATUS, queue = q, description = self.cleaned_data['body'], priority = 3, due_date = None, ) t.save() for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '') customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket = t, title = _('Ticket Opened Via Web'), date = timezone.now(), public = True, comment = self.cleaned_data['body'], ) f.save() files = [] context = safe_template_context(t) messages_sent_to = [] send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
def escalate_tickets(queues, verbose): """ Only include queues with escalation configured """ queryset = Queue.objects.filter(escalate_days__isnull=False).exclude(escalate_days=0) if queues: queryset = queryset.filter(slug__in=queues) for q in queryset: last = date.today() - timedelta(days=q.escalate_days) today = date.today() workdate = last days = 0 while workdate < today: if EscalationExclusion.objects.filter(date=workdate).count() == 0: days += 1 workdate = workdate + timedelta(days=1) req_last_escl_date = date.today() - timedelta(days=days) if verbose: print "Processing: %s" % q for t in q.ticket_set.filter( Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS) ).exclude( priority=1 ).filter( Q(on_hold__isnull=True) | Q(on_hold=False) ).filter( Q(last_escalation__lte=req_last_escl_date) | Q(last_escalation__isnull=True, created__lte=req_last_escl_date) ): t.last_escalation = timezone.now() t.priority -= 1 t.save() context = safe_template_context(t) if t.submitter_email: send_templated_mail( 'escalated_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, ) if t.queue.updated_ticket_cc: send_templated_mail( 'escalated_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, ) if t.assigned_to: send_templated_mail( 'escalated_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, ) if verbose: print " - Esclating %s from %s>%s" % ( t.ticket, t.priority+1, t.priority ) f = FollowUp( ticket = t, title = 'Ticket Escalated', date=timezone.now(), public=True, comment=_('Ticket escalated after %s days' % q.escalate_days), ) f.save() tc = TicketChange( followup = f, field = _('Priority'), old_value = t.priority + 1, new_value = t.priority, ) tc.save()
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 api_public_resolve(self): try: ticket = Ticket.objects.get( id=self.request.POST.get('ticket', False)) except Ticket.DoesNotExist: return api_return(STATUS_ERROR, "Invalid ticket ID") resolution = self.request.POST.get('resolution', None) if not resolution: return api_return(STATUS_ERROR, "Blank resolution") f = FollowUp( ticket=ticket, date=timezone.now(), comment=resolution, user=self.request.user, title='Resolved', public=True, ) f.save() context = safe_template_context(ticket) context['resolution'] = f.comment subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title) messages_sent_to = [] if ticket.submitter_email: send_templated_mail( 'resolved_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, ) 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( 'resolved_submitter', context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'resolved_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.queue.updated_ticket_cc) if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr( ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False ) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: send_templated_mail( 'resolved_resolved', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, ) ticket.resoltuion = f.comment ticket.status = Ticket.RESOLVED_STATUS ticket.save() return api_return(STATUS_OK)
def mass_update(request): tickets = request.POST.getlist('ticket_id') action = request.POST.get('action', None) if not (tickets and action): return HttpResponseRedirect(reverse('helpdesk_list')) if action.startswith('assign_'): parts = action.split('_') user = User.objects.get(id=parts[1]) action = 'assign' elif action == 'take': user = request.user action = 'assign' for t in Ticket.objects.filter(id__in=tickets): if action == 'assign' and t.assigned_to != user: t.assigned_to = user t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Assigned to %(username)s in bulk update' % {'username': user.username}), public=True, user=request.user) f.save() elif action == 'unassign' and t.assigned_to is not None: t.assigned_to = None t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Unassigned in bulk update'), public=True, user=request.user) f.save() elif action == 'close' and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=False, user=request.user, new_status=Ticket.CLOSED_STATUS) f.save() elif action == 'close_public' and t.status != Ticket.CLOSED_STATUS: t.status = Ticket.CLOSED_STATUS t.save() f = FollowUp(ticket=t, date=datetime.now(), title=_('Closed in bulk update'), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS) f.save() # Send email to Submitter, Owner, Queue CC context = safe_template_context(t) context.update( resolution=t.resolution, queue=t.queue, ) messages_sent_to = [] if t.submitter_email: send_templated_mail( 'closed_submitter', context, recipients=t.submitter_email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.submitter_email) for cc in t.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( 'closed_submitter', context, recipients=cc.email_address, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if t.assigned_to and request.user != t.assigned_to and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'closed_owner', context, recipients=t.assigned_to.email, sender=t.queue.from_address, fail_silently=True, ) messages_sent_to.append(t.assigned_to.email) if t.queue.updated_ticket_cc and t.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'closed_cc', context, recipients=t.queue.updated_ticket_cc, sender=t.queue.from_address, fail_silently=True, ) elif action == 'delete': t.delete() return HttpResponseRedirect(reverse('helpdesk_list'))
def save(self): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data['queue'])) t = Ticket( title = self.cleaned_data['title'], submitter_email = self.cleaned_data['submitter_email'], created = datetime.now(), status = Ticket.OPEN_STATUS, queue = q, description = self.cleaned_data['body'], priority = self.cleaned_data['priority'], due_date = self.cleaned_data['due_date'], ) t.save() for field, value in self.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '') customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() f = FollowUp( ticket = t, title = _('Ticket Opened Via Web'), date = datetime.now(), public = True, comment = self.cleaned_data['body'], ) f.save() files = [] if self.cleaned_data['attachment']: import mimetypes file = self.cleaned_data['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) context = safe_template_context(t) messages_sent_to = [] send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return t
def api_public_add_followup(self): try: ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False)) except Ticket.DoesNotExist: return api_return(STATUS_ERROR, "Invalid ticket ID") message = self.request.POST.get('message', None) public = self.request.POST.get('public', 'n') if public not in ['y', 'n']: return api_return(STATUS_ERROR, "Invalid 'public' flag") if not message: return api_return(STATUS_ERROR, "Blank message") f = FollowUp( ticket=ticket, date=datetime.now(), comment=message, user=self.request.user, title='Comment Added', ) if public: f.public = True f.save() context = safe_template_context(ticket) context['comment'] = f.comment messages_sent_to = [] if public and ticket.submitter_email: send_templated_mail( 'updated_submitter', context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.submitter_email) if public: for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( 'updated_submitter', context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'updated_cc', context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(ticket.queue.updated_ticket_cc) if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: send_templated_mail( 'updated_owner', context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, ) ticket.save() return api_return(STATUS_OK)
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: 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') print files for file in files: print file if file['content']: filename = file['filename'].encode('ascii', 'replace').replace(' ', '_') print filename filename = re.sub('[^a-zA-Z0-9._-]+', '', filename) print 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 form_valid(self, form): self.object = form.save(True) t = self.object q = t.queue for field, value in form.cleaned_data.items(): if field.startswith('custom_'): field_name = field.replace('custom_', '', 1) customfields = CustomField.objects.filter(customfieldset__queue_id=form.cleaned_data['queue']) for customfield in customfields: if customfield.name == field_name: cfv = TicketCustomFieldValue(ticket=t, field=customfield, value=value) cfv.save() cfs = CustomFieldSet(queue_id=q.pk, field=customfield) cfs.save() f = FollowUp( ticket_id = t.pk, title = _('Ticket Opened'), date = timezone.now(), public = True, comment = form.cleaned_data['body'], user_id = t.assigned_to_id ) if form.cleaned_data['assigned_to']: f.title = _('Ticket Opened & Assigned to %(name)s') % { 'name': t.get_assigned_to } f.save() files = [] if form.cleaned_data['attachment']: import mimetypes file = form.cleaned_data['attachment'] filename = file.name.replace(' ', '_') a = Attachment( followup_id=f.pk, 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. try: files.append([a.filename, a.file]) except NotImplementedError: pass context = safe_template_context(t) context['comment'] = f.comment messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) if t.assigned_to and t.assigned_to != self.request.user and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to: send_templated_mail( 'assigned_owner', context, recipients=t.assigned_to.email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.assigned_to.email) if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.new_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(q.new_ticket_cc) if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to: send_templated_mail( 'newticket_cc', context, recipients=q.updated_ticket_cc, sender=q.from_address, fail_silently=True, files=files, ) return HttpResponseRedirect(self.get_success_url())