diff --git a/engage/hamls/msgs/message_box.haml b/engage/hamls/msgs/message_box.haml
index e4b24c2b2..913cb82a6 100644
--- a/engage/hamls/msgs/message_box.haml
+++ b/engage/hamls/msgs/message_box.haml
@@ -210,7 +210,7 @@
%td
.flex.w-full.items-end.justify-end.pr-4
- .time.whitespace-nowrap.eng
+ %a.time.whitespace-nowrap.eng{href:'{% url "msgs.msg_read" object.uuid %}'}
{% format_datetime object.created_on %}
-if show_channel_logs and not user_org.is_anon or perms.contacts.contact_break_anon
diff --git a/engage/hamls/msgs/msg_read.haml b/engage/hamls/msgs/msg_read.haml
index 30d167b1d..d14ed565b 100644
--- a/engage/hamls/msgs/msg_read.haml
+++ b/engage/hamls/msgs/msg_read.haml
@@ -8,108 +8,178 @@
temba-button {
display: block;
}
+ li {
+ list-style-type: disc;
+ }
+ .data-field {
+ display: flex;
+ margin-bottom: 1rem;
+ }
+ .data-label {
+ font-weight: bolder;
+ width: 12%;
+ }
+ .data-value {
+ font-weight: none;
+ }
+ .button-success {
+ background: lightgreen;
+ }
-block page-title
- {{title}}
+ {{ page_title }}
-block title-icon
%span.title-icon
.glyph.icon-inbox
-block title
- .flex.flex-wrap.flex-row.items-center
- .mr-3
- "Message Details"
+ "Message Details"
+ %hr
-block subtitle
- .flex.flex-wrap.mt-1
- {{ object.uuid }}
-
-
--block buttons
-
--block page-top
+ {% if object.direction == 'O' %}OUTGOING{% else %}INCOMING{% endif %}
-block content
-
+ %hr
+ %br
%div.sms.object
- %p
- id:
- %span
- {{object.id}}
- uuid:
- %span
- {{object.uuid}}
- contact:
- %span.whitespace-nowrap
- {{ object.contact|name_or_urn:user_org }}
- .time.whitespace-nowrap
- -block message_time
- {% short_datetime object.created_on %}
- -if show_channel_logs and not user_org.is_anon
- .inline-block.text-gray-400.linked.ml-3
- {% channel_log_link object %}
- %p
- %span.msg_text
- {% get_value object 'text' %}
- {% if object.attachments %}
- .value-attachments{ style:"margin-top: 5px" }
- - for attachment in object.attachments
- {% attachment_button attachment %}
- {% endif %}
-
- .data-field
- {{ object.id }}
- .data-field
- {{ object.uuid }}
- .data-field
- {{ object.text }}
- .data-field
- {{ object.high_priority }}
- .data-field
- {{ object.created_on }}
- .data-field
- {{ object.modified_on }}
- .data-field
- {{ object.sent_on }}
- .data-field
- {{ object.queued_on }}
.data-field
- {{ object.direction }}
- .data-field
- {{ object.status }}
- .data-field
- {{ object.visibility }}
- .data-field
- {{ object.msg_type }}
- .data-field
- {{ object.msg_count }}
- .data-field
- {{ object.error_count }}
- .data-field
- {{ object.next_attempt }}
- .data-field
- {{ object.external_id }}
- .data-field
- {{ object.attachments }}
- .data-field
- {{ object.metadata }}
- .data-field
- {{ object.broadcast_id }}
- .data-field
- {{ object.channel_id }}
- .data-field
- {{ object.contact_id }}
- .data-field
- {{ object.contact_urn_id }}
- .data-field
- {{ object.org_id }}
- .data-field
- {{ object.topup_id }}
- .data-field
- {{ object.failed_reason }}
+ .data-label
+ UUID:
+ .data-value
+ {{ object.uuid }}
+ .data-field
+ .data-label
+ ID:
+ .data-value
+ {{ object.id }}
+ .data-field
+ .data-label
+ Contact:
+ .data-value
+ %div
+ {{ object.contact|urn:user_org }}
+ {% if object.contact|name:user_org %}
+ %div
+ {{ object.contact|name:user_org }}
+ {% endif %}
+ %hr
+ .data-field
+
+ .data-label
+ Text:
+ .data-value
+ {{ msg_text }}
+ %hr
+ .data-field
+ .data-label
+ Attachments:
+ .data-value
+ {% if object.attachments %}
+ .value-attachments{ style:"margin-top: 5px" }
+ - for attachment in object.attachments
+ {% attachment_button attachment %}
+ {% else %}
+ None
+ {% endif %}
+ .data-field
+ .data-label
+ Labels:
+ .data-value
+ .labels.flex.items-center.flex-wrap
+ -for label in object.labels.all
+ .lbl.linked.ml-2
+ {{ label.name }}
+ .data-field
+ .data-label
+ Channel:
+ .data-value
+ {{ object.channel }}
+ address: {{ object.channel.address }}
+ id: {{ object.channel_id }}
+ {% if show_channel_logs and not user_org.is_anon %}
+ .data-field
+ .data-label
+ {% if err_channel_logs %}
+ Errors:
+ .inline-block.text-gray-400.linked.ml-3.button.button-danger
+ {% channel_log_link object %}
+ {% else %}
+ Channel Logs:
+ {% endif %}
+ .data-value
+ {% if err_channel_logs %}
+ -block log-entries
+ {% for log in err_channel_logs %}
+ -include "channels/channellog_log.haml"
+ {% endfor %}
+ {% else %}
+ .inline-block.linked.ml-3.button.button-success
+ {% channel_log_link object %}
+ {% endif %}
+ {% endif %}
+ {% if object.failed_reason %}
.data-field
- {{ object.flow_id }}
+ .data-label
+ Failed Reason:
+ .data-value
+ {{ object.failed_reason }}
+ {% endif %}
+ {% for msg_ts in msg_timestamps %}
+ .data-field.flex
+ .data-label
+ {{ msg_ts.label }}
+ %ul.data-value
+ %li
+ {% format_datetime msg_ts.value %}
+ %li
+ {% short_datetime msg_ts.value %}
+ %li
+ {{ msg_ts.value|date:"c" }}
+ %li
+ {{ msg_ts.value|date:"r" }}
+ {% endfor %}
+ .data-field
+ .data-label
+ Status:
+ .data-value
+ {{ msg_status }}
+ .data-field
+ .data-label
+ Visibility:
+ .data-value
+ {{ msg_visibility }}
+ .data-field
+ .data-label
+ Message Type:
+ .data-value
+ {{ msg_type }}
+ .data-field
+ .data-label
+ Flow ID:
+ .data-value
+ {{ object.flow_id }}
+ .data-field
+ .data-label
+ Is High Priority:
+ .data-value
+ {{ object.high_priority }}
+ .data-field
+ .data-label
+ External ID:
+ .data-value
+ {{ object.external_id }}
+ .data-field
+ .data-label
+ Metadata:
+ .data-value
+ {{ object.metadata }}
+ .data-field
+ .data-label
+ Broadcast ID:
+ .data-value
+ {{ object.broadcast_id }}
-block extra-script
{{ block.super }}
diff --git a/engage/msgs/views/read.py b/engage/msgs/views/read.py
index 6e660400b..54269af8a 100644
--- a/engage/msgs/views/read.py
+++ b/engage/msgs/views/read.py
@@ -5,26 +5,41 @@
from smartmin.views import SmartReadView
from engage.utils.middleware import RedirectTo
-from temba.msgs.models import Msg
+from engage.utils.strings import sanitize_text
+from temba.api.v2.serializers import MsgReadSerializer
+from temba.msgs.models import Msg
from temba.orgs.views import OrgPermsMixin
class Read(OrgPermsMixin, SmartReadView):
"""
- Choices are: attachments, broadcast, broadcast_id, channel, channel_id, channel_logs, contact, contact_id, contact_urn, contact_urn_id, created_on, direction, error_count, external_id, failed_reason, flow, flow_id, high_priority, id, labels, metadata, modified_on, msg_count, msg_type, next_attempt, org, org_id, queued_on, sent_on, status, text, topup, topup_id, uuid, visibility
+ Choices are: attachments, broadcast, broadcast_id,
+ channel, channel_id, channel_logs, contact, contact_id, contact_urn, contact_urn_id, reated_on,
+ direction, error_count, external_id, failed_reason, flow, flow_id, high_priority, id, labels,
+ metadata, modified_on, msg_count, msg_type, next_attempt, org, org_id, queued_on, sent_on, status,
+ text, topup, topup_id, uuid, visibility
"""
slug_url_kwarg = "uuid"
- fields = ("text",)
+ fields = Msg._meta.get_fields()
select_related = ("org", "contact", "current_flow",)
title = _("Message Details")
template_name = "msgs/msg_read.haml"
+ show_channel_logs = True
+ VISIBILITIES = { # deleted messages should never be exposed over API
+ Msg.VISIBILITY_VISIBLE: "visible",
+ Msg.VISIBILITY_ARCHIVED: "archived",
+ Msg.VISIBILITY_DELETED_BY_SENDER: "deleted by sender",
+ Msg.VISIBILITY_DELETED_BY_USER: "deleted by user",
+ }
+
logger = logging.getLogger()
def get_gear_links(self):
links = []
#user = self.get_user()
return links
+ #enddef get_gear_links
def has_permission(self, request: WSGIRequest, *args, **kwargs):
user = self.get_user()
@@ -47,4 +62,83 @@ def has_permission(self, request: WSGIRequest, *args, **kwargs):
#endif
#enddef
+ def get_queryset(self, **kwargs):
+ qs = super().get_queryset()
+ if self.show_channel_logs:
+ qs = qs.prefetch_related("channel_logs")
+ return qs
+ #enddef get_queryset
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ obj = self.get_object()
+ context['page_title'] = self.title
+
+ org = self.request.user.get_org()
+ context["org"] = org
+
+ if self.show_channel_logs:
+ if obj.channel_logs:
+ err_logs = sorted(obj.channel_logs.filter(is_error=True), key=lambda l: l.created_on, reverse=True)
+ context["err_channel_logs"] = err_logs if err_logs and len(err_logs) > 0 else None
+ msg_logs = sorted(obj.channel_logs.all(), key=lambda l: l.created_on, reverse=True)
+ context["msg_channel_logs"] = msg_logs if msg_logs and len(msg_logs) > 0 else None
+ context["show_channel_logs"] = msg_logs or err_logs
+ else:
+ context["msg_channel_logs"] = None
+ context["show_channel_logs"] = False
+ #endif
+
+ msg_text = obj.text
+ if msg_text:
+ # Ensure HTML in messages does not bork our display.
+ if isinstance(msg_text, str):
+ msg_text = sanitize_text(msg_text)
+ elif isinstance(msg_text, dict):
+ for key, val in msg_text.items():
+ if isinstance(val, str):
+ msg_text[key] = sanitize_text(val)
+ #endif
+ #endfor
+ #endif
+ #endif
+ context["msg_text"] = msg_text
+
+ msg_timestamps = [{
+ 'label': "Created:",
+ 'value': obj.created_on,
+ }]
+ if obj.queued_on:
+ msg_timestamps.append({
+ 'label': "Queued:",
+ 'value': obj.queued_on,
+ })
+ #endif
+ if obj.sent_on:
+ msg_timestamps.append({
+ 'label': "Sent:",
+ 'value': obj.sent_on,
+ })
+ #endif
+ if obj.modified_on and obj.modified_on != obj.created_on:
+ msg_timestamps.append({
+ 'label': "Modified:",
+ 'value': obj.modified_on,
+ })
+ #endif
+ if obj.next_attempt:
+ msg_timestamps.append({
+ 'label': "Next attempt:",
+ 'value': obj.next_attempt,
+ })
+ #endif
+ context['msg_timestamps'] = msg_timestamps
+ context['msg_status'] = MsgReadSerializer.STATUSES.get(obj.status)
+ context['msg_visibility'] = self.VISIBILITIES.get(obj.visibility)
+ context['msg_type'] = MsgReadSerializer.TYPES.get(obj.msg_type)
+
+ #self.logger.debug("context=", extra={'context': context})
+ return context
+ #enddef get_context_data
+
#endclass Read