diff --git a/temba/api/v2/views.py b/temba/api/v2/views.py index f0d268d267..bbeb5f7ede 100644 --- a/temba/api/v2/views.py +++ b/temba/api/v2/views.py @@ -20,7 +20,7 @@ from temba.flows.models import Flow, FlowRun, FlowStart, FlowStartCount from temba.globals.models import Global from temba.locations.models import AdminBoundary, BoundaryAlias -from temba.msgs.models import Broadcast, BroadcastMsgCount, Label, LabelCount, Media, Msg, OptIn, SystemLabel +from temba.msgs.models import Broadcast, BroadcastMsgCount, Label, LabelCount, Media, Msg, MsgFolder, OptIn from temba.orgs.models import OrgMembership, User from temba.orgs.views.mixins import OrgPermsMixin from temba.tickets.models import Ticket, Topic @@ -2411,12 +2411,12 @@ def get_ordering(self, request, queryset, view=None): throttle_scope = "v2.messages" FOLDER_FILTERS = { - "inbox": SystemLabel.TYPE_INBOX, - "flows": SystemLabel.TYPE_FLOWS, - "archived": SystemLabel.TYPE_ARCHIVED, - "outbox": SystemLabel.TYPE_OUTBOX, - "failed": SystemLabel.TYPE_FAILED, - "sent": SystemLabel.TYPE_SENT, + "inbox": MsgFolder.INBOX, + "flows": MsgFolder.HANDLED, + "archived": MsgFolder.ARCHIVED, + "outbox": MsgFolder.OUTBOX, + "sent": MsgFolder.SENT, + "failed": MsgFolder.FAILED, } def derive_queryset(self): @@ -2424,9 +2424,9 @@ def derive_queryset(self): folder = self.request.query_params.get("folder") if folder: - sys_label = self.FOLDER_FILTERS.get(folder.lower()) - if sys_label: - return SystemLabel.get_queryset(org, sys_label) + msg_folder = self.FOLDER_FILTERS.get(folder.lower()) + if msg_folder: + return msg_folder.get_queryset(org) elif folder == "incoming": return self.model.objects.filter(org=org, direction=Msg.DIRECTION_IN, status=Msg.STATUS_HANDLED) else: diff --git a/temba/msgs/models.py b/temba/msgs/models.py index f08c77198b..d96d3ed5dc 100644 --- a/temba/msgs/models.py +++ b/temba/msgs/models.py @@ -5,6 +5,7 @@ import re from array import array from dataclasses import dataclass +from enum import Enum from fnmatch import fnmatch from urllib.parse import unquote, urlparse @@ -793,13 +794,94 @@ def bulk_annotate(cls, broadcasts): bcast.msg_count = counts_by_bcast.get(bcast.id, 0) +class MsgFolder(Enum): + """ + A folder of messages owned by an org. + """ + + INBOX = ( + "I", + dict( + direction=Msg.DIRECTION_IN, + visibility=Msg.VISIBILITY_VISIBLE, + status=Msg.STATUS_HANDLED, + flow__isnull=True, + msg_type=Msg.TYPE_TEXT, + ), + dict(direction="in", visibility="visible", status="handled", flow__isnull=True, type__ne="voice"), + ) + HANDLED = ( + "W", + dict( + direction=Msg.DIRECTION_IN, + visibility=Msg.VISIBILITY_VISIBLE, + status=Msg.STATUS_HANDLED, + flow__isnull=False, + msg_type=Msg.TYPE_TEXT, + ), + dict(direction="in", visibility="visible", status="handled", flow__isnull=False, type__ne="voice"), + ) + ARCHIVED = ( + "A", + dict( + direction=Msg.DIRECTION_IN, + visibility=Msg.VISIBILITY_ARCHIVED, + status=Msg.STATUS_HANDLED, + msg_type=Msg.TYPE_TEXT, + ), + dict(direction="in", visibility="archived", status="handled", type__ne="voice"), + ) + OUTBOX = ( + "O", + dict( + direction=Msg.DIRECTION_OUT, + visibility=Msg.VISIBILITY_VISIBLE, + status__in=(Msg.STATUS_INITIALIZING, Msg.STATUS_QUEUED, Msg.STATUS_ERRORED), + ), + dict(direction="out", visibility="visible", status__in=("initializing", "queued", "errored")), + ) + SENT = ( + "S", + dict( + direction=Msg.DIRECTION_OUT, + visibility=Msg.VISIBILITY_VISIBLE, + status__in=(Msg.STATUS_WIRED, Msg.STATUS_SENT, Msg.STATUS_DELIVERED, Msg.STATUS_READ), + ), + dict(direction="out", visibility="visible", status__in=("wired", "sent", "delivered", "read")), + ) + FAILED = ( + "X", + dict(direction=Msg.DIRECTION_OUT, visibility=Msg.VISIBILITY_VISIBLE, status=Msg.STATUS_FAILED), + dict(direction="out", visibility="visible", status="failed"), + ) + + def __init__(self, code, query: dict, archive_query: dict = None): + self.code = code + self.query = query + self.archive_query = archive_query + + def get_queryset(self, org): + return org.msgs.filter(**self.query) + + def get_archive_query(self) -> dict: + return self.archive_query.copy() + + @classmethod + def from_code(cls, code): + return next(f for f in cls if f.code == code) + + class SystemLabel: - TYPE_INBOX = "I" - TYPE_FLOWS = "W" - TYPE_ARCHIVED = "A" - TYPE_OUTBOX = "O" - TYPE_SENT = "S" - TYPE_FAILED = "X" + """ + TODO replace with MsgFolder once we figure out what to do with calls and broadcasts. + """ + + TYPE_INBOX = MsgFolder.INBOX.code + TYPE_FLOWS = MsgFolder.HANDLED.code + TYPE_ARCHIVED = MsgFolder.ARCHIVED.code + TYPE_OUTBOX = MsgFolder.OUTBOX.code + TYPE_SENT = MsgFolder.SENT.code + TYPE_FAILED = MsgFolder.FAILED.code TYPE_SCHEDULED = "E" TYPE_CALLS = "C" @@ -826,70 +908,18 @@ def get_queryset(cls, org, label_type): trigger used to maintain the label counts. """ - from temba.ivr.models import Call - assert label_type in [c[0] for c in cls.TYPE_CHOICES] - if label_type == cls.TYPE_INBOX: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_IN, - visibility=Msg.VISIBILITY_VISIBLE, - status=Msg.STATUS_HANDLED, - flow__isnull=True, - msg_type=Msg.TYPE_TEXT, - ) - elif label_type == cls.TYPE_FLOWS: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_IN, - visibility=Msg.VISIBILITY_VISIBLE, - status=Msg.STATUS_HANDLED, - flow__isnull=False, - msg_type=Msg.TYPE_TEXT, - ) - elif label_type == cls.TYPE_ARCHIVED: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_IN, - visibility=Msg.VISIBILITY_ARCHIVED, - status=Msg.STATUS_HANDLED, - msg_type=Msg.TYPE_TEXT, - ) - elif label_type == cls.TYPE_OUTBOX: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_OUT, - visibility=Msg.VISIBILITY_VISIBLE, - status__in=(Msg.STATUS_INITIALIZING, Msg.STATUS_QUEUED, Msg.STATUS_ERRORED), - ) - elif label_type == cls.TYPE_SENT: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_OUT, - visibility=Msg.VISIBILITY_VISIBLE, - status__in=(Msg.STATUS_WIRED, Msg.STATUS_SENT, Msg.STATUS_DELIVERED, Msg.STATUS_READ), - ) - elif label_type == cls.TYPE_FAILED: - qs = Msg.objects.filter( - direction=Msg.DIRECTION_OUT, visibility=Msg.VISIBILITY_VISIBLE, status=Msg.STATUS_FAILED - ) - elif label_type == cls.TYPE_SCHEDULED: - qs = Broadcast.objects.filter(is_active=True).exclude(schedule=None) + if label_type == cls.TYPE_SCHEDULED: + return org.broadcasts.filter(is_active=True).exclude(schedule=None) elif label_type == cls.TYPE_CALLS: - qs = Call.objects.all() + return org.calls.all() - return qs.filter(org=org) + return MsgFolder.from_code(label_type).get_queryset(org) @classmethod def get_archive_query(cls, label_type: str) -> dict: - if label_type == cls.TYPE_INBOX: - return dict(direction="in", visibility="visible", status="handled", flow__isnull=True, type__ne="voice") - elif label_type == cls.TYPE_FLOWS: - return dict(direction="in", visibility="visible", status="handled", flow__isnull=False, type__ne="voice") - elif label_type == cls.TYPE_ARCHIVED: - return dict(direction="in", visibility="archived", status="handled", type__ne="voice") - elif label_type == cls.TYPE_OUTBOX: - return dict(direction="out", visibility="visible", status__in=("initializing", "queued", "errored")) - elif label_type == cls.TYPE_SENT: - return dict(direction="out", visibility="visible", status__in=("wired", "sent", "delivered", "read")) - elif label_type == cls.TYPE_FAILED: - return dict(direction="out", visibility="visible", status="failed") + return MsgFolder.from_code(label_type).get_archive_query() class SystemLabelCount(BaseSquashableCount):