Newer
Older
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.contenttypes.models import ContentType
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView
from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import (
assign_perm,
remove_perm,
get_users_with_perms,
get_groups_with_perms,
get_objects_for_user,
)
from scipost.mixins import PermissionsMixin
TICKET_STATUS_UNASSIGNED,
TICKET_STATUS_ASSIGNED,
TICKET_STATUS_PICKEDUP,
TICKET_STATUS_PASSED_ON,
TICKET_STATUS_AWAITING_RESPONSE_ASSIGNEE,
TICKET_STATUS_AWAITING_RESPONSE_USER,
TICKET_STATUS_RESOLVED,
TICKET_STATUS_CLOSED,
TICKET_FOLLOWUP_ACTION_UPDATE,
TICKET_FOLLOWUP_ACTION_RESPONDED_TO_USER,
TICKET_FOLLOWUP_ACTION_MARK_RESOLVED,
TICKET_FOLLOWUP_ACTION_MARK_CLOSED,
)
from .forms import (
QueueForm,
TicketForm,
TicketAssignForm,
FollowupForm,
TicketSearchForm,
)
class HelpdeskView(LoginRequiredMixin, ListView):
template_name = "helpdesk/helpdesk.html"
return get_objects_for_user(
self.request.user, "helpdesk.can_view_ticket"
).assigned_to_others(self.request.user)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["managed_queues"] = get_objects_for_user(
self.request.user, "helpdesk.can_manage_queue"
).anchors()
context["visible_queues"] = get_objects_for_user(
self.request.user, "helpdesk.can_view_queue"
).anchors()
class QueueCreateView(PermissionsMixin, CreateView):
"""
Add a new Queue. Accessible to users with permission: can_add_queue.
"""
permission_required = "helpdesk.add_queue"
form_class = QueueForm
template_name = "helpdesk/queue_form.html"
def get_initial(self, *args, **kwargs):
initial = super().get_initial(*args, **kwargs)
parent_slug = self.kwargs.get("parent_slug")
parent_queue = get_object_or_404(Queue, slug=parent_slug)
initial.update(
{
"managing_group": parent_queue.managing_group,
"response_groups": parent_queue.response_groups.all(),
"parent_queue": parent_queue,
}
)
def form_valid(self, form):
"""
Assign appropriate object-level permissions to managing and response groups.
"""
self.object = form.save()
assign_perm(
"can_manage_queue", form.cleaned_data["managing_group"], self.object
)
assign_perm(
"can_handle_queue", form.cleaned_data["managing_group"], self.object
)
assign_perm("can_view_queue", form.cleaned_data["managing_group"], self.object)
for group in form.cleaned_data["response_groups"].all():
assign_perm("can_handle_queue", group, self.object)
assign_perm("can_view_queue", group, self.object)
class QueueUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = "helpdesk.can_manage_queue"
form_class = QueueForm
template_name = "helpdesk/queue_form.html"
def form_valid(self, form):
"""
Update object-level permissions: remove all existing, then reassign.
"""
groups_perms_dict = get_groups_with_perms(self.object, attach_perms=True)
for group, perms_list in groups_perms_dict.items():
for perm in perms_list:
assign_perm(
"can_manage_queue", form.cleaned_data["managing_group"], self.object
)
assign_perm(
"can_handle_queue", form.cleaned_data["managing_group"], self.object
)
assign_perm("can_view_queue", form.cleaned_data["managing_group"], self.object)
for group in form.cleaned_data["response_groups"].all():
assign_perm("can_handle_queue", group, self.object)
assign_perm("can_view_queue", group, self.object)
class QueueDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = "helpdesk.can_manage_queue"
success_url = reverse_lazy("helpdesk:helpdesk")
def delete(self, request, *args, **kwargs):
"""
A Queue can only be deleted if it has no descendant Queues.
Upon deletion, all object-level permissions associated to the
Queue are explicitly removed, to avoid orphaned permissions.
"""
queue = get_object_or_404(Queue, slug=self.kwargs.get("slug"))
groups_perms_dict = get_groups_with_perms(queue, attach_perms=True)
if queue.sub_queues.all().count() > 0:
messages.warning(request, "A Queue with sub-queues cannot be deleted.")
return redirect(queue.get_absolute_url())
for group, perms_list in groups_perms_dict.items():
for perm in perms_list:
remove_perm(perm, group, queue)
return super().delete(request, *args, **kwargs)
class QueueDetailView(PermissionRequiredMixin, DetailView):
permission_required = "helpdesk.can_view_queue"
template_name = "helpdesk/queue_detail.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["users_with_perms"] = get_users_with_perms(self.object)
class TicketCreateView(LoginRequiredMixin, CreateView):
model = Ticket
form_class = TicketForm
template_name = "helpdesk/ticket_form.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
concerning_type_id = self.kwargs.get("concerning_type_id")
concerning_object_id = self.kwargs.get("concerning_object_id")
if concerning_type_id and concerning_object_id:
concerning_object_type = ContentType.objects.get_for_id(concerning_type_id)
concerning_object = concerning_object_type.get_object_for_this_type(
pk=concerning_object_id
)
context["concerning_object"] = concerning_object
def get_initial(self, *args, **kwargs):
initial = super().get_initial(*args, **kwargs)
initial.update(
{
"defined_on": timezone.now(),
"defined_by": self.request.user,
"status": TICKET_STATUS_UNASSIGNED,
}
)
concerning_type_id = self.kwargs.get("concerning_type_id")
concerning_object_id = self.kwargs.get("concerning_object_id")
concerning_object_type = ContentType.objects.get_for_id(
concerning_type_id
)
initial.update(
{
"concerning_object_type": concerning_object_type,
"concerning_object_id": concerning_object_id,
}
)
except KeyError:
pass
return initial
class TicketUpdateView(UserPassesTestMixin, UpdateView):
model = Ticket
form_class = TicketForm
template_name = "helpdesk/ticket_form.html"
ticket = get_object_or_404(Ticket, pk=self.kwargs.get("pk"))
return self.request.user.groups.filter(
name=ticket.queue.managing_group.name
).exists()
ticket = get_object_or_404(Ticket, pk=self.kwargs.get("pk"))
text = "Ticket updated by %s" % (self.request.user.get_full_name())
followup = Followup(
ticket=ticket,
text=text,
by=self.request.user,
timestamp=timezone.now(),
action=TICKET_FOLLOWUP_ACTION_UPDATE,
)
followup.save()
return super().form_valid(form)
class TicketDeleteView(UserPassesTestMixin, DeleteView):
model = Ticket
success_url = reverse_lazy("helpdesk:helpdesk")
def test_func(self):
ticket = get_object_or_404(Ticket, pk=self.kwargs.get("pk"))
return self.request.user.groups.filter(
name=ticket.queue.managing_group.name
).exists()
class TicketAssignView(UserPassesTestMixin, UpdateView):
model = Ticket
form_class = TicketAssignForm
template_name = "helpdesk/ticket_assign.html"
def test_func(self):
ticket = get_object_or_404(Ticket, pk=self.kwargs.get("pk"))
return self.request.user.groups.filter(
name=ticket.queue.managing_group.name
).exists()
def form_valid(self, form):
self.object.status = TICKET_STATUS_ASSIGNED
return super().form_valid(form)
def is_ticket_creator_or_handler(request, pk):
"""Details of a ticket can only be viewed by ticket creator, or handlers."""
ticket = get_object_or_404(Ticket, pk=pk)
if request.user == ticket.defined_by:
return True
elif request.user.has_perm("can_view_queue", ticket.queue):
elif request.user.has_perm("can_view_ticket", ticket):
return True
return False
class TicketDetailView(UserPassesTestMixin, DetailView):
template_name = "helpdesk/ticket_detail.html"
return self.request.user.is_authenticated and is_ticket_creator_or_handler(
self.request, self.kwargs.get("pk")
)
class TicketFollowupView(UserPassesTestMixin, CreateView):
model = Followup
form_class = FollowupForm
template_name = "helpdesk/followup_form.html"
return self.request.user.is_authenticated and is_ticket_creator_or_handler(
self.request, self.kwargs.get("pk")
)
def get_initial(self):
initial = super().get_initial()
ticket = get_object_or_404(Ticket, pk=self.kwargs.get("pk"))
if self.request.user == ticket.defined_by:
action = TICKET_FOLLOWUP_ACTION_USER_RESPONDED
else:
action = TICKET_FOLLOWUP_ACTION_RESPONDED_TO_USER
initial.update(
{
"ticket": ticket,
"by": self.request.user,
"timestamp": timezone.now(),
"action": action,
}
)
ticket = form.cleaned_data["ticket"]
if self.request.user == ticket.defined_by:
ticket.status = TICKET_STATUS_AWAITING_RESPONSE_ASSIGNEE
else:
ticket.status = TICKET_STATUS_AWAITING_RESPONSE_USER
ticket.save()
queue_managers = User.objects.filter(
groups__name=ticket.queue.managing_group.name
)
bcc_emails = [k["email"] for k in list(queue_managers.all().values("email"))]
if ticket.assigned_to and ticket.assigned_to.email not in bcc_emails:
bcc_emails.append(ticket.assigned_to.email)
mail_sender = DirectMailUtil(
"helpdesk/followup_on_ticket",
delayed_processing=False,
bcc=bcc_emails,
followup=self.object,
)
mail_sender.send_mail()
return redirect(self.get_success_url())
class TicketMarkResolved(TicketFollowupView):
def get_initial(self):
initial = super().get_initial()
text = "%s %s marked this ticket as Resolved." % (
self.request.user.first_name,
self.request.user.last_name,
)
initial.update({"text": text, "action": TICKET_FOLLOWUP_ACTION_MARK_RESOLVED})
return initial
def form_valid(self, form):
ticket = form.cleaned_data["ticket"]
ticket.status = TICKET_STATUS_RESOLVED
ticket.save()
self.object = form.save()
return redirect(self.get_success_url())
class TicketMarkClosed(TicketFollowupView):
def get_initial(self):
initial = super().get_initial()
text = "%s %s marked this ticket as Closed." % (
self.request.user.first_name,
self.request.user.last_name,
)
initial.update({"text": text, "action": TICKET_FOLLOWUP_ACTION_MARK_CLOSED})
return initial
def form_valid(self, form):
ticket = form.cleaned_data["ticket"]
ticket.status = TICKET_STATUS_CLOSED
ticket.save()
self.object = form.save()
return redirect(self.get_success_url())
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def _hx_ticket_search_form(request, filter_set: str, queue_slug=None):
queue = get_object_or_404(Queue, slug=queue_slug) if queue_slug else None
form = TicketSearchForm(
request.POST or None,
user=request.user,
queue=queue,
session_key=request.session.session_key,
)
if filter_set == "empty":
form.apply_filter_set(
{
"show_email_unknown": True,
"show_with_CI": True,
"show_unavailable": True,
},
none_on_empty=True,
)
context = {"form": form, "queue": queue}
return render(request, "helpdesk/_hx_ticket_search_form.html", context)
def _hx_ticket_search_table(request, queue_slug=None):
queue = get_object_or_404(Queue, slug=queue_slug) if queue_slug else None
form = TicketSearchForm(
request.POST or None,
user=request.user,
queue=queue,
session_key=request.session.session_key,
)
if form.is_valid():
tickets = form.search_results()
else:
tickets = form.tickets
paginator = Paginator(tickets, 16)
page_nr = request.GET.get("page")
page_obj = paginator.get_page(page_nr)
count = paginator.count
start_index = page_obj.start_index
context = {
"queue": queue,
"count": count,
"page_obj": page_obj,
"start_index": start_index,
}
return render(request, "helpdesk/_hx_ticket_search_table.html", context)