def followup_edit(request, ticket_id, followup_id, ): "Edit followup options with an ability to change the ticket." followup = get_object_or_404(FollowUp, id=followup_id) ticket = get_object_or_404(Ticket, id=ticket_id) if request.method == 'GET': form = EditFollowUpForm(initial= {'title': escape(followup.title), 'ticket': followup.ticket, 'comment': escape(followup.comment), 'public': followup.public, 'new_status': followup.new_status, }) return render_to_response('helpdesk/followup_edit.html', RequestContext(request, { 'followup': followup, 'ticket': ticket, 'form': form, })) elif request.method == 'POST': form = EditFollowUpForm(request.POST) if form.is_valid(): title = form.cleaned_data['title'] _ticket = form.cleaned_data['ticket'] comment = form.cleaned_data['comment'] public = form.cleaned_data['public'] new_status = form.cleaned_data['new_status'] #will save previous date old_date = followup.date followup.delete() new_followup = FollowUp(title=title, date=old_date, ticket=_ticket, comment=comment, public=public, new_status=new_status, ) new_followup.save() return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket.id]))
def get_issue_status(repo, ticket): repo = repo + "/issues/" + ticket.github_issue_number token = settings.GITHUB_AUTH_TOKEN header = {'Authorization': 'token %s' % token} r = requests.get(repo, headers=header) try: if int(r.status_code) == 200: data = json.loads(r.content) update_ticket = Ticket.objects.get(id=ticket.id) title = data['title'] updated_date = data['updated_at'] state = data['state'] description = ticket.description state_txt = '' if state == 'closed': status = 4 # If 'Closed' in github, save as 'Closed' in TW state_txt = 'Closed' elif state == 'open' and update_ticket.status == 4: status = 2 state_txt = 'Reopened' else: status = 1 state_txt = 'Open' # add a comment/explanation for the change of state in TW comments = '[GitHub Sync] Ticket is ' + str( state_txt) + ' in GitHub' current_status = update_ticket.status print 'Ticket#' + str(ticket.id) + str(comments) try: if current_status != 7 and status != 1: if not int(current_status) == int(status): new_followup = FollowUp( title=title, date=updated_date, ticket_id=ticket.id, comment=comments, public=1, new_status=status, ) new_followup.save() update_ticket.status = status except Exception, e: pass update_ticket.title = title update_ticket.description = description try: update_ticket.save() except Exception, e: pass
def _create_follow_up(self, ticket, title, user=None): followup = FollowUp( ticket=ticket, title=title, date=timezone.now(), public=True, comment=self.cleaned_data["body"] ) if user: followup.user = user return followup
def _create_follow_up(self, ticket, title, user=None): followup = FollowUp(ticket=ticket, title=title, date=timezone.now(), public=True, comment=self.cleaned_data['body'], ) if user: followup.user = user return followup
def set_validated(self, validated, user): if validated == True: self.validate_date = datetime.now() # append follow up to ticket follow = FollowUp(ticket=self.ticket, date=self.validate_date, title="已進行審核", user=user) follow.save() elif validated == False: self.validate_date = None
def take(self, request, *args, **kwargs): f = FollowUp( ticket_id=self.object.pk, user_id = request.user.pk, date=timezone.now(), comment='', title=_('Assigned to %s') % request.user.username, public = 1 ) f.save() self.object.assigned_to_id = request.user.id self.object.save(update_fields=['assigned_to']) return HttpResponseRedirect(self.get_success_url())
def get_label(repo,ticket): repo = repo + "/issues/" + ticket.github_issue_number + "/labels" r = requests.get(repo) if int(r.status_code) == 200: data = json.loads(r.content) label_txt2 = "" label_txt = "" label_int = '0' for item in range(len(data)): label = data[item]['name'] if label == "Tola-Work Ticket": label_txt = 'Submitted from TolaWork ' elif label == "accepted": label_txt2 = ', accepted by QA Lead and moved into the Ready Queue' label_int = '5' elif label == "1 - Ready": label_txt2 = ', accepted by Developers and moved into the Ready Queue' label_int = '1' elif label == "2 - Working": label_txt2 = ' and Developers have started working on the Ticket' label_int = '2' elif label == "3 - In Dev": label_txt2 = ' and moved to Dev' label_int = '3' elif label == "4 - Done": label_txt2 = ' and its Done' label_int = '4' comments = '[Progress Update] Ticket ' + str(label_txt) + str(label_txt2) update_ticket = Ticket.objects.get(id=ticket.id) db_label = update_ticket.git_label if not int(db_label) == int(label_int): new_followup = FollowUp(title=ticket.title, ticket_id=ticket.id, comment=comments, public=1, ) new_followup.save() update_ticket.git_label = label_int update_ticket.save() return r.status_code
def hold_ticket(request, ticket_id, unhold=False): ticket = get_object_or_404(Ticket, id=ticket_id) if unhold: ticket.on_hold = False title = _("Ticket taken off hold") else: ticket.on_hold = True title = _("Ticket placed on hold") f = FollowUp(ticket=ticket, user=request.user, title=title, date=datetime.now(), public=True) f.save() ticket.save() return HttpResponseRedirect(ticket.get_absolute_url())
def ticket_mark_complete(request, ticket_id): resolution = "Marked complete by " + request.user.username action = Ticket.objects.get(pk=ticket_id) follow_up = FollowUp ( ticket=action, date=datetime.now(), comment=resolution, title="Reply Sent", public=True) follow_up.save() action = get_object_or_404(Ticket, pk=ticket_id) action.status = Ticket.CLOSED_STATUS action.resolution = follow_up.comment action.save() return redirect("list_email_tickets")
def post(self, request, *args, **kwargs): if self.kwargs['unhold']: self.object.on_hold = False title = _('Ticket taken off hold') else: self.object.on_hold = True title = _('Ticket placed on hold') self.object.save(update_fields=['on_hold']) f = FollowUp( ticket = ticket, user = request.user, title = title, date = timezone.now(), public = True, ) f.save() return HttpResponseRedirect(self.get_success_url())
def followup_edit(request, ticket_id, followup_id): "Edit followup options with an ability to change the ticket." followup = get_object_or_404(FollowUp, id=followup_id) ticket = get_object_or_404(Ticket, id=ticket_id) if request.method == "GET": form = EditFollowUpForm( initial={ "title": escape(followup.title), "ticket": followup.ticket, "comment": escape(followup.comment), "public": followup.public, "new_status": followup.new_status, } ) return render_to_response( "helpdesk/followup_edit.html", RequestContext(request, {"followup": followup, "ticket": ticket, "form": form}), ) elif request.method == "POST": form = EditFollowUpForm(request.POST) if form.is_valid(): title = form.cleaned_data["title"] _ticket = form.cleaned_data["ticket"] comment = form.cleaned_data["comment"] public = form.cleaned_data["public"] new_status = form.cleaned_data["new_status"] # will save previous date old_date = followup.date new_followup = FollowUp( title=title, date=old_date, ticket=_ticket, comment=comment, public=public, new_status=new_status ) # keep old user if one did exist before. if followup.user: new_followup.user = followup.user new_followup.save() # get list of old attachments & link them to new_followup attachments = Attachment.objects.filter(followup=followup) for attachment in attachments: attachment.followup = new_followup attachment.save() # delete old followup followup.delete() return HttpResponseRedirect(reverse("helpdesk_view", args=[ticket.id]))
def close(self, request, *args, **kwargs): if not self.object.assigned_to: owner = 0 else: owner = self.object.assigned_to.id self.object.assigned_to_id = owner self.object.status = Ticket.CLOSED_STATUS self.object.save(update_fields=['status', 'assigned_to']) f = FollowUp( ticket_id=self.object.pk, user_id = request.user.pk, date=timezone.now(), new_status = self.object.status, comment = _('Accepted resolution and closed ticket'), title=_('Assigned to %s') % request.user.username, public = 1 ) f.save() return HttpResponseRedirect(self.get_success_url())
def record_reply_received(self, email): """ Record a reply to this mail as a FollowUp to the Reply action. """ queue = Queue.objects.get(pk=1) actions = self.action.filter(queue=queue) resolution = 'Reply received from %s on %s' % (email.fromParticipant, email.date) for action in actions: follow_up = FollowUp ( ticket=action, date=datetime.now(), comment=resolution, title="Reply received", public=True) follow_up.save() action.status = Ticket.CLOSED_STATUS action.resolution = follow_up.comment action.save()
def hold_ticket(request, ticket_id, unhold=False): ticket = get_object_or_404(Ticket, id=ticket_id) if unhold: ticket.on_hold = False title = _('Ticket taken off hold') else: ticket.on_hold = True title = _('Ticket placed on hold') f = FollowUp( ticket=ticket, user=request.user, title=title, date=datetime.now(), public=True, ) f.save() ticket.save() return HttpResponseRedirect(ticket.get_absolute_url())
def reply_ticket(request, ticket_id): """reply ticket to the client""" if not (request.user.is_authenticated() and request.user.is_active and (request.user.is_staff)): return HttpResponseRedirect('%s?next=%s' % (reverse('staff/login'), request.path)) ticket = get_object_or_404(Ticket, id=ticket_id) if request.method == 'POST': title = request.POST.get('title', '') comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) if not all([title, comment]): return render(request, 'helpdesk/reply_ticket.html', { 'ticket': ticket, }) #save models f = FollowUp(public=True, ticket=ticket, date=timezone.now(), comment=comment, user=request.user) f.title = 'Email:{}'.format(title) if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status f.save() #save attachment files = [] if request.FILES: import mimetypes for file in request.FILES.getlist('attachment'): print(dir(file)) filename = file.name a = Attachment( followup=f, 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() files.append([a.filename, a.file]) if new_status in [Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS]: if new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment ticket.save() #send email send_reply_email(files=files, subject=title, body=comment, recipients=[ticket.submitter_email], sender=ticket.queue.from_address, fail_silently=True) return HttpResponseRedirect(ticket.get_absolute_url()) return render(request, 'helpdesk/reply_ticket.html', { 'ticket': ticket, })
def email_send(request): """ Send an email. The post request should contain the necessary data for sending the mail as follows: subject: the subject of the email body: the body of the email to: the to address """ body = request.POST['body'] to = request.POST['to'] subject = request.POST['subject'] reply_to_id = request.POST['reply_to_id'] message_id = email.utils.make_msgid() mail = EmailMessage(subject, body, '*****@*****.**', [to], headers = {'Message-ID': message_id, 'In-Reply-To': reply_to_id}) mail.send() queue = Queue.objects.get(pk=1) if reply_to_id: replyTo = Message.objects.get(id = reply_to_id) actions = replyTo.action.filter(queue=queue) resolution = 'Reply sent on %s' % (datetime.now()) for action in actions: follow_up = FollowUp ( ticket=action, date=datetime.now(), comment=resolution, title="Reply sent", public=False) follow_up.save() action.status = Ticket.RESOLVED_STATUS action.resolution = follow_up.comment action.save() return redirect("list_email_tickets")
def get_issue_status(repo,ticket): repo = repo + "/issues/" + ticket.github_issue_number r = requests.get(repo) if int(r.status_code) == 200: data = json.loads(r.content) title = data['title'] updated_date = data['updated_at'] state = data['state'] description = data['body'] if state == 'closed': status = 4 # If 'Closed' in github, save as 'Closed' in TW state_txt = 'Closed' else: status = 1 #open state_txt = 'Open' # add a comment/explanation for the change of state in TW comments = '[GitHub Sync] Ticket is ' + str(state_txt) + ' in GitHub' update_ticket = Ticket.objects.get(id=ticket.id) current_status = update_ticket.status print 'Ticket#' + str(ticket.id) + str(comments) if not int(current_status) == int(status): new_followup = FollowUp(title=title, date=updated_date, ticket_id=ticket.id, comment=comments, public=1, new_status=status, ) new_followup.save() update_ticket.status = status update_ticket.title =title update_ticket.description = description update_ticket.save() return r.status_code
def get(self, request, *args, **kwargs): ticket = get_object_or_404(Ticket, id=self.kwargs['pk']) if not _has_access_to_queue(request.user, ticket.queue): raise PermissionDenied() if 'unhold' in self.kwargs.keys(): ticket.on_hold = False title = _('Ticket taken off hold') else: ticket.on_hold = True title = _('Ticket placed on hold') f = FollowUp( ticket = ticket, user = request.user, title = title, date = timezone.now(), public = True, ) f.save() ticket.save() return HttpResponseRedirect(ticket.get_staff_url())
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 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'], account=self.cleaned_data['account'], created=datetime.now(), status=Ticket.OPEN_STATUS, queue=q, description=self.cleaned_data['body'], priority=self.cleaned_data['priority'], owner=self.cleaned_data['owner']) if HAS_TAG_SUPPORT: t.tags = self.cleaned_data['tags'] if self.cleaned_data['assigned_to']: try: t.assigned_to = self.cleaned_data['assigned_to'] except User.DoesNotExist: t.assigned_to = None t.save() f = FollowUp( ticket=t, title=_('Ticket Opened'), date=datetime.now(), public=False, comment=self.cleaned_data['body'], systemuser=user.account, ) 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 = { 'ticket': t, 'queue': q, 'comment': f.comment, } messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_owner', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) #FIX USERSETTINGS #======================================================================= # 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_to', # 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 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 get_label(repo, ticket): repo = repo + "/issues/" + ticket.github_issue_number + "/labels" token = settings.GITHUB_AUTH_TOKEN header = {'Authorization': 'token %s' % token} r = requests.get(repo, headers=header) try: if int(r.status_code) == 200: data = json.loads(r.content) label_txt2 = "" label_txt = "" label_int = '0' labels = [] label = '' for item in range(len(data)): labels.append(data[item]['name']) for l in labels: if "Tola-Work Ticket" in l: pass elif "accepted" in l: label_txt2 = ', accepted by QA Lead and moved into the Ready Queue' label_int = '5' label = l elif "Ready" in l: label_txt2 = ', accepted by Developers and moved into the Ready Queue' label_int = '1' label = l elif "Working" in l: label_txt2 = ' and Developers have started working on the Ticket' label_int = '2' label = l elif "In Dev" in l: label_txt2 = ' and moved to Dev' label_int = '3' label = l elif "Done" in l: label_txt2 = ' and its Done' label_int = '4' label = l print labels comments = '[Progress Update] Ticket ' + str(label_txt) + str( label_txt2) update_ticket = Ticket.objects.get(id=ticket.id) db_label = update_ticket.git_label if not int(db_label) == int(label_int): new_followup = FollowUp( title=ticket.title, ticket_id=ticket.id, comment=comments, public=1, ) new_followup.save() update_ticket.git_label = label_int update_ticket.save() except Exception, e: pass
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 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'], account = self.cleaned_data['account'], created = datetime.now(), status = Ticket.OPEN_STATUS, queue = q, description = self.cleaned_data['body'], priority = self.cleaned_data['priority'], owner = self.cleaned_data['owner'] ) if HAS_TAG_SUPPORT: t.tags = self.cleaned_data['tags'] if self.cleaned_data['assigned_to']: try: t.assigned_to = self.cleaned_data['assigned_to'] except User.DoesNotExist: t.assigned_to = None t.save() f = FollowUp( ticket = t, title = _('Ticket Opened'), date = datetime.now(), public = False, comment = self.cleaned_data['body'], systemuser = user.account, ) 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 = { 'ticket': t, 'queue': q, 'comment': f.comment, } messages_sent_to = [] if t.submitter_email: send_templated_mail( 'newticket_owner', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) messages_sent_to.append(t.submitter_email) #FIX USERSETTINGS #======================================================================= # 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_to', # 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, request=None): """ 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'], ) 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 = { 'ticket': t, 'queue': q, 'site': Site.objects.get_current(), 'request': request } send_templated_mail( 'newticket_submitter', context, recipients=t.submitter_email, sender=q.from_address, fail_silently=True, files=files, ) who_to_notify = [] emails = "%s,%s" % (q.new_ticket_cc, q.updated_ticket_cc) for e in emails.split(','): email = e.strip(' ') if email and email != t.submitter_email and email not in who_to_notify: who_to_notify.append(email) if who_to_notify: send_templated_mail( 'newticket_cc', context, recipients=who_to_notify, sender=q.from_address, fail_silently=True, files=files, ) 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 = { 'ticket': t, 'queue': t.queue, 'resolution': t.resolution, } 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 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 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): """ 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 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 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 = { 'ticket': ticket, 'queue': ticket.queue, '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 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
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 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 = { 'ticket': ticket, 'queue': ticket.queue, '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 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 create_object_from_email_message(message, ticket_id, payload, files, logger): ticket, previous_followup, new = None, None, False now = timezone.now() queue = payload['queue'] sender_email = payload['sender_email'] to_list = getaddresses(message.get_all('To', [])) cc_list = getaddresses(message.get_all('Cc', [])) message_id = message.get('Message-Id') in_reply_to = message.get('In-Reply-To') if in_reply_to is not None: try: queryset = FollowUp.objects.filter( message_id=in_reply_to).order_by('-date') if queryset.count() > 0: previous_followup = queryset.first() ticket = previous_followup.ticket except FollowUp.DoesNotExist: pass # play along. The header may be wrong if previous_followup is None and ticket_id is not None: try: ticket = Ticket.objects.get(id=ticket_id) except Ticket.DoesNotExist: ticket = None else: new = False # Check if the ticket has been merged to another ticket if ticket.merged_to: logger.info("Ticket has been merged to %s" % ticket.merged_to.ticket) # Use the ticket in which it was merged to for next operations ticket = ticket.merged_to # New issue, create a new <Ticket> instance if ticket is None: if not settings.QUEUE_EMAIL_BOX_UPDATE_ONLY: ticket = Ticket.objects.create( title=payload['subject'], queue=queue, submitter_email=sender_email, created=now, description=payload['body'], priority=payload['priority'], ) ticket.save() logger.debug("Created new ticket %s-%s" % (ticket.queue.slug, ticket.id)) new = True update = '' # Old issue being re-opened elif ticket.status == Ticket.CLOSED_STATUS: ticket.status = Ticket.REOPENED_STATUS ticket.save() f = FollowUp(ticket=ticket, title=_('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}), date=now, public=True, comment=payload['body'], message_id=message_id) if ticket.status == Ticket.REOPENED_STATUS: f.new_status = Ticket.REOPENED_STATUS f.title = _( 'Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}) f.save() logger.debug("Created new FollowUp for Ticket") logger.info("[%s-%s] %s" % ( ticket.queue.slug, ticket.id, ticket.title, )) attached = process_attachments(f, files) for att_file in attached: logger.info( "Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size)) context = safe_template_context(ticket) new_ticket_ccs = [] new_ticket_ccs.append(create_ticket_cc(ticket, to_list + cc_list)) notifications_to_be_sent = [sender_email] if queue.enable_notifications_on_email_events and len( notifications_to_be_sent): ticket_cc_list = TicketCC.objects.filter( ticket=ticket).all().values_list('email', flat=True) for email in ticket_cc_list: notifications_to_be_sent.append(email) # send mail to appropriate people now depending on what objects # were created and who was CC'd if new: ticket.send( { 'submitter': ('newticket_submitter', context), 'new_ticket_cc': ('newticket_cc', context), 'ticket_cc': ('newticket_cc', context) }, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) else: context.update(comment=f.comment) ticket.send( { 'submitter': ('newticket_submitter', context), 'assigned_to': ('updated_owner', context) }, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) if queue.enable_notifications_on_email_events: ticket.send( {'ticket_cc': ('updated_cc', context)}, fail_silently=True, extra_headers={'In-Reply-To': message_id}, ) return ticket
def ticket_from_message(message, queue, logger): # 'message' must be an RFC822 formatted message. message = email.message_from_string(message) if six.PY3 else email.message_from_string(message.encode('utf-8')) subject = message.get('subject', _('Comment from e-mail')) subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject)) for affix in STRIPPED_SUBJECT_STRINGS: subject = subject.replace(affix, "") subject = subject.strip() sender = message.get('from', _('Unknown Sender')) sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender)) sender_email = email.utils.parseaddr(sender)[1] cc = message.get_all('cc', None) if cc: # first, fixup the encoding if necessary cc = [decode_mail_headers(decodeUnknown(message.get_charset(), x)) for x in cc] # get_all checks if multiple CC headers, but individual emails may be comma separated too tempcc = [] for hdr in cc: tempcc.extend(hdr.split(',')) # use a set to ensure no duplicates cc = set([x.strip() for x in tempcc]) for ignore in IgnoreEmail.objects.filter(Q(queues=queue) | Q(queues__isnull=True)): if ignore.test(sender_email): if ignore.keep_in_mailbox: # By returning 'False' the message will be kept in the mailbox, # and the 'True' will cause the message to be deleted. return False return True matchobj = re.match(r".*\[" + queue.slug + r"-(?P<id>\d+)\]", subject) if matchobj: # This is a reply or forward. ticket = matchobj.group('id') logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket)) else: logger.info("No tracking ID matched.") ticket = None body = None counter = 0 files = [] for part in message.walk(): if part.get_content_maintype() == 'multipart': continue name = part.get_param("name") if name: name = email.utils.collapse_rfc2231_value(name) if part.get_content_maintype() == 'text' and name is None: if part.get_content_subtype() == 'plain': body = EmailReplyParser.parse_reply( decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)) ) # workaround to get unicode text out rather than escaped text try: body = body.encode('ascii').decode('unicode_escape') except UnicodeEncodeError: body.encode('utf-8') logger.debug("Discovered plain text MIME part") else: files.append( SimpleUploadedFile(_("email_html_body.html"), encoding.smart_bytes(part.get_payload()), 'text/html') ) logger.debug("Discovered HTML MIME part") else: if not name: ext = mimetypes.guess_extension(part.get_content_type()) name = "part-%i%s" % (counter, ext) payload = part.get_payload() if isinstance(payload, list): payload = payload.pop().as_string() payloadToWrite = payload # check version of python to ensure use of only the correct error type if six.PY2: non_b64_err = binascii.Error else: non_b64_err = TypeError try: logger.debug("Try to base64 decode the attachment payload") if six.PY2: payloadToWrite = base64.decodestring(payload) else: payloadToWrite = base64.decodebytes(payload) except non_b64_err: logger.debug("Payload was not base64 encoded, using raw bytes") payloadToWrite = payload files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0])) logger.debug("Found MIME attachment %s" % name) counter += 1 if not body: mail = BeautifulSoup(part.get_payload(), "lxml") if ">" in mail.text: body = mail.find('body') body = body.text body = body.encode('ascii', errors='ignore') else: body = mail.text if ticket: try: t = Ticket.objects.get(id=ticket) except Ticket.DoesNotExist: logger.info("Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket)) ticket = None else: logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id)) if t.status == Ticket.CLOSED_STATUS: t.status = Ticket.REOPENED_STATUS t.save() new = False smtp_priority = message.get('priority', '') smtp_importance = message.get('importance', '') high_priority_types = {'high', 'important', '1', 'urgent'} priority = 2 if high_priority_types & {smtp_priority, smtp_importance} else 3 if ticket is None: if settings.QUEUE_EMAIL_BOX_UPDATE_ONLY: return None new = True t = Ticket.objects.create( title=subject, queue=queue, submitter_email=sender_email, created=timezone.now(), description=body, priority=priority, ) logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id)) if cc: # get list of currently CC'd emails current_cc = TicketCC.objects.filter(ticket=ticket) current_cc_emails = [x.email for x in current_cc if x.email] # get emails of any Users CC'd to email, if defined # (some Users may not have an associated email, e.g, when using LDAP) current_cc_users = [x.user.email for x in current_cc if x.user and x.user.email] # ensure submitter, assigned user, queue email not added other_emails = [queue.email_address] if t.submitter_email: other_emails.append(t.submitter_email) if t.assigned_to: other_emails.append(t.assigned_to.email) current_cc = set(current_cc_emails + current_cc_users + other_emails) # first, add any User not previously CC'd (as identified by User's email) all_users = User.objects.all() all_user_emails = set([x.email for x in all_users]) users_not_currently_ccd = all_user_emails.difference(set(current_cc)) users_to_cc = cc.intersection(users_not_currently_ccd) for user in users_to_cc: tcc = TicketCC.objects.create( ticket=t, user=User.objects.get(email=user), can_view=True, can_update=False ) tcc.save() # then add remaining emails alphabetically, makes testing easy new_cc = cc.difference(current_cc).difference(all_user_emails) new_cc = sorted(list(new_cc)) for ccemail in new_cc: tcc = TicketCC.objects.create( ticket=t, email=ccemail.replace('\n', ' ').replace('\r', ' '), can_view=True, can_update=False ) tcc.save() f = FollowUp( ticket=t, title=_('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}), date=timezone.now(), public=True, comment=body, ) if t.status == Ticket.REOPENED_STATUS: f.new_status = Ticket.REOPENED_STATUS f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}) f.save() logger.debug("Created new FollowUp for Ticket") if six.PY2: logger.info(("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace')) elif six.PY3: logger.info("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)) attached = process_attachments(f, files) for att_file in attached: logger.info("Attachment '%s' (with size %s) successfully added to ticket from email." % (att_file[0], att_file[1].size)) context = safe_template_context(t) if new: if sender_email: send_templated_mail( 'newticket_submitter', context, recipients=sender_email, sender=queue.from_address, fail_silently=True, ) if queue.new_ticket_cc: send_templated_mail( 'newticket_cc', context, recipients=queue.new_ticket_cc, sender=queue.from_address, fail_silently=True, ) if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc: send_templated_mail( 'newticket_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, ) else: context.update(comment=f.comment) if t.assigned_to: send_templated_mail( 'updated_owner', context, recipients=t.assigned_to.email, sender=queue.from_address, fail_silently=True, ) if queue.updated_ticket_cc: send_templated_mail( 'updated_cc', context, recipients=queue.updated_ticket_cc, sender=queue.from_address, fail_silently=True, ) return t
def 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 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 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 save(self, owner=None): """ Writes and returns a Ticket() object """ q = Queue.objects.get(id=int(self.cleaned_data['queue'])) t = Ticket(title=self.cleaned_data['title'], owner=owner, 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'], account=owner.account) t.save() f = FollowUp(ticket=t, title=_('Ticket Opened Via Web'), date=datetime.now(), public=True, comment=self.cleaned_data['body'], account=owner.account) 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 = { 'ticket': t, 'queue': q, } messages_sent_to = [] send_templated_mail( 'newticket_owner', 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=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 update_ticket(request, ticket_id, public=False): if not (public or (request.user.is_authenticated() and request.user.is_active and request.user.is_staff)): return HttpResponseForbidden(_('Sorry, you need to login to do that.')) ticket = get_object_or_404(Ticket, id=ticket_id) comment = request.POST.get('comment', '') new_status = int(request.POST.get('new_status', ticket.status)) title = request.POST.get('title', ticket.title) public = request.POST.get('public', public) owner = int(request.POST.get('owner', 0)) priority = int(request.POST.get('priority', ticket.priority)) tags = request.POST.get('tags', '') if public: ticket.notify_owner = True else: ticket.notify_owner = False # We need to allow the 'ticket' and 'queue' contexts to be applied to the # comment. from django.template import loader, Context context = safe_template_context(ticket) comment = loader.get_template_from_string(comment).render(Context(context)) if owner is None and ticket.assigned_to: owner = ticket.assigned_to.id f = FollowUp(ticket=ticket, date=datetime.now(), comment=comment) if request.user.is_authenticated(): f.user = request.user f.public = public reassigned = False if owner is not None: if owner != 0 and ( (ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): new_user = User.objects.get(id=owner) f.title = _('Assigned to %(username)s') % { 'username': new_user.username, } ticket.assigned_to = new_user reassigned = True elif owner == 0 and ticket.assigned_to is not None: f.title = _('Unassigned') ticket.assigned_to = None if new_status != ticket.status: ticket.status = new_status ticket.save() f.new_status = new_status if f.title: f.title += ' and %s' % ticket.get_status_display() else: f.title = '%s' % ticket.get_status_display() if not f.title: if f.comment: f.title = _('Comment') else: f.title = _('Updated') f.save() files = [] if request.FILES: import mimetypes, os for file in request.FILES.getlist('attachment'): filename = file.name.replace(' ', '_') a = Attachment( followup=f, filename=filename, mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream', size=file.size, ) a.file.save(file.name, file, save=False) a.save() if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000): # Only files smaller than 512kb (or as defined in # settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email. files.append(a.file.path) if title != ticket.title: c = TicketChange( followup=f, field=_('Title'), old_value=ticket.title, new_value=title, ) c.save() ticket.title = title if priority != ticket.priority: c = TicketChange( followup=f, field=_('Priority'), old_value=ticket.priority, new_value=priority, ) c.save() ticket.priority = priority if HAS_TAG_SUPPORT: if tags != ticket.tags: c = TicketChange( followup=f, field=_('Tags'), old_value=ticket.tags, new_value=tags, ) c.save() ticket.tags = tags if f.new_status == Ticket.RESOLVED_STATUS: ticket.resolution = comment messages_sent_to = [] context.update( resolution=ticket.resolution, comment=f.comment, ) if ticket.submitter_email and public and ( f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))): if f.new_status == Ticket.RESOLVED_STATUS: template = 'resolved_owner' elif f.new_status == Ticket.CLOSED_STATUS: template = 'closed_owner' else: template = 'updated_owner' send_templated_mail( template, context, recipients=ticket.submitter_email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.submitter_email) for cc in ticket.ticketcc_set.all(): if cc.email_address not in messages_sent_to: send_templated_mail( template, context, recipients=cc.email_address, sender=ticket.queue.from_address, fail_silently=True, ) messages_sent_to.append(cc.email_address) if ticket.assigned_to and request.user.account != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to: # We only send e-mails to staff members if the ticket is updated by # another user. The actual template varies, depending on what has been # changed. if reassigned: template_staff = 'assigned_to' elif f.new_status == Ticket.RESOLVED_STATUS: template_staff = 'resolved_asigned_to' elif f.new_status == Ticket.CLOSED_STATUS: template_staff = 'closed_assigned_to' else: template_staff = 'updated_assigned_to' if (not reassigned or (reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_assign', False))) or ( not reassigned and ticket.assigned_to.usersettings.settings.get( 'email_on_ticket_change', False)): send_templated_mail( template_staff, context, recipients=ticket.assigned_to.email, sender=ticket.queue.from_address, fail_silently=True, files=files, ) messages_sent_to.append(ticket.assigned_to.email) if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to: if reassigned: template_cc = 'assigned_cc' elif f.new_status == Ticket.RESOLVED_STATUS: template_cc = 'resolved_cc' elif f.new_status == Ticket.CLOSED_STATUS: template_cc = 'closed_cc' else: template_cc = 'updated_cc' send_templated_mail( template_cc, context, recipients=ticket.queue.updated_ticket_cc, sender=ticket.queue.from_address, fail_silently=True, files=files, ) ticket.save() if request.user.is_staff: return HttpResponseRedirect(ticket.get_absolute_url()) else: return HttpResponseRedirect(ticket.ticket_url)
def update_ticket(request, ticket_id, public=False): if not (public or (request.user.is_authenticated() and request.user.is_active and request.user.is_staff)): 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): """ 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 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 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 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 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)