From 47c5731bddc7923d012430399197a3f8ef64c582 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 29 Oct 2024 18:15:00 +0000 Subject: [PATCH] Some cleanup to tickets list view --- temba/tickets/models.py | 25 +++---- temba/tickets/views.py | 148 ++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 87 deletions(-) diff --git a/temba/tickets/models.py b/temba/tickets/models.py index 03b25f35f3..f1f32bf006 100644 --- a/temba/tickets/models.py +++ b/temba/tickets/models.py @@ -301,7 +301,7 @@ class TicketFolder(metaclass=ABCMeta): icon = None verbose_name = None - def get_queryset(self, org, user, ordered): + def get_queryset(self, org, user, *, ordered: bool): qs = org.tickets.all() if ordered: @@ -325,20 +325,20 @@ def all(cls): class MineFolder(TicketFolder): """ - Tickets assigned to the current user + Tickets assigned to the current user. """ slug = "mine" name = _("My Tickets") icon = "tickets_mine" - def get_queryset(self, org, user, ordered): - return super().get_queryset(org, user, ordered).filter(assignee=user) + def get_queryset(self, org, user, *, ordered: bool): + return super().get_queryset(org, user, ordered=ordered).filter(assignee=user) class UnassignedFolder(TicketFolder): """ - Tickets not assigned to any user + Tickets not assigned to any user. """ slug = "unassigned" @@ -346,13 +346,13 @@ class UnassignedFolder(TicketFolder): verbose_name = _("Unassigned Tickets") icon = "tickets_unassigned" - def get_queryset(self, org, user, ordered): - return super().get_queryset(org, user, ordered).filter(assignee=None) + def get_queryset(self, org, user, *, ordered: bool): + return super().get_queryset(org, user, ordered=ordered).filter(assignee=None) class AllFolder(TicketFolder): """ - All tickets + All tickets the user can access. """ slug = "all" @@ -360,16 +360,13 @@ class AllFolder(TicketFolder): verbose_name = _("All Tickets") icon = "tickets_all" - def get_queryset(self, org, user, ordered): - return super().get_queryset(org, user, ordered) - FOLDERS = {f.slug: f() for f in TicketFolder.__subclasses__()} class TopicFolder(TicketFolder): """ - Tickets with a specific topic + Wraps a topic so we can use it like a folder. """ def __init__(self, topic: Topic): @@ -377,8 +374,8 @@ def __init__(self, topic: Topic): self.name = topic.name self.topic = topic - def get_queryset(self, org, user, ordered): - return super().get_queryset(org, user, ordered).filter(topic=self.topic) + def get_queryset(self, org, user, *, ordered: bool): + return super().get_queryset(org, user, ordered=ordered).filter(topic=self.topic) class TicketCount(SquashableModel): diff --git a/temba/tickets/views.py b/temba/tickets/views.py index 8be95f82f7..088ec65f5d 100644 --- a/temba/tickets/views.py +++ b/temba/tickets/views.py @@ -217,7 +217,7 @@ def derive_menu(self): class List(SpaMixin, ContextMenuMixin, OrgPermsMixin, NotificationTargetMixin, SmartListView): """ - A placeholder view for the ticket handling frontend components which fetch tickets from the endpoint below + Placeholder view for the ticketing frontend components which fetch tickets from the folders view below. """ @classmethod @@ -226,105 +226,107 @@ def derive_url_pattern(cls, path, action): return rf"^ticket/((?P{folders}|{UUID_REGEX.pattern})/((?Popen|closed)/((?P[a-z0-9\-]+)/)?)?)?$" def get_notification_scope(self) -> tuple: - folder, status, _, _ = self.tickets_path - if folder == UnassignedFolder.slug and status == "open": + folder, status, ticket, in_page = self.tickets_path + + if folder.slug == UnassignedFolder.slug and status == Ticket.STATUS_OPEN: return "tickets:opened", "" - elif folder == MineFolder.slug and status == "open": + elif folder.slug == MineFolder.slug and status == Ticket.STATUS_OPEN: return "tickets:activity", "" return "", "" def derive_menu_path(self): - return f"/ticket/{self.kwargs.get('folder', 'mine')}/" + folder, status, ticket, in_page = self.tickets_path + + return f"/ticket/{folder.slug}/" @cached_property - def tickets_path(self) -> tuple: + def tickets_path(self) -> tuple[TicketFolder, str, Ticket, bool]: """ - Returns tuple of folder, status, ticket uuid, and whether that ticket exists in first page of tickets + Returns tuple of folder, status, ticket, and whether that ticket exists in first page of tickets """ - folder = self.kwargs.get("folder") - status = self.kwargs.get("status") - uuid = self.kwargs.get("uuid") + + # get requested folder, defaulting to Mine + folder = TicketFolder.from_slug(self.request.org, self.kwargs.get("folder", MineFolder.slug)) + if not folder: + raise Http404() + + status = Ticket.STATUS_OPEN if self.kwargs.get("status", "open") == "open" else Ticket.STATUS_CLOSED + ticket = None in_page = False - # if we have a uuid make sure it is in our first page of tickets - if uuid: - status_code = Ticket.STATUS_OPEN if status == "open" else Ticket.STATUS_CLOSED + # is the request for a specific ticket? + if uuid := self.kwargs.get("uuid"): org = self.request.org user = self.request.user - ticket_folder = TicketFolder.from_slug(org, folder) - if not ticket_folder: - raise Http404() + # is the ticket in the first page from of current folder? + for t in list(folder.get_queryset(org, user, ordered=True).filter(status=status)[:25]): + if str(t.uuid) == uuid: + ticket = t + in_page = True + break - tickets = list(ticket_folder.get_queryset(org, user, True).filter(status=status_code)[:25]) + # if not, see if we can access it in the All tickets folder and if so switch to that + if not in_page: + all_folder = TicketFolder.from_slug(self.request.org, AllFolder.slug) + ticket = all_folder.get_queryset(org, user, ordered=False).filter(uuid=uuid).first() - found = list(filter(lambda t: str(t.uuid) == uuid, tickets)) - if found: - in_page = True - else: - # if it's not, switch our folder to everything with that ticket's state - ticket = org.tickets.filter(uuid=uuid).first() if ticket: - folder = AllFolder.slug - status = "open" if ticket.status == Ticket.STATUS_OPEN else "closed" + folder = all_folder + status = ticket.status - return folder or MineFolder.slug, status or "open", uuid, in_page + return folder, status, ticket, in_page def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - folder, status, uuid, in_page = self.tickets_path - context["folder"] = folder - context["status"] = status - context["has_tickets"] = self.request.org.tickets.exists() + folder, status, ticket, in_page = self.tickets_path - folder = TicketFolder.from_slug(self.request.org, folder) context["title"] = folder.name + context["folder"] = folder.slug + context["status"] = "open" if status == Ticket.STATUS_OPEN else "closed" + context["has_tickets"] = self.request.org.tickets.exists() - if uuid: - context["nextUUID" if in_page else "uuid"] = uuid + if ticket: + context["nextUUID" if in_page else "uuid"] = str(ticket.uuid) return context def build_context_menu(self, menu): - uuid = self.kwargs.get("uuid") - if uuid: - ticket = self.request.org.tickets.filter(uuid=uuid).first() - if ticket: - if ticket.status == Ticket.STATUS_OPEN: - if self.has_org_perm("tickets.ticket_update"): - menu.add_modax( - _("Edit"), - "edit-ticket", - f"{reverse('tickets.ticket_update', args=[ticket.uuid])}", - title=_("Edit Ticket"), - on_submit="handleTicketEditComplete()", - ) - - if self.has_org_perm("tickets.ticket_note"): - menu.add_modax( - _("Add Note"), - "add-note", - f"{reverse('tickets.ticket_note', args=[ticket.uuid])}", - on_submit="handleNoteAdded()", - ) - - # we don't want to show start flow if interrupt was given as an option - interrupt_added = False - if self.has_org_perm("contacts.contact_interrupt") and ticket.contact.current_flow: - menu.add_url_post( - _("Interrupt"), reverse("contacts.contact_interrupt", args=(ticket.contact.id,)) - ) - interrupt_added = True - - if not interrupt_added and self.has_org_perm("flows.flow_start"): - menu.add_modax( - _("Start Flow"), - "start-flow", - f"{reverse('flows.flow_start')}?c={ticket.contact.uuid}", - disabled=True, - on_submit="handleFlowStarted()", - ) + folder, status, ticket, in_page = self.tickets_path + + if ticket and ticket.status == Ticket.STATUS_OPEN: + if self.has_org_perm("tickets.ticket_update"): + menu.add_modax( + _("Edit"), + "edit-ticket", + f"{reverse('tickets.ticket_update', args=[ticket.uuid])}", + title=_("Edit Ticket"), + on_submit="handleTicketEditComplete()", + ) + + if self.has_org_perm("tickets.ticket_note"): + menu.add_modax( + _("Add Note"), + "add-note", + f"{reverse('tickets.ticket_note', args=[ticket.uuid])}", + on_submit="handleNoteAdded()", + ) + + # we don't want to show start flow if interrupt was given as an option + interrupt_added = False + if self.has_org_perm("contacts.contact_interrupt") and ticket.contact.current_flow: + menu.add_url_post(_("Interrupt"), reverse("contacts.contact_interrupt", args=(ticket.contact.id,))) + interrupt_added = True + + if not interrupt_added and self.has_org_perm("flows.flow_start"): + menu.add_modax( + _("Start Flow"), + "start-flow", + f"{reverse('flows.flow_start')}?c={ticket.contact.uuid}", + disabled=True, + on_submit="handleFlowStarted()", + ) def get_queryset(self, **kwargs): return super().get_queryset(**kwargs).none() @@ -374,7 +376,7 @@ def get_queryset(self, **kwargs): # fetching new activity gets a different order later ordered = False if after else True - qs = self.folder.get_queryset(org, user, ordered).filter(status=status) + qs = self.folder.get_queryset(org, user, ordered=ordered).filter(status=status) # all new activity after = int(self.request.GET.get("after", 0)) @@ -395,7 +397,7 @@ def get_queryset(self, **kwargs): if count == self.paginate_by: last_ticket = qs[len(qs) - 1] - qs = self.folder.get_queryset(org, user, ordered).filter( + qs = self.folder.get_queryset(org, user, ordered=ordered).filter( status=status, last_activity_on__gte=last_ticket.last_activity_on )