From 8a57f72349e47a1bbb7cd3af6620a4bac6e9a958 Mon Sep 17 00:00:00 2001
From: Timothy Hobbs <timothy@hobbs.cz>
Date: Fri, 11 Oct 2019 17:12:39 +0200
Subject: [PATCH] Further refactor datatables code

---
 helpdesk/query.py       |  13 ++++
 helpdesk/user.py        |  60 ++++++++++++++++++
 helpdesk/views/staff.py | 131 +++++++++++-----------------------------
 3 files changed, 107 insertions(+), 97 deletions(-)
 create mode 100644 helpdesk/user.py

diff --git a/helpdesk/query.py b/helpdesk/query.py
index d821c2c5..81fe4082 100644
--- a/helpdesk/query.py
+++ b/helpdesk/query.py
@@ -1,4 +1,5 @@
 from django.db.models import Q
+from django.core.cache import cache
 
 from model_utils import Choices
 
@@ -87,6 +88,18 @@ def apply_query(queryset, params):
     return queryset
 
 
+def get_query(query, huser):
+    # Prefilter the allowed tickets
+    objects = cache.get(huser.user.email + query)
+    if objects is not None:
+        return objects
+    tickets = huser.get_tickets_in_queues().select_related()
+    query_params = query_from_base64(query)
+    ticket_qs = apply_query(tickets, query_params)
+    cache.set(huser.user.email + query, ticket_qs, timeout=60*60)
+    return ticket_qs
+
+
 ORDER_COLUMN_CHOICES = Choices(
     ('0', 'id'),
     ('2', 'priority'),
diff --git a/helpdesk/user.py b/helpdesk/user.py
new file mode 100644
index 00000000..e2492a41
--- /dev/null
+++ b/helpdesk/user.py
@@ -0,0 +1,60 @@
+from helpdesk.models import (
+    Ticket,
+    Queue
+)
+
+from helpdesk import settings as helpdesk_settings
+
+class HelpdeskUser:
+    def __init__(self, user):
+        self.user = user
+
+    def get_queues(self):
+        """Return the list of Queues the user can access.
+
+        :param user: The User (the class should have the has_perm method)
+        :return: A Python list of Queues
+        """
+        user = self.user
+        all_queues = Queue.objects.all()
+        public_ids = [q.pk for q in
+                      Queue.objects.filter(allow_public_submission=True)]
+        limit_queues_by_user = \
+            helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION \
+            and not user.is_superuser
+        if limit_queues_by_user:
+            id_list = [q.pk for q in all_queues if user.has_perm(q.permission_name)]
+            id_list += public_ids
+            return all_queues.filter(pk__in=id_list)
+        else:
+            return all_queues
+
+    def get_tickets_in_queues(self):
+        return Ticket.objects.filter(queue__in=self.get_queues())
+
+
+    def can_access_queue(self, queue):
+        """Check if a certain user can access a certain queue.
+
+        :param user: The User (the class should have the has_perm method)
+        :param queue: The django-helpdesk Queue instance
+        :return: True if the user has permission (either by default or explicitly), false otherwise
+        """
+        user = self.user
+        if user.is_superuser or not helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION:
+            return True
+        else:
+            return user.has_perm(queue.permission_name)
+
+
+    def can_access_ticket(self, ticket):
+        """Check to see if the user has permission to access
+            a ticket. If not then deny access."""
+        user = self.user
+        if self.can_access_queue(ticket.queue):
+            return True
+        elif user.is_superuser or user.is_staff or \
+             (ticket.assigned_to and user.id == ticket.assigned_to.id):
+            return True
+        else:
+            return False
diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py
index ef59715a..240d97a4 100644
--- a/helpdesk/views/staff.py
+++ b/helpdesk/views/staff.py
@@ -25,16 +25,18 @@ from django.utils.html import escape
 from django import forms
 from django.utils import timezone
 from django.views.generic.edit import FormView, UpdateView
-from django.core.cache import cache
 
 from helpdesk.query import (
     query_to_dict,
+    get_query,
     apply_query,
     query_tickets_by_args,
     query_to_base64,
     query_from_base64,
 )
 
+from helpdesk.user import HelpdeskUser
+
 from helpdesk.serializers import DatatablesTicketSerializer
 
 from helpdesk.decorators import (
@@ -96,44 +98,10 @@ def _get_queue_choices(queues):
     return queue_choices
 
 
-def _get_user_queues(user):
-    """Return the list of Queues the user can access.
-
-    :param user: The User (the class should have the has_perm method)
-    :return: A Python list of Queues
-    """
-    all_queues = Queue.objects.all()
-    public_ids = [q.pk for q in
-                  Queue.objects.filter(allow_public_submission=True)]
-
-    limit_queues_by_user = \
-        helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION \
-        and not user.is_superuser
-    if limit_queues_by_user:
-        id_list = [q.pk for q in all_queues if user.has_perm(q.permission_name)]
-        id_list += public_ids
-        return all_queues.filter(pk__in=id_list)
-    else:
-        return all_queues
-
-
-def _has_access_to_queue(user, queue):
-    """Check if a certain user can access a certain queue.
-
-    :param user: The User (the class should have the has_perm method)
-    :param queue: The django-helpdesk Queue instance
-    :return: True if the user has permission (either by default or explicitly), false otherwise
-    """
-    if user.is_superuser or not helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION:
-        return True
-    else:
-        return user.has_perm(queue.permission_name)
-
-
 def _is_my_ticket(user, ticket):
     """Check to see if the user has permission to access
     a ticket. If not then deny access."""
-    if _has_access_to_queue(user, ticket.queue):
+    if (user, ticket.queue):
         return True
     elif user.is_superuser or user.is_staff or \
             (ticket.assigned_to and user.id == ticket.assigned_to.id):
@@ -163,7 +131,7 @@ def dashboard(request):
         assigned_to=request.user,
         status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS])
 
