Skip to content

Commit

Permalink
Merge branch 'main' into team_list
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Oct 28, 2024
2 parents 51188d2 + 0ca6d9e commit a7dcb61
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 90 deletions.
55 changes: 27 additions & 28 deletions temba/tickets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from temba.utils.dates import date_range
from temba.utils.export import MultiSheetExporter
from temba.utils.models import DailyCountModel, DailyTimingModel, SquashableModel, TembaModel
from temba.utils.uuid import uuid4
from temba.utils.uuid import is_uuid, uuid4

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -296,54 +296,39 @@ class Meta:


class TicketFolder(metaclass=ABCMeta):
id = None
slug = None
name = None
icon = None
verbose_name = None

def get_queryset(self, org, user, ordered):
qs = Ticket.objects.filter(org=org)
qs = org.tickets.all()

if ordered:
qs = qs.order_by("-last_activity_on", "-id")

return qs.select_related("topic", "assignee").prefetch_related("contact")

@classmethod
def from_id(cls, org, id: str):
folder = FOLDERS.get(id, None)
if not folder:
topic = Topic.objects.filter(org=org, uuid=id).first()
def from_slug(cls, org, slug_or_uuid: str):
if is_uuid(slug_or_uuid):
topic = org.topics.filter(uuid=slug_or_uuid, is_active=True).first()
if topic:
folder = TopicFolder(topic)
return folder
return TopicFolder(topic)

return FOLDERS.get(slug_or_uuid, None)

@classmethod
def all(cls):
return FOLDERS


class TopicFolder(TicketFolder):
"""
Tickets assigned to the current user
"""

def __init__(self, topic: Topic):
self.topic = topic
self.id = topic.uuid
self.name = topic.name
self.is_system = topic.is_system

def get_queryset(self, org, user, ordered):
return super().get_queryset(org, user, ordered).filter(topic=self.topic)


class MineFolder(TicketFolder):
"""
Tickets assigned to the current user
"""

id = "mine"
slug = "mine"
name = _("My Tickets")
icon = "tickets_mine"

Expand All @@ -356,7 +341,7 @@ class UnassignedFolder(TicketFolder):
Tickets not assigned to any user
"""

id = "unassigned"
slug = "unassigned"
name = _("Unassigned")
verbose_name = _("Unassigned Tickets")
icon = "tickets_unassigned"
Expand All @@ -370,7 +355,7 @@ class AllFolder(TicketFolder):
All tickets
"""

id = "all"
slug = "all"
name = _("All")
verbose_name = _("All Tickets")
icon = "tickets_all"
Expand All @@ -379,7 +364,21 @@ def get_queryset(self, org, user, ordered):
return super().get_queryset(org, user, ordered)


FOLDERS = {f.id: f() for f in TicketFolder.__subclasses__() if f.id}
FOLDERS = {f.slug: f() for f in TicketFolder.__subclasses__()}


class TopicFolder(TicketFolder):
"""
Tickets with a specific topic
"""

def __init__(self, topic: Topic):
self.slug = topic.uuid
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)


class TicketCount(SquashableModel):
Expand Down
6 changes: 3 additions & 3 deletions temba/tickets/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def test_create(self):
def test_update(self):
topic = Topic.create(self.org, self.admin, "Hot Topic")

update_url = reverse("tickets.topic_update", args=[topic.uuid])
update_url = reverse("tickets.topic_update", args=[topic.id])

self.assertRequestDisallowed(update_url, [None, self.user, self.agent, self.admin2])

Expand All @@ -407,14 +407,14 @@ def test_update(self):

# can't edit a system topic
self.assertRequestDisallowed(
reverse("tickets.topic_update", args=[self.org.default_ticket_topic.uuid]), [self.admin]
reverse("tickets.topic_update", args=[self.org.default_ticket_topic.id]), [self.admin]
)

def test_delete(self):
topic1 = Topic.create(self.org, self.admin, "Planes")
topic2 = Topic.create(self.org, self.admin, "Trains")

delete_url = reverse("tickets.topic_delete", args=[topic1.uuid])
delete_url = reverse("tickets.topic_delete", args=[topic1.id])

self.assertRequestDisallowed(delete_url, [None, self.user, self.agent, self.admin2])

Expand Down
95 changes: 36 additions & 59 deletions temba/tickets/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from datetime import timedelta

from smartmin.views import SmartCRUDL, SmartDeleteView, SmartListView, SmartTemplateView, SmartUpdateView
from smartmin.views import SmartCRUDL, SmartListView, SmartTemplateView, SmartUpdateView

from django import forms
from django.db.models.aggregates import Max
from django.db.models.functions import Lower
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.http import Http404, JsonResponse
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
Expand Down Expand Up @@ -95,29 +95,17 @@ def save(self, obj):
class Update(BaseUpdateModal):
form_class = TopicForm
success_url = "hide"
slug_url_kwarg = "uuid"

class Delete(ModalFormMixin, OrgObjPermsMixin, SmartDeleteView):
default_template = "smartmin/delete_confirm.html"
submit_button_name = _("Delete")
slug_url_kwarg = "uuid"
fields = ("uuid",)
class Delete(BaseDeleteModal):
cancel_url = "@tickets.ticket_list"
redirect_url = "@tickets.ticket_list"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["has_tickets"] = self.object.tickets.exists()
return context

def post(self, request, *args, **kwargs):
self.get_object().release(self.request.user)
redirect_url = self.get_redirect_url()
return HttpResponseRedirect(redirect_url)

def get_redirect_url(self, **kwargs):
default_topic = self.get_object().org.topics.filter(is_default=True).first()
return f"/ticket/{str(default_topic.uuid)}/open/"
return f"/ticket/{self.request.org.default_ticket_topic.uuid}/open/"


