2014-07-29 20:10:37 +00:00
|
|
|
from django.conf import settings as djsettings
|
2014-05-08 16:59:35 +00:00
|
|
|
from django.db import models
|
2016-03-31 16:02:50 +00:00
|
|
|
from django.db.models import query, Q
|
2023-11-17 12:25:13 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2015-04-05 10:46:24 +00:00
|
|
|
from orchestra.contrib.contacts import settings as contacts_settings
|
|
|
|
from orchestra.contrib.contacts.models import Contact
|
2014-05-08 16:59:35 +00:00
|
|
|
from orchestra.models.fields import MultiSelectField
|
2015-05-06 19:30:13 +00:00
|
|
|
from orchestra.utils.mail import send_email_template
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
from . import settings
|
|
|
|
|
|
|
|
|
|
|
|
class Queue(models.Model):
|
|
|
|
name = models.CharField(_("name"), max_length=128, unique=True)
|
2015-03-29 16:10:07 +00:00
|
|
|
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
2014-05-08 16:59:35 +00:00
|
|
|
default = models.BooleanField(_("default"), default=False)
|
|
|
|
notify = MultiSelectField(_("notify"), max_length=256, blank=True,
|
2015-04-05 10:46:24 +00:00
|
|
|
choices=Contact.EMAIL_USAGES,
|
|
|
|
default=contacts_settings.CONTACTS_DEFAULT_EMAIL_USAGES,
|
|
|
|
help_text=_("Contacts to notify by email"))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
2015-03-29 16:10:07 +00:00
|
|
|
return self.verbose_name or self.name
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
""" mark as default queue if needed """
|
|
|
|
existing_default = Queue.objects.filter(default=True)
|
|
|
|
if self.default:
|
|
|
|
existing_default.update(default=False)
|
|
|
|
elif not existing_default:
|
|
|
|
self.default = True
|
|
|
|
super(Queue, self).save(*args, **kwargs)
|
|
|
|
|
|
|
|
|
2016-03-31 16:02:50 +00:00
|
|
|
class TicketQuerySet(query.QuerySet):
|
|
|
|
def involved_by(self, user, *args, **kwargs):
|
|
|
|
qset = Q(creator=user) | Q(owner=user) | Q(messages__author=user)
|
|
|
|
return self.filter(qset, *args, **kwargs).distinct()
|
|
|
|
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
class Ticket(models.Model):
|
|
|
|
HIGH = 'HIGH'
|
|
|
|
MEDIUM = 'MEDIUM'
|
|
|
|
LOW = 'LOW'
|
|
|
|
PRIORITIES = (
|
|
|
|
(HIGH, 'High'),
|
|
|
|
(MEDIUM, 'Medium'),
|
|
|
|
(LOW, 'Low'),
|
|
|
|
)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
NEW = 'NEW'
|
|
|
|
IN_PROGRESS = 'IN_PROGRESS'
|
|
|
|
RESOLVED = 'RESOLVED'
|
|
|
|
FEEDBACK = 'FEEDBACK'
|
|
|
|
REJECTED = 'REJECTED'
|
|
|
|
CLOSED = 'CLOSED'
|
|
|
|
STATES = (
|
|
|
|
(NEW, 'New'),
|
|
|
|
(IN_PROGRESS, 'In Progress'),
|
|
|
|
(RESOLVED, 'Resolved'),
|
|
|
|
(FEEDBACK, 'Feedback'),
|
|
|
|
(REJECTED, 'Rejected'),
|
|
|
|
(CLOSED, 'Closed'),
|
|
|
|
)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-07-29 20:10:37 +00:00
|
|
|
creator = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("created by"),
|
2016-04-27 08:35:13 +00:00
|
|
|
related_name='tickets_created', null=True, on_delete=models.SET_NULL)
|
2014-05-13 13:46:40 +00:00
|
|
|
creator_name = models.CharField(_("creator name"), max_length=256, blank=True)
|
2014-07-29 20:10:37 +00:00
|
|
|
owner = models.ForeignKey(djsettings.AUTH_USER_MODEL, null=True, blank=True,
|
2016-04-27 08:35:13 +00:00
|
|
|
on_delete=models.SET_NULL,
|
2015-04-05 10:46:24 +00:00
|
|
|
related_name='tickets_owned', verbose_name=_("assigned to"))
|
2016-04-27 08:35:13 +00:00
|
|
|
queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True,
|
|
|
|
on_delete=models.SET_NULL)
|
2014-05-08 16:59:35 +00:00
|
|
|
subject = models.CharField(_("subject"), max_length=256)
|
|
|
|
description = models.TextField(_("description"))
|
2016-03-31 16:02:50 +00:00
|
|
|
priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES, default=MEDIUM)
|
2014-05-08 16:59:35 +00:00
|
|
|
state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW)
|
2015-07-09 10:19:30 +00:00
|
|
|
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
2014-09-26 15:05:20 +00:00
|
|
|
updated_at = models.DateTimeField(_("modified"), auto_now=True)
|
2016-03-31 16:02:50 +00:00
|
|
|
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), blank=True)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2016-03-31 16:02:50 +00:00
|
|
|
objects = TicketQuerySet.as_manager()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
class Meta:
|
2014-09-26 15:05:20 +00:00
|
|
|
ordering = ['-updated_at']
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
|
|
|
return str(self.pk)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def get_notification_emails(self):
|
|
|
|
""" Get emails of the users related to the ticket """
|
|
|
|
emails = list(settings.ISSUES_SUPPORT_EMAILS)
|
|
|
|
emails.append(self.creator.email)
|
|
|
|
if self.owner:
|
|
|
|
emails.append(self.owner.email)
|
2015-04-28 15:23:57 +00:00
|
|
|
for contact in self.creator.contacts.all():
|
2014-07-23 18:28:40 +00:00
|
|
|
if self.queue and set(contact.email_usage).union(set(self.queue.notify)):
|
2014-05-08 16:59:35 +00:00
|
|
|
emails.append(contact.email)
|
|
|
|
for message in self.messages.distinct('author'):
|
|
|
|
emails.append(message.author.email)
|
|
|
|
return set(emails + self.get_cc_emails())
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def notify(self, message=None, content=None):
|
|
|
|
""" Send an email to ticket stakeholders notifying an state update """
|
|
|
|
emails = self.get_notification_emails()
|
|
|
|
template = 'issues/ticket_notification.mail'
|
|
|
|
html_template = 'issues/ticket_notification_html.mail'
|
|
|
|
context = {
|
|
|
|
'ticket': self,
|
|
|
|
'ticket_message': message
|
|
|
|
}
|
|
|
|
send_email_template(template, context, emails, html=html_template)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
""" notify stakeholders of new ticket """
|
|
|
|
new_issue = not self.pk
|
2014-05-13 13:46:40 +00:00
|
|
|
if not self.creator_name and self.creator:
|
|
|
|
self.creator_name = self.creator.get_full_name()
|
2014-05-08 16:59:35 +00:00
|
|
|
super(Ticket, self).save(*args, **kwargs)
|
|
|
|
if new_issue:
|
|
|
|
# PK should be available for rendering the template
|
|
|
|
self.notify()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def is_involved_by(self, user):
|
|
|
|
""" returns whether user has participated or is referenced on the ticket
|
|
|
|
as owner or member of the group
|
|
|
|
"""
|
|
|
|
return Ticket.objects.filter(pk=self.pk).involved_by(user).exists()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def get_cc_emails(self):
|
|
|
|
return self.cc.split(',') if self.cc else []
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def mark_as_read_by(self, user):
|
2014-05-13 13:46:40 +00:00
|
|
|
self.trackers.get_or_create(user=user)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def mark_as_unread_by(self, user):
|
2014-05-13 13:46:40 +00:00
|
|
|
self.trackers.filter(user=user).delete()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def mark_as_unread(self):
|
2014-05-13 13:46:40 +00:00
|
|
|
self.trackers.all().delete()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def is_read_by(self, user):
|
2014-05-13 13:46:40 +00:00
|
|
|
return self.trackers.filter(user=user).exists()
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def reject(self):
|
|
|
|
self.state = Ticket.REJECTED
|
2016-04-07 11:14:44 +00:00
|
|
|
self.save(update_fields=('state', 'updated_at'))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def resolve(self):
|
|
|
|
self.state = Ticket.RESOLVED
|
2016-04-07 11:14:44 +00:00
|
|
|
self.save(update_fields=('state', 'updated_at'))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def close(self):
|
|
|
|
self.state = Ticket.CLOSED
|
2016-04-07 11:14:44 +00:00
|
|
|
self.save(update_fields=('state', 'updated_at'))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def take(self, user):
|
|
|
|
self.owner = user
|
2016-04-07 11:14:44 +00:00
|
|
|
self.save(update_fields=('state', 'updated_at'))
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Message(models.Model):
|
2021-04-22 08:28:00 +00:00
|
|
|
ticket = models.ForeignKey('issues.Ticket', on_delete=models.CASCADE,
|
|
|
|
verbose_name=_("ticket"), related_name='messages')
|
2021-04-22 08:52:33 +00:00
|
|
|
author = models.ForeignKey(djsettings.AUTH_USER_MODEL, on_delete=models.CASCADE,
|
2021-04-22 08:28:00 +00:00
|
|
|
verbose_name=_("author"), related_name='ticket_messages')
|
2014-05-13 13:46:40 +00:00
|
|
|
author_name = models.CharField(_("author name"), max_length=256, blank=True)
|
2014-05-08 16:59:35 +00:00
|
|
|
content = models.TextField(_("content"))
|
2016-03-31 16:02:50 +00:00
|
|
|
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-13 13:46:40 +00:00
|
|
|
class Meta:
|
2014-09-24 20:09:41 +00:00
|
|
|
get_latest_by = 'id'
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
|
|
|
return "#%i" % self.id
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
""" notify stakeholders of ticket update """
|
|
|
|
if not self.pk:
|
|
|
|
self.ticket.mark_as_unread()
|
2014-05-13 13:46:40 +00:00
|
|
|
self.ticket.mark_as_read_by(self.author)
|
2014-05-08 16:59:35 +00:00
|
|
|
self.ticket.notify(message=self)
|
2014-05-13 13:46:40 +00:00
|
|
|
self.author_name = self.author.get_full_name()
|
2014-05-08 16:59:35 +00:00
|
|
|
super(Message, self).save(*args, **kwargs)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
@property
|
2014-05-08 18:47:16 +00:00
|
|
|
def number(self):
|
2014-05-08 16:59:35 +00:00
|
|
|
return self.ticket.messages.filter(id__lte=self.id).count()
|
|
|
|
|
|
|
|
|
|
|
|
class TicketTracker(models.Model):
|
|
|
|
""" Keeps track of user read tickets """
|
2021-04-22 08:28:00 +00:00
|
|
|
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE,
|
|
|
|
verbose_name=_("ticket"), related_name='trackers')
|
|
|
|
user = models.ForeignKey(djsettings.AUTH_USER_MODEL, on_delete=models.CASCADE,
|
|
|
|
verbose_name=_("user"), related_name='ticket_trackers')
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
class Meta:
|
2014-09-24 20:09:41 +00:00
|
|
|
unique_together = (
|
|
|
|
('ticket', 'user'),
|
|
|
|
)
|