-    user_queues = _get_user_queues(request.user)
+    user_queues = HelpdeskUser(request.user).get_queues()
 
     unassigned_tickets = active_tickets.filter(
         assigned_to__isnull=True,
@@ -189,7 +157,7 @@ def dashboard(request):
     # Queue 1    10     4
     # Queue 2     4    12
 
-    queues = _get_user_queues(request.user).values_list('id', flat=True)
+    queues = HelpdeskUser(request.user).get_queues().values_list('id', flat=True)
 
     from_clause = """FROM    helpdesk_ticket t,
                     helpdesk_queue q"""
@@ -210,14 +178,17 @@ def dashboard(request):
 
 dashboard = staff_member_required(dashboard)
 
+def ticket_perm_check(request, ticket):
+    huser = HelpdeskUser(request.user)
+    if not huser.can_access_queue(ticket.queue):
+        raise PermissionDenied()
+    if not huser.can_access_ticket(ticket):
+        raise PermissionDenied()
 
 @helpdesk_staff_member_required
 def delete_ticket(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if request.method == 'GET':
         return render(request, 'helpdesk/delete_ticket.html', {
@@ -236,10 +207,7 @@ 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 not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if request.method == 'GET':
         form = EditFollowUpForm(initial={
@@ -311,10 +279,7 @@ followup_delete = staff_member_required(followup_delete)
 @helpdesk_staff_member_required
 def view_ticket(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if 'take' in request.GET:
         # Allow the user to assign the ticket to themselves whilst viewing it.
@@ -360,7 +325,7 @@ def view_ticket(request, ticket_id):
     else:
         users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
 
-    queues = _get_user_queues(request.user)
+    queues = HelpdeskUser(request.user).get_queues()
     queue_choices = _get_queue_choices(queues)
     # TODO: shouldn't this template get a form to begin with?
     form = TicketForm(initial={'due_date': ticket.due_date},
@@ -757,8 +722,9 @@ def mass_update(request):
         user = request.user
         action = 'assign'
 
+    huser = HelpdeskUser(request.user)
     for t in Ticket.objects.filter(id__in=tickets):
-        if not _has_access_to_queue(request.user, t.queue):
+        if not huser.can_access_queue(t.queue):
             continue
 
         if action == 'assign' and t.assigned_to != user:
@@ -838,9 +804,7 @@ mass_update = staff_member_required(mass_update)
 def ticket_list(request):
     context = {}
 
-    user_queues = _get_user_queues(request.user)
-    # Prefilter the allowed tickets
-    base_tickets = Ticket.objects.filter(queue__in=user_queues)
+    huser = HelpdeskUser(request.user)
 
     # Query_params will hold a dictionary of parameters relating to
     # a query, to be saved if needed:
@@ -885,7 +849,7 @@ def ticket_list(request):
 
         if filter:
             try:
-                ticket = base_tickets.get(**filter)
+                ticket = huser.get_tickets_in_queues.get(**filter)
                 return HttpResponseRedirect(ticket.staff_url)
             except Ticket.DoesNotExist:
                 # Go on to standard keyword searching
@@ -940,17 +904,9 @@ def ticket_list(request):
         sortreverse = request.GET.get('sortreverse', None)
         query_params['sortreverse'] = sortreverse
 
-    tickets = base_tickets.select_related()
-
-    try:
-        ticket_qs = apply_query(tickets, query_params)
-    except ValidationError:
-        # invalid parameters in query, return default query
-        ticket_qs = apply_query(tickets, default_query_params)
-
     urlsafe_query = query_to_base64(query_params)
 
-    cache.set(request.user.email + urlsafe_query, ticket_qs, timeout=60*60)
+    get_query(urlsafe_query, huser)
 
     user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
 
@@ -969,7 +925,7 @@ def ticket_list(request):
         context,
         default_tickets_per_page=request.user.usersettings_helpdesk.tickets_per_page,
         user_choices=User.objects.filter(is_active=True, is_staff=True),
-        queue_choices=user_queues,
+        queue_choices=huser.get_queues(),
         status_choices=Ticket.STATUS_CHOICES,
         urlsafe_query=urlsafe_query,
         user_saved_queries=user_saved_queries,
@@ -1019,8 +975,7 @@ def datatables_ticket_list(request, query):
     on the table. query_tickets_by_args is at lib.py, DatatablesTicketSerializer is in
     serializers.py. The serializers and this view use django-rest_framework methods
     """
-    objects = cache.get(request.user.email + query)
-    query_params = query_from_base64(query)
+    objects = get_query(query, HelpdeskUser(request.user))
     model_object = query_tickets_by_args(objects, '-date_created', **request.query_params)
     serializer = DatatablesTicketSerializer(model_object['items'], many=True)
     result = dict()
@@ -1034,10 +989,7 @@ def datatables_ticket_list(request, query):
 @helpdesk_staff_member_required
 def edit_ticket(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if request.method == 'POST':
         form = EditTicketForm(request.POST, instance=ticket)
@@ -1068,7 +1020,7 @@ class CreateTicketView(MustBeStaffMixin, FormView):
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
-        queues = _get_user_queues(self.request.user)
+        queues = HelpdeskUser(self.request.user).get_queues()
         kwargs["queue_choices"] = _get_queue_choices(queues)
         return kwargs
 
@@ -1078,7 +1030,7 @@ class CreateTicketView(MustBeStaffMixin, FormView):
 
     def get_success_url(self):
         request = self.request
-        if _has_access_to_queue(request.user, self.ticket.queue):
+        if HelpdeskUser(request.user).can_access_queue(self.ticket.queue):
             return self.ticket.get_absolute_url()
         else:
             return reverse('helpdesk:dashboard')
@@ -1109,10 +1061,7 @@ raw_details = staff_member_required(raw_details)
 @helpdesk_staff_member_required
 def hold_ticket(request, ticket_id, unhold=False):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if unhold:
         ticket.on_hold = False
@@ -1159,7 +1108,7 @@ def report_index(request):
     number_tickets = Ticket.objects.all().count()
     saved_query = request.GET.get('saved_query', None)
 
-    user_queues = _get_user_queues(request.user)
+    user_queues = HelpdeskUser(request.user).get_queues()
     Tickets = Ticket.objects.filter(queue__in=user_queues)
     basic_ticket_stats = calc_basic_ticket_stats(Tickets)
 
@@ -1202,7 +1151,7 @@ def run_report(request, report):
         return HttpResponseRedirect(reverse("helpdesk:report_index"))
 
     report_queryset = Ticket.objects.all().select_related().filter(
-        queue__in=_get_user_queues(request.user)
+        queue__in=HelpdeskUser(request.user).get_queues()
     )
 
     try:
@@ -1252,7 +1201,7 @@ def run_report(request, report):
     elif report == 'userqueue':
         title = _('User by Queue')
         col1heading = _('User')
-        queue_options = _get_user_queues(request.user)
+        queue_options = HelpdeskUser(request.user).get_queues()
         possible_options = [q.title for q in queue_options]
         charttype = 'bar'
 
@@ -1467,10 +1416,7 @@ email_ignore_del = superuser_required(email_ignore_del)
 @helpdesk_staff_member_required
 def ticket_cc(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     copies_to = ticket.ticketcc_set.all()
     return render(request, 'helpdesk/ticket_cc_list.html', {
@@ -1485,10 +1431,7 @@ ticket_cc = staff_member_required(ticket_cc)
 @helpdesk_staff_member_required
 def ticket_cc_add(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     if request.method == 'POST':
         form = TicketCCForm(request.POST)
@@ -1528,10 +1471,7 @@ ticket_cc_del = staff_member_required(ticket_cc_del)
 @helpdesk_staff_member_required
 def ticket_dependency_add(request, ticket_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
     if request.method == 'POST':
         form = TicketDependencyForm(request.POST)
         if form.is_valid():
@@ -1566,10 +1506,7 @@ ticket_dependency_del = staff_member_required(ticket_dependency_del)
 @helpdesk_staff_member_required
 def attachment_del(request, ticket_id, attachment_id):
     ticket = get_object_or_404(Ticket, id=ticket_id)
-    if not _has_access_to_queue(request.user, ticket.queue):
-        raise PermissionDenied()
-    if not _is_my_ticket(request.user, ticket):
-        raise PermissionDenied()
+    ticket_perm_check(request, ticket)
 
     attachment = get_object_or_404(FollowUpAttachment, id=attachment_id)
     if request.method == 'POST':