class TeamCRUDL(SmartCRUDL):
Expand Down Expand Up @@ -199,9 +187,9 @@ def derive_url_pattern(cls, path, action):

def get_notification_scope(self) -> tuple:
folder, status, _, _ = self.tickets_path
if folder == UnassignedFolder.id and status == "open":
if folder == UnassignedFolder.slug and status == "open":
return "tickets:opened", ""
elif folder == MineFolder.id and status == "open":
elif folder == MineFolder.slug and status == "open":
return "tickets:activity", ""
return "", ""

Expand All @@ -223,7 +211,7 @@ def tickets_path(self) -> tuple:
status_code = Ticket.STATUS_OPEN if status == "open" else Ticket.STATUS_CLOSED
org = self.request.org
user = self.request.user
ticket_folder = TicketFolder.from_id(org, folder)
ticket_folder = TicketFolder.from_slug(org, folder)

if not ticket_folder:
raise Http404()
Expand All @@ -237,10 +225,10 @@ def tickets_path(self) -> tuple:
# 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.id
folder = AllFolder.slug
status = "open" if ticket.status == Ticket.STATUS_OPEN else "closed"

return folder or MineFolder.id, status or "open", uuid, in_page
return folder or MineFolder.slug, status or "open", uuid, in_page

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
Expand All @@ -250,7 +238,7 @@ def get_context_data(self, **kwargs):
context["status"] = status
context["has_tickets"] = self.request.org.tickets.exists()

folder = TicketFolder.from_id(self.request.org, folder)
folder = TicketFolder.from_slug(self.request.org, folder)
context["title"] = folder.name

if uuid:
Expand All @@ -259,10 +247,6 @@ def get_context_data(self, **kwargs):
return context

def build_context_menu(self, menu):
# we only support dynamic content menus
if "HTTP_TEMBA_CONTENT_MENU" not in self.request.META:
return

uuid = self.kwargs.get("uuid")
if uuid:
ticket = self.request.org.tickets.filter(uuid=uuid).first()
Expand Down Expand Up @@ -311,20 +295,20 @@ def derive_menu(self):
user = self.request.user
count_by_assignee = TicketCount.get_by_assignees(org, [None, user], Ticket.STATUS_OPEN)
counts = {
MineFolder.id: count_by_assignee[user],
UnassignedFolder.id: count_by_assignee[None],
AllFolder.id: TicketCount.get_all(org, Ticket.STATUS_OPEN),
MineFolder.slug: count_by_assignee[user],
UnassignedFolder.slug: count_by_assignee[None],
AllFolder.slug: TicketCount.get_all(org, Ticket.STATUS_OPEN),
}

menu = []
for folder in TicketFolder.all().values():
menu.append(
{
"id": folder.id,
"id": folder.slug,
"name": folder.name,
"icon": folder.icon,
"count": counts[folder.id],
"href": f"/ticket/{folder.id}/open/",
"count": counts[folder.slug],
"href": f"/ticket/{folder.slug}/open/",
}
)

Expand Down Expand Up @@ -370,37 +354,30 @@ def derive_url_pattern(cls, path, action):
return rf"^{path}/{action}/(?P<folder>{folders}|{UUID_REGEX.pattern})/(?P<status>open|closed)/((?P<uuid>[a-z0-9\-]+))?$"

@cached_property
def folder(self):
folder = TicketFolder.from_id(self.request.org, self.kwargs["folder"])
def folder(self) -> TicketFolder:
folder = TicketFolder.from_slug(self.request.org, self.kwargs["folder"])
if not folder:
raise Http404()

return folder

def build_context_menu(self, menu):
# we only support dynamic content menus
if "HTTP_TEMBA_CONTENT_MENU" not in self.request.META:
return

if (
self.has_org_perm("tickets.topic_update")
and isinstance(self.folder, TopicFolder)
and not self.folder.is_system
):

menu.add_modax(
_("Edit"),
"edit-topic",
f"{reverse('tickets.topic_update', args=[self.folder.id])}",
title=_("Edit Topic"),
on_submit="handleTopicUpdated()",
)

menu.add_modax(
_("Delete"),
"delete-topic",
f"{reverse('tickets.topic_delete', args=[self.folder.id])}",
title=_("Delete"),
)
if isinstance(self.folder, TopicFolder) and not self.folder.topic.is_system:
if self.has_org_perm("tickets.topic_update"):
menu.add_modax(
_("Edit"),
"edit-topic",
f"{reverse('tickets.topic_update', args=[self.folder.topic.id])}",
title=_("Edit Topic"),
on_submit="handleTopicUpdated()",
)
if self.has_org_perm("tickets.topic_delete"):
menu.add_modax(
_("Delete"),
"delete-topic",
f"{reverse('tickets.topic_delete', args=[self.folder.topic.id])}",
title=_("Delete Topic"),
)

def get_queryset(self, **kwargs):
org = self.request.org
Expand Down Expand Up @@ -502,7 +479,7 @@ def as_json(t):
# build up our next link if we have more
if len(context["tickets"]) >= self.paginate_by:
folder_url = reverse(
"tickets.ticket_folder", kwargs={"folder": self.folder.id, "status": self.kwargs["status"]}
"tickets.ticket_folder", kwargs={"folder": self.folder.slug, "status": self.kwargs["status"]}
)
last_time = results["results"][-1]["ticket"]["last_activity_on"]
results["next"] = f"{folder_url}?before={datetime_to_timestamp(last_time)}"
Expand Down

0 comments on commit a7dcb61

Please sign in to comment.